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

chore(tangle-dapp): Selector Modals, Bridge UI cleanup & Remove Sygma Bridge #2596

Draft
wants to merge 3 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 106 additions & 50 deletions apps/tangle-dapp/app/bridge/AmountAndTokenInput.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,22 @@
'use client';

import { DropdownMenuTrigger as DropdownTrigger } from '@radix-ui/react-dropdown-menu';
import { TokenIcon } from '@webb-tools/icons/TokenIcon';
import { Modal, ModalContent, useModal } from '@webb-tools/webb-ui-components';
import ChainOrTokenButton from '@webb-tools/webb-ui-components/components/buttons/ChainOrTokenButton';
import {
Dropdown,
DropdownBody,
DropdownMenuItem,
} from '@webb-tools/webb-ui-components/components/Dropdown';
import { ScrollArea } from '@webb-tools/webb-ui-components/components/ScrollArea';
import SkeletonLoader from '@webb-tools/webb-ui-components/components/SkeletonLoader';
import { Typography } from '@webb-tools/webb-ui-components/typography/Typography';
import Decimal from 'decimal.js';
import { FC, useMemo } from 'react';
import { FC, useCallback, useEffect, useMemo, useState } from 'react';
import { twMerge } from 'tailwind-merge';

import AmountInput from '../../components/AmountInput/AmountInput';
import { AssetConfig, AssetList } from '../../components/Lists/AssetList';
import { BRIDGE_SUPPORTED_TOKENS } from '../../constants/bridge';
import { useBridge } from '../../context/BridgeContext';
import useExplorerUrl from '../../hooks/useExplorerUrl';
import { BridgeTokenId } from '../../types/bridge';
import convertDecimalToBn from '../../utils/convertDecimalToBn';
import useBalance from './hooks/useBalance';
import { useTokenBalances } from './hooks/useBalance';
import useDecimals from './hooks/useDecimals';
import useSelectedToken from './hooks/useSelectedToken';
import useTypedChainId from './hooks/useTypedChainId';
Expand All @@ -32,34 +29,101 @@ const AmountAndTokenInput: FC = () => {
tokenIdOptions,
setIsAmountInputError,
isAmountInputError,
feeItems,
selectedSourceChain,
} = useBridge();
const selectedToken = useSelectedToken();
const { balance, isLoading } = useBalance();
const decimals = useDecimals();
const { sourceTypedChainId } = useTypedChainId();

const getExplorerUrl = useExplorerUrl();

const minAmount = useMemo(() => {
const existentialDeposit =
selectedToken.existentialDeposit[sourceTypedChainId];
const destChainTransactionFee =
selectedToken.destChainTransactionFee[sourceTypedChainId];

return (existentialDeposit ?? new Decimal(0))
.add(destChainTransactionFee ?? new Decimal(0))
.add(feeItems.sygmaBridge?.amount ?? new Decimal(0));
return (existentialDeposit ?? new Decimal(0)).add(
destChainTransactionFee ?? new Decimal(0),
);
}, [
selectedToken.existentialDeposit,
selectedToken.destChainTransactionFee,
sourceTypedChainId,
feeItems.sygmaBridge?.amount,
]);

const {
status: isTokenModalOpen,
open: openTokenModal,
close: closeTokenModal,
} = useModal(false);

const { getTokenBalance } = useTokenBalances();
const [tokenBalances, setTokenBalances] = useState<
Record<string, Decimal | null>
>({});

const fetchBalances = useCallback(async () => {
const balances: Record<string, Decimal | null> = {};
for (const tokenId of tokenIdOptions) {
const token = BRIDGE_SUPPORTED_TOKENS[tokenId];
const erc20TokenContractAddress =
token.erc20TokenContractAddress?.[sourceTypedChainId];
balances[tokenId] = await getTokenBalance(
erc20TokenContractAddress ?? '0x0',
token.decimals[sourceTypedChainId] ?? 18,
);
}
setTokenBalances(balances);
}, [tokenIdOptions, getTokenBalance, sourceTypedChainId]);

useEffect(() => {
fetchBalances();
}, [fetchBalances]);

