Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(tangle-dapp): Various bug fixes #2796

Merged
merged 13 commits into from
Feb 14, 2025
Merged
2 changes: 0 additions & 2 deletions apps/tangle-dapp/src/app/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ import RestakePage from '../pages/restake';
import { PagePath } from '../types';
import Providers from './providers';

// TODO: Add metadata tags for SEO

function App() {
return (
<div>
Expand Down
37 changes: 0 additions & 37 deletions apps/tangle-dapp/src/components/AddressLink.tsx

This file was deleted.

2 changes: 1 addition & 1 deletion apps/tangle-dapp/src/components/ErrorMessage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export default function ErrorMessage({
children,
className,
typographyProps: {
variant = 'body4',
variant = 'body3',
className: typoClassName,
...typographyProps
} = {},
Expand Down
27 changes: 16 additions & 11 deletions apps/tangle-dapp/src/components/Lists/AssetList.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { ArrowRightUp, Search, TokenIcon } from '@webb-tools/icons';
import {
AmountFormatStyle,
formatDisplayAmount,
Input,
ListItem,
shortenHex,
Expand All @@ -9,16 +11,19 @@ import { ScrollArea } from '@webb-tools/webb-ui-components/components/ScrollArea
import { EMPTY_VALUE_PLACEHOLDER } from '@webb-tools/webb-ui-components/constants';
import { ComponentProps, useMemo, useState } from 'react';
import { twMerge } from 'tailwind-merge';
import { Address } from 'viem';

import { ListCardWrapper } from './ListCardWrapper';
import { BN } from '@polkadot/util';
import { EvmAddress } from '@webb-tools/webb-ui-components/types/address';

export type AssetConfig = {
name?: string;
symbol: string;
optionalSymbol?: string;
balance?: string;
balance?: BN;
explorerUrl?: string;
address?: Address;
address?: EvmAddress;
decimals: number;
};

type AssetListProps = {
Expand Down Expand Up @@ -76,23 +81,24 @@ export const AssetList = ({
}}
className="cursor-pointer w-full flex items-center gap-4 justify-between max-w-full min-h-[60px] py-[12px] px-6"
>
<div className="flex items-center gap-2">
<div className="flex items-center gap-3">
<TokenIcon
size="xl"
name={
asset.symbol === 'SolvBTC.BBN' ? 'SolvBTC' : asset.symbol
}
className="mr-2"
spinnerSize="lg"
/>

<div className="flex flex-col gap-1">
<Typography
variant="h5"
variant="body1"
fw="bold"
className="cursor-default text-mono-200 dark:text-mono-0"
>
{asset.symbol}
{asset.name === undefined
? asset.symbol
: `${asset.name} (${asset.symbol})`}
</Typography>

{asset.explorerUrl !== undefined && (
Expand All @@ -118,12 +124,11 @@ export const AssetList = ({
</div>

<Typography
variant="h5"
fw="bold"
variant="body1"
className="cursor-default text-mono-200 dark:text-mono-0"
>
{asset.balance
? `${asset.balance} ${asset.symbol}`
{asset.balance !== undefined
? `${formatDisplayAmount(asset.balance, asset.decimals, AmountFormatStyle.SHORT)} ${asset.symbol}`
: EMPTY_VALUE_PLACEHOLDER}
</Typography>
</ListItem>
Expand Down
262 changes: 262 additions & 0 deletions apps/tangle-dapp/src/components/TxHistoryDrawer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
'use client';

import * as Dialog from '@radix-ui/react-dialog';
import {
CheckboxCircleLine,
CloseCircleLineIcon,
InformationLine,
ShuffleLine,
Spinner,
} from '@webb-tools/icons';
import {
Alert,
AmountFormatStyle,
Button,
Chip,
formatDisplayAmount,
isEvmAddress,
isSubstrateAddress,
shortenHex,
shortenString,
Typography,
} from '@webb-tools/webb-ui-components';
import { useCallback, useMemo, useState } from 'react';
import { twMerge } from 'tailwind-merge';
import useTxHistoryStore, { HistoryTx } from '../context/useTxHistoryStore';
import useNetworkStore from '@webb-tools/tangle-shared-ui/context/useNetworkStore';
import useActiveAccountAddress from '@webb-tools/tangle-shared-ui/hooks/useActiveAccountAddress';
import { formatDistanceToNow } from 'date-fns';
import addCommasToNumber from '@webb-tools/webb-ui-components/utils/addCommasToNumber';
import {
EvmAddress,
SubstrateAddress,
} from '@webb-tools/webb-ui-components/types/address';
import { BN } from '@polkadot/util';
import { TANGLE_TOKEN_DECIMALS } from '@webb-tools/dapp-config';
import { capitalize } from 'lodash';
import ExternalLink from './ExternalLink';
import useAgnosticAccountInfo from '@webb-tools/tangle-shared-ui/hooks/useAgnosticAccountInfo';

const TxHistoryDrawer = () => {
const activeAccountAddress = useActiveAccountAddress();
const transactions = useTxHistoryStore((state) => state.transactions);
const networkId = useNetworkStore((store) => store.network2?.id);
const [isOpen, setIsOpen] = useState(true);

const relevantTransactions = useMemo(() => {
if (networkId === undefined || activeAccountAddress === null) {
return null;
}

return (
transactions
.filter(
(tx) =>
tx.network === networkId && tx.origin === activeAccountAddress,
)
// Sort by timestamp in descending order.
.toSorted((a, b) => b.timestamp - a.timestamp)
);
}, [activeAccountAddress, networkId, transactions]);

const inProgressCount = useMemo(() => {
if (relevantTransactions === null) {
return null;
}

const count = relevantTransactions.filter(
(tx) => tx.status === 'pending' || tx.status === 'inblock',
).length;

return count === 0 ? null : count;
}, [relevantTransactions]);

// Hide the button if there are no known transactions.
if (
relevantTransactions?.length === undefined ||
relevantTransactions.length === 0
) {
return null;
}

return (
<div className="flex items-center">
<Dialog.Root open={isOpen} onOpenChange={setIsOpen}>
<Dialog.Trigger className="outline-none">
<Button
variant="secondary"
className={twMerge(
'rounded-full border-2 py-2 px-4',
'bg-mono-0/10 border-mono-60 dark:border-mono-140',
'dark:bg-mono-0/5 dark:border-mono-140',
'hover:bg-mono-100/10 dark:hover:bg-mono-0/10',
'hover:border-mono-60 dark:hover:border-mono-140',
)}
>
<div className="flex items-center gap-1">
<ShuffleLine className="fill-mono-160 dark:fill-mono-0" />

<Typography
variant="body1"
fw="semibold"
className="text-mono-160 dark:text-mono-0"
>
Transactions{' '}
{inProgressCount !== null && (
<Chip color="yellow">
{addCommasToNumber(inProgressCount)}
</Chip>
)}
</Typography>
</div>
</Button>
</Dialog.Trigger>

<Dialog.Portal>
<Dialog.Overlay
forceMount
className={twMerge(
'fixed inset-0 z-20 bg-black/65 backdrop-blur-[1px]',
'animate-in duration-200 fade-in-0',
'data-[state=open]:ease-out data-[state=closed]:ease-in',
)}
/>

<Dialog.Content
forceMount
className={twMerge(
'w-[400px] h-[calc(100%-16px)] outline-none overflow-auto py-6 px-4 z-50 rounded-xl',
'bg-mono-0 dark:bg-mono-200 fixed right-2 top-2 bottom-2',
'flex flex-col gap-6 justify-between',
'data-[state=open]:animate-in data-[state=open]:ease-out data-[state=open]:duration-200',
'data-[state=open]:slide-in-from-right-full',
'data-[state=closed]:animate-out data-[state=closed]:ease-in data-[state=closed]:duration-100',
'data-[state=closed]:slide-out-to-right-full',
)}
>
<Dialog.Title className="sr-only">Sidebar Menu</Dialog.Title>

<Dialog.Description className="sr-only">
Sidebar Menu
</Dialog.Description>

<div className="space-y-3">
<div className="flex items-center justify-between">
<Typography variant="h5">Transactions</Typography>

<CloseCircleLineIcon />
</div>

{relevantTransactions !== null &&
relevantTransactions.map((tx) => (
<TransactionItem key={tx.hash} {...tx} />
))}
</div>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
</div>
);
};

/** @internal */
const TransactionItem = ({
hash,
name,
timestamp,
status,
details,
errorMessage,
}: HistoryTx) => {
const { isEvm } = useAgnosticAccountInfo();

// TODO: Open account details on explorer.
const createExplorerAccountUrl = useNetworkStore(
(store) => store.network2?.createExplorerAccountUrl,
);

const createExplorerTxUrl = useNetworkStore(
(store) => store.network2?.createExplorerTxUrl,
);

const formatDetailValue = useCallback(
(value: string | number | SubstrateAddress | EvmAddress | BN) => {
if (typeof value === 'number') {
return addCommasToNumber(value);
} else if (typeof value === 'string' && isEvmAddress(value)) {
return shortenHex(value);
} else if (typeof value === 'string' && isSubstrateAddress(value)) {
return shortenString(value);
} else if (typeof value === 'string') {
return value;
}

return formatDisplayAmount(
value,
TANGLE_TOKEN_DECIMALS,
AmountFormatStyle.SHORT,
);
},
[],
);

const explorerLink = useMemo(() => {
if (createExplorerTxUrl === undefined || isEvm === null) {
return null;
}

return createExplorerTxUrl(isEvm, hash);
}, [createExplorerTxUrl, hash, isEvm]);

return (
<div className="bg-mono-20 dark:bg-mono-180 rounded-md p-3 space-y-4">
<div className="flex items-center justify-between">
<div className="flex gap-2 items-center justify-start">
{status === 'finalized' ? (
<CheckboxCircleLine className="fill-green-50 dark:fill-green-50" />
) : status === 'failed' ? (
<InformationLine
size="md"
className="fill-red-70 dark:fill-red-50"
/>
) : (
<Spinner />
)}

<Typography variant="body1" className="dark:text-mono-0">
{capitalize(name)}
</Typography>
</div>

<ExternalLink href={explorerLink ?? '#'}>Explorer</ExternalLink>
</div>

<div className="flex gap-1 flex-wrap">
{details === undefined
? 'No details.'
: Array.from(details.entries()).map(([key, value]) => (
<Chip
className="normal-case cursor-default"
key={key}
color="blue"
>
{key}: {formatDetailValue(value)}
</Chip>
))}
</div>

{status === 'failed' && errorMessage !== undefined && (
<Alert type="error" size="sm" description={errorMessage} />
)}

<hr className="dark:border-mono-160" />

<Typography className="text-center" variant="body3">
{status} &bull;{' '}
{formatDistanceToNow(new Date(timestamp), { addSuffix: true })}
</Typography>
</div>
);
};

export default TxHistoryDrawer;
Loading
Loading