Skip to content

Commit

Permalink
feat(tangle-dapp): Bridge UI on Tangle Dapp (#2307)
Browse files Browse the repository at this point in the history
  • Loading branch information
vutuanlinh2k2 authored May 15, 2024
1 parent 21d7a18 commit 66e5fbf
Show file tree
Hide file tree
Showing 21 changed files with 584 additions and 73 deletions.
15 changes: 8 additions & 7 deletions apps/bridge-dapp/src/components/Header/ActiveChainDropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { DropdownMenuTrigger as DropdownButton } from '@radix-ui/react-dropdown-menu';
import { DropdownMenuTrigger } from '@radix-ui/react-dropdown-menu';
import {
useWebContext,
useConnectWallet,
Expand All @@ -14,7 +14,7 @@ import {
} from '@webb-tools/webb-ui-components/components/Dropdown';
import { MenuItem } from '@webb-tools/webb-ui-components/components/MenuItem';
import { ScrollArea } from '@webb-tools/webb-ui-components/components/ScrollArea';
import ChainButtonCmp from '@webb-tools/webb-ui-components/components/buttons/ChainButton';
import ChainOrTokenButton from '@webb-tools/webb-ui-components/components/buttons/ChainOrTokenButton';
import { useWebbUI } from '@webb-tools/webb-ui-components/hooks/useWebbUI';
import { useCallback, useMemo } from 'react';
import useChainsFromRoute from '../../hooks/useChainsFromRoute';
Expand Down Expand Up @@ -64,14 +64,15 @@ const ActiveChainDropdown = () => {

return (
<Dropdown>
<DropdownButton asChild disabled={loading}>
<ChainButtonCmp
chain={chain}
<DropdownMenuTrigger asChild disabled={loading}>
<ChainOrTokenButton
value={chain?.name}
status="success"
placeholder={activeChain === null ? 'Unsupported Chain' : undefined}
textClassname="hidden lg:!block"
textClassName="hidden lg:!block"
iconType="chain"
/>
</DropdownButton>
</DropdownMenuTrigger>
<DropdownBody className="mt-2">
<ScrollArea className="h-[var(--dropdown-height)]">
<ul>
Expand Down
6 changes: 3 additions & 3 deletions apps/bridge-dapp/src/pages/Account/AccountSummaryCard.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { DropdownMenuTrigger as DropdownButton } from '@radix-ui/react-dropdown-menu';
import { DropdownMenuTrigger } from '@radix-ui/react-dropdown-menu';
import { useWebContext } from '@webb-tools/api-provider-environment';
import { ZERO_BIG_INT } from '@webb-tools/dapp-config/constants';
import ArrowLeftRightLineIcon from '@webb-tools/icons/ArrowLeftRightLineIcon';
Expand Down Expand Up @@ -174,7 +174,7 @@ function TotalShieldedBalance() {
</Typography>

<Dropdown>
<DropdownButton
<DropdownMenuTrigger
className={cx(
'flex items-center gap-1 disabled:cursor-not-allowed',
{
Expand All @@ -193,7 +193,7 @@ function TotalShieldedBalance() {
</Typography>

<ChevronDown className="mx-2 transition-transform duration-300 ease-in-out enabled:group-radix-state-open:rotate-180" />
</DropdownButton>
</DropdownMenuTrigger>

<DropdownBody className="mt-2" align="center">
<ScrollArea className="max-h-[var(--dropdown-height)]">
Expand Down
67 changes: 67 additions & 0 deletions apps/tangle-dapp/app/bridge/AmountAndTokenInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
'use client';

import { DropdownMenuTrigger as DropdownTrigger } from '@radix-ui/react-dropdown-menu';
import { TokenIcon } from '@webb-tools/icons/TokenIcon';
import ChainOrTokenButton from '@webb-tools/webb-ui-components/components/buttons/ChainOrTokenButton';
import {
Dropdown,
DropdownBody,
} from '@webb-tools/webb-ui-components/components/Dropdown';
import { MenuItem } from '@webb-tools/webb-ui-components/components/MenuItem';
import { ScrollArea } from '@webb-tools/webb-ui-components/components/ScrollArea';
import { FC } from 'react';

import AmountInput from '../../components/AmountInput/AmountInput';
import { useBridge } from '../../context/BridgeContext';

const AmountAndTokenInput: FC = () => {
const { amount, setAmount, selectedToken, setSelectedToken, tokenOptions } =
useBridge();

return (
<div className="flex items-center gap-2 bg-mono-20 dark:bg-mono-160 rounded-lg pr-4">
<AmountInput
id="bridge-amount-input"
title="Amount"
amount={amount}
setAmount={setAmount}
baseInputOverrides={{
isFullWidth: true,
}}
placeholder=""
wrapperClassName="!pr-0"
/>
<Dropdown>
<DropdownTrigger asChild>
<ChainOrTokenButton
value={selectedToken.symbol}
status="success"
className="w-full bg-mono-0 dark:bg-mono-140 border-0 px-3"
iconType="token"
/>
</DropdownTrigger>
<DropdownBody className="border-0 w-[119px] min-w-fit">
<ScrollArea className="max-h-[300px]">
<ul>
{tokenOptions.map((token) => {
return (
<li key={token.id}>
<MenuItem
startIcon={<TokenIcon size="lg" name={token.symbol} />}
onSelect={() => setSelectedToken(token)}
className="px-3"
>
{token.symbol}
</MenuItem>
</li>
);
})}
</ul>
</ScrollArea>
</DropdownBody>
</Dropdown>
</div>
);
};

export default AmountAndTokenInput;
64 changes: 64 additions & 0 deletions apps/tangle-dapp/app/bridge/BridgeContainer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
'use client';

import Button from '@webb-tools/webb-ui-components/components/buttons/Button';
import { FC } from 'react';
import { twMerge } from 'tailwind-merge';

import AddressInput, {
AddressType,
} from '../../components/AddressInput/AddressInput';
import { useBridge } from '../../context/BridgeContext';
import AmountAndTokenInput from './AmountAndTokenInput';
import ChainSelectors from './ChainSelectors';
import useActionButton from './useActionButton';

interface BridgeContainerProps {
className?: string;
}

const BridgeContainer: FC<BridgeContainerProps> = ({ className }) => {
const { destinationAddress, setDestinationAddress } = useBridge();
const { buttonAction, buttonText, isLoading } = useActionButton();

return (
<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',
'shadow-webb-lg dark:shadow-webb-lg-dark',
'flex flex-col',
className
)}
>
<div className="flex-1 w-full flex flex-col justify-between">
<div className="space-y-8">
<ChainSelectors />

<AmountAndTokenInput />

<AddressInput
id="bridge-destination-address-input"
type={AddressType.Both}
title="Receiver Address"
baseInputOverrides={{ isFullWidth: true }}
value={destinationAddress}
setValue={setDestinationAddress}
/>

{/* TODO: Tx Info (Fees & Estimated Time) */}
</div>
<Button
isFullWidth
isDisabled={isLoading}
isLoading={isLoading}
onClick={buttonAction}
loadingText="Connecting..."
>
{buttonText}
</Button>
</div>
</div>
);
};

export default BridgeContainer;
109 changes: 109 additions & 0 deletions apps/tangle-dapp/app/bridge/ChainSelectors.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
'use client';

import { DropdownMenuTrigger as DropdownTrigger } from '@radix-ui/react-dropdown-menu';
import { ChainConfig } from '@webb-tools/dapp-config/chains/chain-config.interface';
import { ArrowRight } from '@webb-tools/icons/ArrowRight';
import { ChainIcon } from '@webb-tools/icons/ChainIcon';
import ChainOrTokenButton from '@webb-tools/webb-ui-components/components/buttons/ChainOrTokenButton';
import {
Dropdown,
DropdownBody,
} from '@webb-tools/webb-ui-components/components/Dropdown';
import { MenuItem } from '@webb-tools/webb-ui-components/components/MenuItem';
import { ScrollArea } from '@webb-tools/webb-ui-components/components/ScrollArea';
import { FC, useCallback } from 'react';

import { useBridge } from '../../context/BridgeContext';

interface ChainSelectorProps {
selectedChain: ChainConfig;
chainOptions: ChainConfig[];
onSelectChain: (chain: ChainConfig) => void;
className?: string;
}

const ChainSelectors: FC = () => {
const {
selectedSourceChain,
setSelectedSourceChain,
selectedDestinationChain,
setSelectedDestinationChain,
sourceChainOptions,
destinationChainOptions,
} = useBridge();

const switchChains = useCallback(() => {
const temp = selectedSourceChain;
setSelectedDestinationChain(temp);
setSelectedSourceChain(selectedDestinationChain);
}, [
setSelectedSourceChain,
setSelectedDestinationChain,
selectedDestinationChain,
selectedSourceChain,
]);

return (
<div className="flex flex-col md:flex-row justify-between items-center gap-3">
<ChainSelector
selectedChain={selectedSourceChain}
chainOptions={sourceChainOptions}
onSelectChain={setSelectedSourceChain}
className="flex-1 w-full md:w-auto"
/>

<div
className="cursor-pointer p-1 rounded-full hover:bg-mono-20 dark:hover:bg-mono-160"
onClick={switchChains}
>
<ArrowRight size="lg" className="rotate-90 md:rotate-0" />
</div>

<ChainSelector
selectedChain={selectedDestinationChain}
chainOptions={destinationChainOptions}
onSelectChain={setSelectedDestinationChain}
className="flex-1 w-full md:w-auto"
/>
</div>
);
};

const ChainSelector: FC<ChainSelectorProps> = ({
selectedChain,
chainOptions,
onSelectChain,
className,
}) => {
return (
<Dropdown className={className}>
<DropdownTrigger asChild>
<ChainOrTokenButton
value={selectedChain.name}
className="w-full bg-mono-20 dark:bg-mono-160 border-0 hover:bg-mono-20 dark:hover:bg-mono-160"
iconType="chain"
/>
</DropdownTrigger>
<DropdownBody className="border-0">
<ScrollArea className="max-h-[300px] w-[calc(100vw-74px)] md:w-[259px]">
<ul>
{chainOptions.map((chain) => {
return (
<li key={`${chain.chainType}-${chain.id}`}>
<MenuItem
startIcon={<ChainIcon size="lg" name={chain.name} />}
onSelect={() => onSelectChain(chain)}
>
{chain.name}
</MenuItem>
</li>
);
})}
</ul>
</ScrollArea>
</DropdownBody>
</Dropdown>
);
};

export default ChainSelectors;
9 changes: 9 additions & 0 deletions apps/tangle-dapp/app/bridge/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { FC, PropsWithChildren } from 'react';

import BridgeProvider from '../../context/BridgeContext';

const BridgeLayout: FC<PropsWithChildren> = ({ children }) => {
return <BridgeProvider>{children}</BridgeProvider>;
};

export default BridgeLayout;
19 changes: 19 additions & 0 deletions apps/tangle-dapp/app/bridge/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Metadata } from 'next';
import { FC } from 'react';

import createPageMetadata from '../../utils/createPageMetadata';
import BridgeContainer from './BridgeContainer';

export const metadata: Metadata = createPageMetadata({
title: 'Bridge',
});

const Bridge: FC = () => {
return (
<div>
<BridgeContainer className="mx-auto" />
</div>
);
};

export default Bridge;
32 changes: 32 additions & 0 deletions apps/tangle-dapp/app/bridge/useActionButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
'use client';

import {
useConnectWallet,
useWebContext,
} from '@webb-tools/api-provider-environment';
import { useCallback, useMemo } from 'react';

export default function useActionButton() {
const { activeAccount, activeWallet, loading, isConnecting } =
useWebContext();

const { toggleModal } = useConnectWallet();

const noActiveAccountOrWallet = useMemo(() => {
return !activeAccount || !activeWallet;
}, [activeAccount, activeWallet]);

const openWalletModal = useCallback(() => {
toggleModal(true);
}, [toggleModal]);

const bridgeTx = useCallback(() => {
// TODO: handle bridge Tx for each case from the source and destination chain
}, []);

return {
isLoading: loading || isConnecting,
buttonAction: noActiveAccountOrWallet ? openWalletModal : bridgeTx,
buttonText: noActiveAccountOrWallet ? 'Connect' : 'Approve',
};
}
Loading

0 comments on commit 66e5fbf

Please sign in to comment.