const assets: AssetConfig[] = useMemo(() => {
return tokenIdOptions.map((tokenId) => {
const token = BRIDGE_SUPPORTED_TOKENS[tokenId];
const erc20TokenContractAddress =
token.erc20TokenContractAddress?.[sourceTypedChainId];
const selectedChainExplorerUrl =
selectedSourceChain.blockExplorers?.default;
const explorerUrl = getExplorerUrl(
erc20TokenContractAddress ?? '0x0',
'address',
'web3',
selectedChainExplorerUrl?.url,
false,
);
return {
symbol: token.symbol,
balance: tokenBalances[tokenId] ?? new Decimal(0),
explorerUrl: explorerUrl?.toString(),
};
});
}, [
tokenIdOptions,
sourceTypedChainId,
selectedSourceChain.blockExplorers?.default,
getExplorerUrl,
tokenBalances,
]);

const onSelectAsset = (asset: AssetConfig) => {
setSelectedTokenId(asset.symbol as BridgeTokenId);
closeTokenModal();
};

const selectedAssetBalance = useMemo(() => {
return tokenBalances[selectedToken.id] ?? new Decimal(0);
}, [tokenBalances, selectedToken.id]);

return (
<div className="relative">
<div
className={twMerge(
'w-full flex items-center gap-2 bg-mono-20 dark:bg-mono-160 rounded-lg pr-4',
'w-full flex items-center gap-2 bg-mono-20 dark:bg-mono-170 rounded-lg pr-4',
isAmountInputError && 'border border-red-70 dark:border-red-50',
)}
>
Expand All @@ -83,39 +147,15 @@ const AmountAndTokenInput: FC = () => {
}
errorMessageClassName="absolute left-0 bottom-[-24px] !text-[14px] !leading-[21px]"
/>
<Dropdown>
<DropdownTrigger asChild>
<ChainOrTokenButton
value={selectedToken.symbol}
status="success"
className={twMerge(
'w-[130px] border-0 px-3 bg-[#EFF3F6] dark:bg-mono-140',
'hover:bg-[#EFF3F6] dark:hover:bg-mono-140',
)}
iconType="token"
/>
</DropdownTrigger>
<DropdownBody className="border-0 w-[119px] min-w-fit mr-[11px]">
<ScrollArea className="max-h-[300px] w-[130px]">
<ul>
{tokenIdOptions.map((tokenId) => {
const token = BRIDGE_SUPPORTED_TOKENS[tokenId];
return (
<li key={tokenId}>
<DropdownMenuItem
leftIcon={<TokenIcon size="lg" name={token.symbol} />}
onSelect={() => setSelectedTokenId(tokenId)}
className="px-3 normal-case"
>
{token.symbol}
</DropdownMenuItem>
</li>
);
})}
</ul>
</ScrollArea>
</DropdownBody>
</Dropdown>

{/* Token Selector */}
<ChainOrTokenButton
value={selectedToken.symbol}
iconType="token"
onClick={openTokenModal}
className="w-[130px] border-0 px-3 bg-[#EFF3F6] dark:bg-mono-140"
status="success"
/>
</div>

{isLoading ? (
Expand All @@ -129,11 +169,27 @@ const AmountAndTokenInput: FC = () => {
className="absolute right-0 bottom-[-24px] text-mono-120 dark:text-mono-100"
>
Balance:{' '}
{balance !== null
? `${balance.toString()} ${selectedToken.symbol}`
{selectedAssetBalance !== null
? `${selectedAssetBalance.toString()} ${selectedToken.symbol}`
: 'N/A'}
</Typography>
)}

<Modal>
{/* Token Selector Modal */}
<ModalContent
isCenter
isOpen={isTokenModalOpen}
onInteractOutside={closeTokenModal}
className="w-[500px] h-[600px]"
>
<AssetList
onClose={closeTokenModal}
assets={assets}
onSelectAsset={onSelectAsset}
/>
</ModalContent>
</Modal>
</div>
);
};
Expand Down
49 changes: 39 additions & 10 deletions apps/tangle-dapp/app/bridge/BridgeConfirmationModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
Typography,
useWebbUI,
} from '@webb-tools/webb-ui-components';
import { shortenHex } from '@webb-tools/webb-ui-components/utils/shortenHex';
import { FC, useCallback } from 'react';

import { useBridge } from '../../context/BridgeContext';
Expand Down Expand Up @@ -122,7 +123,6 @@ const BridgeConfirmationModal: FC<BridgeConfirmationModalProps> = ({
handleClose(); // TODO: handle clear form
}}
isDisabled={
feeItems.sygmaBridge?.isLoading ||
feeItems.hyperlaneInterchain?.isLoading ||
feeItems.gas?.isLoading ||
feeItems.gas?.amount === null ||
Expand Down Expand Up @@ -150,33 +150,62 @@ const ConfirmationItem: FC<{
tokenName: string;
}> = ({ type, chainName, accAddress, amount, tokenName }) => {
return (
<div className="bg-mono-20 dark:bg-mono-160 w-full space-y-2 p-4 rounded-xl">
<div className="bg-mono-20 dark:bg-mono-170 w-full space-y-2 p-4 rounded-xl">
<div className="flex justify-between items-center">
<Typography variant="body1">
<Typography
variant="body1"
className="text-mono-120 dark:text-mono-120"
fw="bold"
>
{type === 'source' ? 'From' : 'To'}
</Typography>
<div className="flex items-center gap-1.5">
<ChainIcon name={chainName} />
<Typography variant="body1">{chainName}</Typography>
<Typography
variant="body1"
className="text-mono-200 dark:text-mono-0"
>
{chainName}
</Typography>
</div>
</div>
<div className="flex justify-between items-center">
<Typography variant="body1">Account</Typography>
<Typography
variant="body1"
className="max-w-[65%] break-words text-right"
fw="bold"
className="text-mono-120 dark:text-mono-120"
>
{accAddress}
Account
</Typography>
<Typography
variant="body1"
className="max-w-[65%] break-words text-right text-mono-200 dark:text-mono-0"
>
{shortenHex(accAddress, 5)}
</Typography>
</div>
<div className="flex justify-between items-center">
<Typography variant="body1">Amount</Typography>
<Typography
variant="body1"
fw="bold"
className="text-mono-120 dark:text-mono-120"
>
Amount
</Typography>
<div className="flex items-center gap-1.5">
<Typography variant="body1" fw="bold">
<Typography
variant="body1"
className="text-mono-200 dark:text-mono-0"
>
{amount}
</Typography>
<TokenIcon name={tokenName} />
<Typography variant="body1">{tokenName}</Typography>
<Typography
variant="body1"
className="text-mono-200 dark:text-mono-0"
>
{tokenName}
</Typography>
</div>
</div>
</div>
Expand Down
17 changes: 5 additions & 12 deletions apps/tangle-dapp/app/bridge/BridgeContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,10 @@ const BridgeContainer: FC<BridgeContainerProps> = ({ className }) => {
} = useBridge();
const activeAccountAddress = useActiveAccountAddress();
const [isConfirmModalOpen, setIsConfirmModalOpen] = useState(false);
const {
buttonAction,
buttonText,
buttonLoadingText,
isLoading,
isDisabled,
// errorMessage,
} = useActionButton({
handleOpenConfirmModal: () => setIsConfirmModalOpen(true),
});
const { buttonAction, buttonText, buttonLoadingText, isLoading, isDisabled } =
useActionButton({
handleOpenConfirmModal: () => setIsConfirmModalOpen(true),
});

const hideFeeDetails = useMemo(
() =>
Expand All @@ -63,8 +57,7 @@ const BridgeContainer: FC<BridgeContainerProps> = ({ className }) => {
<>
<div
className={twMerge(
'max-w-[640px] min-h-[580px] bg-mono-0 dark:bg-mono-190 p-5 md:p-8',
'rounded-xl border border-mono-40 dark:border-mono-160',
'max-w-[640px] min-h-[580px] bg-mono-0 dark:bg-mono-190 p-5 md:p-8 rounded-xl',
'shadow-webb-lg dark:shadow-webb-lg-dark',
'flex flex-col',
className,
Expand Down
Loading