From 38c50178b9829ec990a7976eb7b0cd9b0ba5582a Mon Sep 17 00:00:00 2001 From: vutuanlinh2k2 Date: Sat, 11 May 2024 15:45:21 +0700 Subject: [PATCH 01/13] feat: add new Bridge page and prepare the UI --- .../app/bridge/BridgeContainer.tsx | 98 +++++++++++++++++++ apps/tangle-dapp/app/bridge/ChainSelector.tsx | 62 ++++++++++++ apps/tangle-dapp/app/bridge/layout.tsx | 9 ++ apps/tangle-dapp/app/bridge/page.tsx | 19 ++++ .../components/AmountInput/AmountInput.tsx | 4 +- .../components/Breadcrumbs/Breadcrumbs.tsx | 2 + .../components/Sidebar/sidebarProps.ts | 10 ++ apps/tangle-dapp/constants/bridge.ts | 8 ++ apps/tangle-dapp/context/BridgeContext.tsx | 79 +++++++++++++++ apps/tangle-dapp/context/RestakeContext.tsx | 6 +- apps/tangle-dapp/types/index.ts | 1 + .../src/components/buttons/ChainButton.tsx | 2 +- 12 files changed, 295 insertions(+), 5 deletions(-) create mode 100644 apps/tangle-dapp/app/bridge/BridgeContainer.tsx create mode 100644 apps/tangle-dapp/app/bridge/ChainSelector.tsx create mode 100644 apps/tangle-dapp/app/bridge/layout.tsx create mode 100644 apps/tangle-dapp/app/bridge/page.tsx create mode 100644 apps/tangle-dapp/constants/bridge.ts create mode 100644 apps/tangle-dapp/context/BridgeContext.tsx diff --git a/apps/tangle-dapp/app/bridge/BridgeContainer.tsx b/apps/tangle-dapp/app/bridge/BridgeContainer.tsx new file mode 100644 index 0000000000..d22bfbd78c --- /dev/null +++ b/apps/tangle-dapp/app/bridge/BridgeContainer.tsx @@ -0,0 +1,98 @@ +'use client'; + +import { ArrowRight } from '@webb-tools/icons'; +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 AmountInput from '../../components/AmountInput/AmountInput'; +import { BRIDGE_SUPPORTED_CHAINS } from '../../constants/bridge'; +import { useBridge } from '../../context/BridgeContext'; +import ChainSelector from './ChainSelector'; + +interface BridgeContainerProps { + className?: string; +} + +const BridgeContainer: FC = ({ className }) => { + const { + sourceChain, + setSourceChain, + destinationChain, + setDestinationChain, + destinationAddress, + setDestinationAddress, + amount, + setAmount, + isLoading, + } = useBridge(); + + return ( +
+
+
+ {/* Chain Dropdowns */} +
+ + +
+ +
+ + +
+ + + + + + {/* Tx Info (Fees & Estimated Time) */} +
+ +
+
+ ); +}; + +export default BridgeContainer; diff --git a/apps/tangle-dapp/app/bridge/ChainSelector.tsx b/apps/tangle-dapp/app/bridge/ChainSelector.tsx new file mode 100644 index 0000000000..86e4730586 --- /dev/null +++ b/apps/tangle-dapp/app/bridge/ChainSelector.tsx @@ -0,0 +1,62 @@ +'use client'; + +import { DropdownMenuTrigger as DropdownTrigger } from '@radix-ui/react-dropdown-menu'; +import { ChainConfig } from '@webb-tools/dapp-config/chains/chain-config.interface'; +import { ChainIcon } from '@webb-tools/icons/ChainIcon'; +import ChainButton from '@webb-tools/webb-ui-components/components/buttons/ChainButton'; +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'; + +interface ChainSelectorProps { + title: string; + selectedChain: ChainConfig | null; + chainOptions: ChainConfig[]; + onSelectChain: (chain: ChainConfig) => void; + className?: string; +} + +const ChainSelector: FC = ({ + title, + selectedChain, + chainOptions, + onSelectChain, + className, +}) => { + return ( + + + + + + +
    + {chainOptions.map((chain) => { + return ( +
  • + } + onSelect={() => onSelectChain(chain)} + > + {chain.name} + +
  • + ); + })} +
+
+
+
+ ); +}; + +export default ChainSelector; diff --git a/apps/tangle-dapp/app/bridge/layout.tsx b/apps/tangle-dapp/app/bridge/layout.tsx new file mode 100644 index 0000000000..be0bbdd8af --- /dev/null +++ b/apps/tangle-dapp/app/bridge/layout.tsx @@ -0,0 +1,9 @@ +import { FC, PropsWithChildren } from 'react'; + +import BridgeProvider from '../../context/BridgeContext'; + +const BridgeLayout: FC = ({ children }) => { + return {children}; +}; + +export default BridgeLayout; diff --git a/apps/tangle-dapp/app/bridge/page.tsx b/apps/tangle-dapp/app/bridge/page.tsx new file mode 100644 index 0000000000..aa2ef0e921 --- /dev/null +++ b/apps/tangle-dapp/app/bridge/page.tsx @@ -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 ( +
+ +
+ ); +}; + +export default Bridge; diff --git a/apps/tangle-dapp/components/AmountInput/AmountInput.tsx b/apps/tangle-dapp/components/AmountInput/AmountInput.tsx index f297fb597b..3c52043ffc 100644 --- a/apps/tangle-dapp/components/AmountInput/AmountInput.tsx +++ b/apps/tangle-dapp/components/AmountInput/AmountInput.tsx @@ -20,6 +20,7 @@ export type AmountInputProps = { errorOnEmptyValue?: boolean; setAmount: (newAmount: BN | null) => void; setErrorMessage?: (error: string | null) => void; + placeholder?: string; }; const AmountInput: FC = ({ @@ -36,6 +37,7 @@ const AmountInput: FC = ({ baseInputOverrides, errorOnEmptyValue = false, setErrorMessage, + placeholder, }) => { const inputRef = useRef(null); const { nativeTokenSymbol } = useNetworkStore(); @@ -99,7 +101,7 @@ const AmountInput: FC = ({ inputRef={inputRef} inputClassName="placeholder:text-md" type="text" - placeholder={`0 ${nativeTokenSymbol}`} + placeholder={placeholder ?? `0 ${nativeTokenSymbol}`} size="sm" autoComplete="off" value={displayAmount} diff --git a/apps/tangle-dapp/components/Breadcrumbs/Breadcrumbs.tsx b/apps/tangle-dapp/components/Breadcrumbs/Breadcrumbs.tsx index f9ceff2679..a143765887 100644 --- a/apps/tangle-dapp/components/Breadcrumbs/Breadcrumbs.tsx +++ b/apps/tangle-dapp/components/Breadcrumbs/Breadcrumbs.tsx @@ -2,6 +2,7 @@ import { isAddress } from '@polkadot/util-crypto'; import { + ArrowLeftRightLineIcon, CheckboxBlankCircleLine, CodeFill, FundsLine, @@ -30,6 +31,7 @@ const BREADCRUMB_ICONS: Record = { services: , restake: , nomination: , + bridge: , }; // TODO: Need to statically link the breadcrumb labels to the page path for better type safety and to enable fearless refactoring in the future. diff --git a/apps/tangle-dapp/components/Sidebar/sidebarProps.ts b/apps/tangle-dapp/components/Sidebar/sidebarProps.ts index 9b183f032e..73f2cfde2b 100644 --- a/apps/tangle-dapp/components/Sidebar/sidebarProps.ts +++ b/apps/tangle-dapp/components/Sidebar/sidebarProps.ts @@ -1,6 +1,7 @@ import { isAppEnvironmentType } from '@webb-tools/dapp-config/types'; import { AppsLine, + ArrowLeftRightLineIcon, DocumentationIcon, FundsLine, GiftLineIcon, @@ -33,6 +34,15 @@ const SIDEBAR_STATIC_ITEMS: SideBarItemProps[] = [ Icon: UserLineIcon, subItems: [], }, + { + name: 'Bridge', + href: PagePath.BRIDGE, + isInternal: true, + isNext: true, + Icon: ArrowLeftRightLineIcon, + subItems: [], + environments: ['development', 'staging', 'test'], + }, { name: 'Services', href: '', diff --git a/apps/tangle-dapp/constants/bridge.ts b/apps/tangle-dapp/constants/bridge.ts new file mode 100644 index 0000000000..bb0b057daa --- /dev/null +++ b/apps/tangle-dapp/constants/bridge.ts @@ -0,0 +1,8 @@ +import { chainsConfig } from '@webb-tools/dapp-config'; +import { ChainConfig } from '@webb-tools/dapp-config/chains/chain-config.interface'; +import { PresetTypedChainId } from '@webb-tools/dapp-types'; + +export const BRIDGE_SUPPORTED_CHAINS: ChainConfig[] = [ + chainsConfig[PresetTypedChainId.TangleMainnetNative], + chainsConfig[PresetTypedChainId.TangleTestnetNative], +]; diff --git a/apps/tangle-dapp/context/BridgeContext.tsx b/apps/tangle-dapp/context/BridgeContext.tsx new file mode 100644 index 0000000000..ef5f82916b --- /dev/null +++ b/apps/tangle-dapp/context/BridgeContext.tsx @@ -0,0 +1,79 @@ +'use client'; + +import { BN } from '@polkadot/util'; +import { useWebContext } from '@webb-tools/api-provider-environment'; +import { ChainConfig } from '@webb-tools/dapp-config/chains/chain-config.interface'; +import { + createContext, + FC, + PropsWithChildren, + useContext, + useState, +} from 'react'; + +interface BridgeContextProps { + sourceChain: ChainConfig | null; + setSourceChain: (chain: ChainConfig) => void; + destinationChain: ChainConfig | null; + setDestinationChain: (chain: ChainConfig) => void; + destinationAddress: string; + setDestinationAddress: (address: string) => void; + amount: BN | null; + setAmount: (amount: BN | null) => void; + isLoading: boolean; +} + +const BridgeContext = createContext({ + sourceChain: null, + setSourceChain: () => { + // + }, + destinationChain: null, + setDestinationChain: () => { + // + }, + destinationAddress: '', + setDestinationAddress: () => { + // + }, + amount: null, + setAmount: () => { + // + }, + isLoading: false, +}); + +export const useBridge = () => { + return useContext(BridgeContext); +}; + +const BridgeProvider: FC = ({ children }) => { + const { loading: isLoading } = useWebContext(); + + const [sourceChain, setSourceChain] = useState(null); + const [destinationChain, setDestinationChain] = useState( + null + ); + const [destinationAddress, setDestinationAddress] = useState(''); + const [amount, setAmount] = useState(null); + + return ( + + {children} + + ); +}; + +export default BridgeProvider; diff --git a/apps/tangle-dapp/context/RestakeContext.tsx b/apps/tangle-dapp/context/RestakeContext.tsx index bc080c1a91..96affd96b1 100644 --- a/apps/tangle-dapp/context/RestakeContext.tsx +++ b/apps/tangle-dapp/context/RestakeContext.tsx @@ -9,13 +9,13 @@ import useRestakingEarnings from '../data/restaking/useRestakingEarnings'; import useRestakingRoleLedger from '../data/restaking/useRestakingRoleLedger'; import useSubstrateAddress from '../hooks/useSubstrateAddress'; -export type RestakeContextType = { +interface RestakeContextProps { ledger: Option | null; earningsRecord: EarningRecord | null; isLoading: boolean; -}; +} -export const RestakeContext = createContext({ +export const RestakeContext = createContext({ ledger: null, earningsRecord: null, isLoading: true, diff --git a/apps/tangle-dapp/types/index.ts b/apps/tangle-dapp/types/index.ts index d5648891b7..a61d203cb7 100644 --- a/apps/tangle-dapp/types/index.ts +++ b/apps/tangle-dapp/types/index.ts @@ -14,6 +14,7 @@ export enum PagePath { NOMINATION = '/nomination', CLAIM_AIRDROP = '/claim', ACCOUNT = '/', + BRIDGE = '/bridge', SERVICES_OVERVIEW = '/services', SERVICES_RESTAKE = '/restake', } diff --git a/libs/webb-ui-components/src/components/buttons/ChainButton.tsx b/libs/webb-ui-components/src/components/buttons/ChainButton.tsx index 475f2eb765..8cd7a1572e 100644 --- a/libs/webb-ui-components/src/components/buttons/ChainButton.tsx +++ b/libs/webb-ui-components/src/components/buttons/ChainButton.tsx @@ -36,7 +36,7 @@ const ChainButton = forwardRef( )} ref={ref} > -
+
{chain && ( Date: Sat, 11 May 2024 16:54:09 +0700 Subject: [PATCH 02/13] chore: handle select chain --- .../components/Header/ActiveChainDropdown.tsx | 2 +- .../app/bridge/BridgeContainer.tsx | 32 +---- apps/tangle-dapp/app/bridge/ChainSelector.tsx | 62 --------- .../tangle-dapp/app/bridge/ChainSelectors.tsx | 123 ++++++++++++++++++ apps/tangle-dapp/context/BridgeContext.tsx | 12 +- .../src/components/buttons/ChainButton.tsx | 6 +- .../src/components/buttons/types.ts | 2 +- 7 files changed, 141 insertions(+), 98 deletions(-) delete mode 100644 apps/tangle-dapp/app/bridge/ChainSelector.tsx create mode 100644 apps/tangle-dapp/app/bridge/ChainSelectors.tsx diff --git a/apps/bridge-dapp/src/components/Header/ActiveChainDropdown.tsx b/apps/bridge-dapp/src/components/Header/ActiveChainDropdown.tsx index a7ffdbd41a..4d88f61eba 100644 --- a/apps/bridge-dapp/src/components/Header/ActiveChainDropdown.tsx +++ b/apps/bridge-dapp/src/components/Header/ActiveChainDropdown.tsx @@ -69,7 +69,7 @@ const ActiveChainDropdown = () => { chain={chain} status="success" placeholder={activeChain === null ? 'Unsupported Chain' : undefined} - textClassname="hidden lg:!block" + textClassName="hidden lg:!block" /> diff --git a/apps/tangle-dapp/app/bridge/BridgeContainer.tsx b/apps/tangle-dapp/app/bridge/BridgeContainer.tsx index d22bfbd78c..5c48503497 100644 --- a/apps/tangle-dapp/app/bridge/BridgeContainer.tsx +++ b/apps/tangle-dapp/app/bridge/BridgeContainer.tsx @@ -1,6 +1,5 @@ 'use client'; -import { ArrowRight } from '@webb-tools/icons'; import Button from '@webb-tools/webb-ui-components/components/buttons/Button'; import { FC } from 'react'; import { twMerge } from 'tailwind-merge'; @@ -9,9 +8,8 @@ import AddressInput, { AddressType, } from '../../components/AddressInput/AddressInput'; import AmountInput from '../../components/AmountInput/AmountInput'; -import { BRIDGE_SUPPORTED_CHAINS } from '../../constants/bridge'; import { useBridge } from '../../context/BridgeContext'; -import ChainSelector from './ChainSelector'; +import ChainSelectors from './ChainSelectors'; interface BridgeContainerProps { className?: string; @@ -19,10 +17,6 @@ interface BridgeContainerProps { const BridgeContainer: FC = ({ className }) => { const { - sourceChain, - setSourceChain, - destinationChain, - setDestinationChain, destinationAddress, setDestinationAddress, amount, @@ -41,29 +35,9 @@ const BridgeContainer: FC = ({ className }) => { )} >
-
+
{/* Chain Dropdowns */} -
- - -
- -
- - -
+ void; - className?: string; -} - -const ChainSelector: FC = ({ - title, - selectedChain, - chainOptions, - onSelectChain, - className, -}) => { - return ( - - - - - - -
    - {chainOptions.map((chain) => { - return ( -
  • - } - onSelect={() => onSelectChain(chain)} - > - {chain.name} - -
  • - ); - })} -
-
-
-
- ); -}; - -export default ChainSelector; diff --git a/apps/tangle-dapp/app/bridge/ChainSelectors.tsx b/apps/tangle-dapp/app/bridge/ChainSelectors.tsx new file mode 100644 index 0000000000..268e50dbbf --- /dev/null +++ b/apps/tangle-dapp/app/bridge/ChainSelectors.tsx @@ -0,0 +1,123 @@ +'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 ChainButton from '@webb-tools/webb-ui-components/components/buttons/ChainButton'; +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 { + title: string; + selectedChain: ChainConfig | null; + chainOptions: ChainConfig[]; + onSelectChain: (chain: ChainConfig) => void; + className?: string; +} + +const ChainSelectors: FC = () => { + const { + sourceChain, + setSourceChain, + destinationChain, + setDestinationChain, + supportedSourceChains, + supportedDestinationChains, + } = useBridge(); + + const onSetSourceChain = useCallback( + (chain: ChainConfig) => { + setSourceChain(chain); + // If the source chain is the same as the destination chain, clear the destination chain. + if (chain.id === destinationChain?.id) { + setDestinationChain(null); + } + }, + [destinationChain?.id, setDestinationChain, setSourceChain] + ); + + const onSetDestinationChain = useCallback( + (chain: ChainConfig) => { + setDestinationChain(chain); + // If the destination chain is the same as the source chain, clear the source chain. + if (chain.id === sourceChain?.id) { + setSourceChain(null); + } + }, + [setDestinationChain, setSourceChain, sourceChain?.id] + ); + + return ( +
+ + + + + +
+ ); +}; + +const ChainSelector: FC = ({ + title, + selectedChain, + chainOptions, + onSelectChain, + className, +}) => { + return ( + + + + + + +
    + {chainOptions.map((chain) => { + return ( +
  • + } + onSelect={() => onSelectChain(chain)} + > + {chain.name} + +
  • + ); + })} +
+
+
+
+ ); +}; + +export default ChainSelectors; diff --git a/apps/tangle-dapp/context/BridgeContext.tsx b/apps/tangle-dapp/context/BridgeContext.tsx index ef5f82916b..1d1ff0dd22 100644 --- a/apps/tangle-dapp/context/BridgeContext.tsx +++ b/apps/tangle-dapp/context/BridgeContext.tsx @@ -11,11 +11,15 @@ import { useState, } from 'react'; +import { BRIDGE_SUPPORTED_CHAINS } from '../constants/bridge'; + interface BridgeContextProps { sourceChain: ChainConfig | null; - setSourceChain: (chain: ChainConfig) => void; + setSourceChain: (chain: ChainConfig | null) => void; + supportedSourceChains: ChainConfig[]; destinationChain: ChainConfig | null; - setDestinationChain: (chain: ChainConfig) => void; + setDestinationChain: (chain: ChainConfig | null) => void; + supportedDestinationChains: ChainConfig[]; destinationAddress: string; setDestinationAddress: (address: string) => void; amount: BN | null; @@ -32,10 +36,12 @@ const BridgeContext = createContext({ setDestinationChain: () => { // }, + supportedSourceChains: [], destinationAddress: '', setDestinationAddress: () => { // }, + supportedDestinationChains: [], amount: null, setAmount: () => { // @@ -64,8 +70,10 @@ const BridgeProvider: FC = ({ children }) => { setSourceChain, destinationChain, setDestinationChain, + supportedSourceChains: BRIDGE_SUPPORTED_CHAINS, destinationAddress, setDestinationAddress, + supportedDestinationChains: BRIDGE_SUPPORTED_CHAINS, amount, setAmount, isLoading, diff --git a/libs/webb-ui-components/src/components/buttons/ChainButton.tsx b/libs/webb-ui-components/src/components/buttons/ChainButton.tsx index 8cd7a1572e..4f2d93d6af 100644 --- a/libs/webb-ui-components/src/components/buttons/ChainButton.tsx +++ b/libs/webb-ui-components/src/components/buttons/ChainButton.tsx @@ -11,7 +11,7 @@ const ChainButton = forwardRef( className, chain, status, - textClassname, + textClassName, disabled, placeholder = 'Select Chain', ...props @@ -19,8 +19,8 @@ const ChainButton = forwardRef( ref ) => { const textClsx = useMemo(() => { - return twMerge('font-bold', textClassname); - }, [textClassname]); + return twMerge('font-bold', textClassName); + }, [textClassName]); return (
diff --git a/apps/tangle-dapp/app/bridge/useActionButton.tsx b/apps/tangle-dapp/app/bridge/useActionButton.tsx new file mode 100644 index 0000000000..a912d1eeca --- /dev/null +++ b/apps/tangle-dapp/app/bridge/useActionButton.tsx @@ -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' : 'Transfer', + }; +} diff --git a/apps/tangle-dapp/context/BridgeContext.tsx b/apps/tangle-dapp/context/BridgeContext.tsx index 1d1ff0dd22..6b5711eb0c 100644 --- a/apps/tangle-dapp/context/BridgeContext.tsx +++ b/apps/tangle-dapp/context/BridgeContext.tsx @@ -1,7 +1,6 @@ 'use client'; import { BN } from '@polkadot/util'; -import { useWebContext } from '@webb-tools/api-provider-environment'; import { ChainConfig } from '@webb-tools/dapp-config/chains/chain-config.interface'; import { createContext, @@ -24,7 +23,6 @@ interface BridgeContextProps { setDestinationAddress: (address: string) => void; amount: BN | null; setAmount: (amount: BN | null) => void; - isLoading: boolean; } const BridgeContext = createContext({ @@ -46,7 +44,6 @@ const BridgeContext = createContext({ setAmount: () => { // }, - isLoading: false, }); export const useBridge = () => { @@ -54,13 +51,11 @@ export const useBridge = () => { }; const BridgeProvider: FC = ({ children }) => { - const { loading: isLoading } = useWebContext(); - const [sourceChain, setSourceChain] = useState(null); const [destinationChain, setDestinationChain] = useState( null ); - const [destinationAddress, setDestinationAddress] = useState(''); + const [destinationAddress, setDestinationAddress] = useState(''); const [amount, setAmount] = useState(null); return ( @@ -76,7 +71,6 @@ const BridgeProvider: FC = ({ children }) => { supportedDestinationChains: BRIDGE_SUPPORTED_CHAINS, amount, setAmount, - isLoading, }} > {children} From f76852f4c2e2e8488cffbff5af16c9b61084cb4c Mon Sep 17 00:00:00 2001 From: vutuanlinh2k2 Date: Mon, 13 May 2024 21:12:47 +0700 Subject: [PATCH 04/13] chore: switch chain button --- .../app/bridge/BridgeContainer.tsx | 1 + .../tangle-dapp/app/bridge/ChainSelectors.tsx | 36 ++++++++++++++++++- apps/tangle-dapp/context/BridgeContext.tsx | 2 +- 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/apps/tangle-dapp/app/bridge/BridgeContainer.tsx b/apps/tangle-dapp/app/bridge/BridgeContainer.tsx index 52a7fc70dc..78a7ecc5ff 100644 --- a/apps/tangle-dapp/app/bridge/BridgeContainer.tsx +++ b/apps/tangle-dapp/app/bridge/BridgeContainer.tsx @@ -63,6 +63,7 @@ const BridgeContainer: FC = ({ className }) => { isDisabled={isLoading} isLoading={isLoading} onClick={buttonAction} + loadingText="Connecting..." > {buttonText} diff --git a/apps/tangle-dapp/app/bridge/ChainSelectors.tsx b/apps/tangle-dapp/app/bridge/ChainSelectors.tsx index 268e50dbbf..c4878a1f8c 100644 --- a/apps/tangle-dapp/app/bridge/ChainSelectors.tsx +++ b/apps/tangle-dapp/app/bridge/ChainSelectors.tsx @@ -55,6 +55,35 @@ const ChainSelectors: FC = () => { [setDestinationChain, setSourceChain, sourceChain?.id] ); + const switchChains = useCallback(() => { + const temp = sourceChain; + + if ( + temp === null || + supportedDestinationChains.find((chain) => chain.id === temp.id) + ) { + setDestinationChain(temp); + } else { + setDestinationChain(null); + } + + if ( + destinationChain === null || + supportedSourceChains.find((chain) => chain.id === destinationChain.id) + ) { + setSourceChain(destinationChain); + } else { + setSourceChain(null); + } + }, [ + setSourceChain, + setDestinationChain, + destinationChain, + sourceChain, + supportedDestinationChains, + supportedSourceChains, + ]); + return (
{ className="flex-1 w-full md:w-auto" /> - +
+ +
= ({ children }) => { supportedSourceChains: BRIDGE_SUPPORTED_CHAINS, destinationAddress, setDestinationAddress, - supportedDestinationChains: BRIDGE_SUPPORTED_CHAINS, + supportedDestinationChains: [BRIDGE_SUPPORTED_CHAINS[0]], amount, setAmount, }} From 21aed3487330f130da5237f89af53569caaf3774 Mon Sep 17 00:00:00 2001 From: vutuanlinh2k2 Date: Mon, 13 May 2024 21:17:01 +0700 Subject: [PATCH 05/13] chore: add comments --- apps/tangle-dapp/context/BridgeContext.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/tangle-dapp/context/BridgeContext.tsx b/apps/tangle-dapp/context/BridgeContext.tsx index 54df9c6458..8bca09ed7f 100644 --- a/apps/tangle-dapp/context/BridgeContext.tsx +++ b/apps/tangle-dapp/context/BridgeContext.tsx @@ -65,10 +65,12 @@ const BridgeProvider: FC = ({ children }) => { setSourceChain, destinationChain, setDestinationChain, + // TODO: add logic to get supported chains for the bridge supportedSourceChains: BRIDGE_SUPPORTED_CHAINS, destinationAddress, setDestinationAddress, - supportedDestinationChains: [BRIDGE_SUPPORTED_CHAINS[0]], + // TODO: add logic to get supported chains for the bridge + supportedDestinationChains: BRIDGE_SUPPORTED_CHAINS, amount, setAmount, }} From 43cf7a6fcb05ece9c46601da1a89d9d9e5a6d9bc Mon Sep 17 00:00:00 2001 From: vutuanlinh2k2 Date: Mon, 13 May 2024 21:22:51 +0700 Subject: [PATCH 06/13] chore: add more comments --- apps/tangle-dapp/app/bridge/BridgeContainer.tsx | 1 - apps/tangle-dapp/app/bridge/ChainSelectors.tsx | 4 ++++ apps/tangle-dapp/constants/bridge.ts | 1 + apps/tangle-dapp/context/BridgeContext.tsx | 8 ++++---- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/apps/tangle-dapp/app/bridge/BridgeContainer.tsx b/apps/tangle-dapp/app/bridge/BridgeContainer.tsx index 78a7ecc5ff..053e718fae 100644 --- a/apps/tangle-dapp/app/bridge/BridgeContainer.tsx +++ b/apps/tangle-dapp/app/bridge/BridgeContainer.tsx @@ -33,7 +33,6 @@ const BridgeContainer: FC = ({ className }) => { >
- {/* Chain Dropdowns */} { const switchChains = useCallback(() => { const temp = sourceChain; + // If the destination chain is null or is in the supported destination chains, + // set it as the source chain. if ( temp === null || supportedDestinationChains.find((chain) => chain.id === temp.id) @@ -67,6 +69,8 @@ const ChainSelectors: FC = () => { setDestinationChain(null); } + // If the source chain is null or is in the supported source chains, + // set it as the destination chain. if ( destinationChain === null || supportedSourceChains.find((chain) => chain.id === destinationChain.id) diff --git a/apps/tangle-dapp/constants/bridge.ts b/apps/tangle-dapp/constants/bridge.ts index bb0b057daa..e5f7772187 100644 --- a/apps/tangle-dapp/constants/bridge.ts +++ b/apps/tangle-dapp/constants/bridge.ts @@ -2,6 +2,7 @@ import { chainsConfig } from '@webb-tools/dapp-config'; import { ChainConfig } from '@webb-tools/dapp-config/chains/chain-config.interface'; import { PresetTypedChainId } from '@webb-tools/dapp-types'; +// This is just a temporary variable to use as supported source and destination chains export const BRIDGE_SUPPORTED_CHAINS: ChainConfig[] = [ chainsConfig[PresetTypedChainId.TangleMainnetNative], chainsConfig[PresetTypedChainId.TangleTestnetNative], diff --git a/apps/tangle-dapp/context/BridgeContext.tsx b/apps/tangle-dapp/context/BridgeContext.tsx index 8bca09ed7f..59331390e1 100644 --- a/apps/tangle-dapp/context/BridgeContext.tsx +++ b/apps/tangle-dapp/context/BridgeContext.tsx @@ -28,21 +28,21 @@ interface BridgeContextProps { const BridgeContext = createContext({ sourceChain: null, setSourceChain: () => { - // + return; }, destinationChain: null, setDestinationChain: () => { - // + return; }, supportedSourceChains: [], destinationAddress: '', setDestinationAddress: () => { - // + return; }, supportedDestinationChains: [], amount: null, setAmount: () => { - // + return; }, }); From f395aada7803a2cee8d26ac6b7128ba320457ed0 Mon Sep 17 00:00:00 2001 From: vutuanlinh2k2 Date: Tue, 14 May 2024 13:27:12 +0700 Subject: [PATCH 07/13] chore: update chain selector logic --- .../tangle-dapp/app/bridge/ChainSelectors.tsx | 67 +++---------------- apps/tangle-dapp/context/BridgeContext.tsx | 52 +++++++++----- 2 files changed, 45 insertions(+), 74 deletions(-) diff --git a/apps/tangle-dapp/app/bridge/ChainSelectors.tsx b/apps/tangle-dapp/app/bridge/ChainSelectors.tsx index 31d94cf648..a2ba9b757b 100644 --- a/apps/tangle-dapp/app/bridge/ChainSelectors.tsx +++ b/apps/tangle-dapp/app/bridge/ChainSelectors.tsx @@ -29,72 +29,23 @@ const ChainSelectors: FC = () => { setSourceChain, destinationChain, setDestinationChain, - supportedSourceChains, - supportedDestinationChains, + sourceChainOptions, + destinationChainOptions, } = useBridge(); - const onSetSourceChain = useCallback( - (chain: ChainConfig) => { - setSourceChain(chain); - // If the source chain is the same as the destination chain, clear the destination chain. - if (chain.id === destinationChain?.id) { - setDestinationChain(null); - } - }, - [destinationChain?.id, setDestinationChain, setSourceChain] - ); - - const onSetDestinationChain = useCallback( - (chain: ChainConfig) => { - setDestinationChain(chain); - // If the destination chain is the same as the source chain, clear the source chain. - if (chain.id === sourceChain?.id) { - setSourceChain(null); - } - }, - [setDestinationChain, setSourceChain, sourceChain?.id] - ); - const switchChains = useCallback(() => { const temp = sourceChain; - - // If the destination chain is null or is in the supported destination chains, - // set it as the source chain. - if ( - temp === null || - supportedDestinationChains.find((chain) => chain.id === temp.id) - ) { - setDestinationChain(temp); - } else { - setDestinationChain(null); - } - - // If the source chain is null or is in the supported source chains, - // set it as the destination chain. - if ( - destinationChain === null || - supportedSourceChains.find((chain) => chain.id === destinationChain.id) - ) { - setSourceChain(destinationChain); - } else { - setSourceChain(null); - } - }, [ - setSourceChain, - setDestinationChain, - destinationChain, - sourceChain, - supportedDestinationChains, - supportedSourceChains, - ]); + setDestinationChain(temp); + setSourceChain(destinationChain); + }, [setSourceChain, setDestinationChain, destinationChain, sourceChain]); return (
@@ -108,8 +59,8 @@ const ChainSelectors: FC = () => {
diff --git a/apps/tangle-dapp/context/BridgeContext.tsx b/apps/tangle-dapp/context/BridgeContext.tsx index 59331390e1..853320b073 100644 --- a/apps/tangle-dapp/context/BridgeContext.tsx +++ b/apps/tangle-dapp/context/BridgeContext.tsx @@ -7,18 +7,20 @@ import { FC, PropsWithChildren, useContext, + useEffect, + useMemo, useState, } from 'react'; import { BRIDGE_SUPPORTED_CHAINS } from '../constants/bridge'; interface BridgeContextProps { - sourceChain: ChainConfig | null; - setSourceChain: (chain: ChainConfig | null) => void; - supportedSourceChains: ChainConfig[]; - destinationChain: ChainConfig | null; - setDestinationChain: (chain: ChainConfig | null) => void; - supportedDestinationChains: ChainConfig[]; + sourceChain: ChainConfig; + setSourceChain: (chain: ChainConfig) => void; + sourceChainOptions: ChainConfig[]; + destinationChain: ChainConfig; + setDestinationChain: (chain: ChainConfig) => void; + destinationChainOptions: ChainConfig[]; destinationAddress: string; setDestinationAddress: (address: string) => void; amount: BN | null; @@ -26,20 +28,20 @@ interface BridgeContextProps { } const BridgeContext = createContext({ - sourceChain: null, + sourceChain: BRIDGE_SUPPORTED_CHAINS[0], setSourceChain: () => { return; }, - destinationChain: null, + destinationChain: BRIDGE_SUPPORTED_CHAINS[1], setDestinationChain: () => { return; }, - supportedSourceChains: [], + sourceChainOptions: [], destinationAddress: '', setDestinationAddress: () => { return; }, - supportedDestinationChains: [], + destinationChainOptions: [], amount: null, setAmount: () => { return; @@ -51,26 +53,44 @@ export const useBridge = () => { }; const BridgeProvider: FC = ({ children }) => { - const [sourceChain, setSourceChain] = useState(null); - const [destinationChain, setDestinationChain] = useState( - null + const [sourceChain, setSourceChain] = useState( + BRIDGE_SUPPORTED_CHAINS[0] + ); + const [destinationChain, setDestinationChain] = useState( + BRIDGE_SUPPORTED_CHAINS[1] ); const [destinationAddress, setDestinationAddress] = useState(''); const [amount, setAmount] = useState(null); + const destinationChainOptions = useMemo( + () => + BRIDGE_SUPPORTED_CHAINS.filter((chain) => chain.id !== sourceChain.id), + [sourceChain] + ); + + useEffect(() => { + // If current destination chain is not in the destination chain options, + // set the first option as the destination chain. + if ( + !destinationChainOptions.find((chain) => chain.id === destinationChain.id) + ) { + setDestinationChain(destinationChainOptions[0]); + } + }, [destinationChainOptions, destinationChain.id]); + return ( Date: Tue, 14 May 2024 17:13:12 +0700 Subject: [PATCH 08/13] feat: add token selection dropdown --- .../components/Header/ActiveChainDropdown.tsx | 13 +-- .../src/pages/Account/AccountSummaryCard.tsx | 6 +- .../app/bridge/AmountAndTokenInput.tsx | 66 ++++++++++++++ .../app/bridge/BridgeContainer.tsx | 16 +--- .../tangle-dapp/app/bridge/ChainSelectors.tsx | 47 +++++----- .../app/bridge/useActionButton.tsx | 2 +- .../components/AmountInput/AmountInput.tsx | 9 ++ apps/tangle-dapp/constants/bridge.ts | 10 +++ apps/tangle-dapp/context/BridgeContext.tsx | 90 +++++++++++++------ apps/tangle-dapp/types/index.ts | 6 ++ .../{ChainButton.tsx => DropdownButton.tsx} | 33 ++++--- .../src/components/buttons/index.ts | 2 +- .../src/components/buttons/types.ts | 27 +++--- .../stories/molecules/ChainButton.stories.tsx | 30 ------- .../molecules/DropdownButton.stories.tsx | 38 ++++++++ 15 files changed, 265 insertions(+), 130 deletions(-) create mode 100644 apps/tangle-dapp/app/bridge/AmountAndTokenInput.tsx rename libs/webb-ui-components/src/components/buttons/{ChainButton.tsx => DropdownButton.tsx} (62%) delete mode 100644 libs/webb-ui-components/src/stories/molecules/ChainButton.stories.tsx create mode 100644 libs/webb-ui-components/src/stories/molecules/DropdownButton.stories.tsx diff --git a/apps/bridge-dapp/src/components/Header/ActiveChainDropdown.tsx b/apps/bridge-dapp/src/components/Header/ActiveChainDropdown.tsx index 4d88f61eba..d377b373fd 100644 --- a/apps/bridge-dapp/src/components/Header/ActiveChainDropdown.tsx +++ b/apps/bridge-dapp/src/components/Header/ActiveChainDropdown.tsx @@ -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, @@ -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 DropdownButton from '@webb-tools/webb-ui-components/components/buttons/DropdownButton'; import { useWebbUI } from '@webb-tools/webb-ui-components/hooks/useWebbUI'; import { useCallback, useMemo } from 'react'; import useChainsFromRoute from '../../hooks/useChainsFromRoute'; @@ -64,14 +64,15 @@ const ActiveChainDropdown = () => { return ( - - + - +
    diff --git a/apps/bridge-dapp/src/pages/Account/AccountSummaryCard.tsx b/apps/bridge-dapp/src/pages/Account/AccountSummaryCard.tsx index bb1b5cb1c7..c4bef04f1c 100644 --- a/apps/bridge-dapp/src/pages/Account/AccountSummaryCard.tsx +++ b/apps/bridge-dapp/src/pages/Account/AccountSummaryCard.tsx @@ -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'; @@ -174,7 +174,7 @@ function TotalShieldedBalance() { - - + diff --git a/apps/tangle-dapp/app/bridge/AmountAndTokenInput.tsx b/apps/tangle-dapp/app/bridge/AmountAndTokenInput.tsx new file mode 100644 index 0000000000..e8b0e3f36e --- /dev/null +++ b/apps/tangle-dapp/app/bridge/AmountAndTokenInput.tsx @@ -0,0 +1,66 @@ +'use client'; + +import { DropdownMenuTrigger as DropdownTrigger } from '@radix-ui/react-dropdown-menu'; +import { TokenIcon } from '@webb-tools/icons/TokenIcon'; +import DropdownButton from '@webb-tools/webb-ui-components/components/buttons/DropdownButton'; +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 ( +
    + + + + + + + +
      + {tokenOptions.map((token) => { + return ( +
    • + } + onSelect={() => setSelectedToken(token)} + > + {token.symbol} + +
    • + ); + })} +
    +
    +
    +
    +
    + ); +}; + +export default AmountAndTokenInput; diff --git a/apps/tangle-dapp/app/bridge/BridgeContainer.tsx b/apps/tangle-dapp/app/bridge/BridgeContainer.tsx index 053e718fae..249867b4f8 100644 --- a/apps/tangle-dapp/app/bridge/BridgeContainer.tsx +++ b/apps/tangle-dapp/app/bridge/BridgeContainer.tsx @@ -7,8 +7,8 @@ import { twMerge } from 'tailwind-merge'; import AddressInput, { AddressType, } from '../../components/AddressInput/AddressInput'; -import AmountInput from '../../components/AmountInput/AmountInput'; import { useBridge } from '../../context/BridgeContext'; +import AmountAndTokenInput from './AmountAndTokenInput'; import ChainSelectors from './ChainSelectors'; import useActionButton from './useActionButton'; @@ -17,8 +17,7 @@ interface BridgeContainerProps { } const BridgeContainer: FC = ({ className }) => { - const { destinationAddress, setDestinationAddress, amount, setAmount } = - useBridge(); + const { destinationAddress, setDestinationAddress } = useBridge(); const { buttonAction, buttonText, isLoading } = useActionButton(); return ( @@ -35,16 +34,7 @@ const BridgeContainer: FC = ({ className }) => {
    - + void; className?: string; @@ -25,27 +24,31 @@ interface ChainSelectorProps { const ChainSelectors: FC = () => { const { - sourceChain, - setSourceChain, - destinationChain, - setDestinationChain, + selectedSourceChain, + setSelectedSourceChain, + selectedDestinationChain, + setSelectedDestinationChain, sourceChainOptions, destinationChainOptions, } = useBridge(); const switchChains = useCallback(() => { - const temp = sourceChain; - setDestinationChain(temp); - setSourceChain(destinationChain); - }, [setSourceChain, setDestinationChain, destinationChain, sourceChain]); + const temp = selectedSourceChain; + setSelectedDestinationChain(temp); + setSelectedSourceChain(selectedDestinationChain); + }, [ + setSelectedSourceChain, + setSelectedDestinationChain, + selectedDestinationChain, + selectedSourceChain, + ]); return (
    @@ -57,10 +60,9 @@ const ChainSelectors: FC = () => {
    @@ -68,7 +70,6 @@ const ChainSelectors: FC = () => { }; const ChainSelector: FC = ({ - title, selectedChain, chainOptions, onSelectChain, @@ -77,14 +78,10 @@ const ChainSelector: FC = ({ return ( - diff --git a/apps/tangle-dapp/app/bridge/useActionButton.tsx b/apps/tangle-dapp/app/bridge/useActionButton.tsx index a912d1eeca..fa9f820664 100644 --- a/apps/tangle-dapp/app/bridge/useActionButton.tsx +++ b/apps/tangle-dapp/app/bridge/useActionButton.tsx @@ -27,6 +27,6 @@ export default function useActionButton() { return { isLoading: loading || isConnecting, buttonAction: noActiveAccountOrWallet ? openWalletModal : bridgeTx, - buttonText: noActiveAccountOrWallet ? 'Connect' : 'Transfer', + buttonText: noActiveAccountOrWallet ? 'Connect' : 'Approve', }; } diff --git a/apps/tangle-dapp/components/AmountInput/AmountInput.tsx b/apps/tangle-dapp/components/AmountInput/AmountInput.tsx index 3c52043ffc..923cc7c366 100644 --- a/apps/tangle-dapp/components/AmountInput/AmountInput.tsx +++ b/apps/tangle-dapp/components/AmountInput/AmountInput.tsx @@ -21,6 +21,9 @@ export type AmountInputProps = { setAmount: (newAmount: BN | null) => void; setErrorMessage?: (error: string | null) => void; placeholder?: string; + wrapperClassName?: string; + bodyClassName?: string; + dropdownBodyClassName?: string; }; const AmountInput: FC = ({ @@ -38,6 +41,9 @@ const AmountInput: FC = ({ errorOnEmptyValue = false, setErrorMessage, placeholder, + wrapperClassName, + bodyClassName, + dropdownBodyClassName, }) => { const inputRef = useRef(null); const { nativeTokenSymbol } = useNetworkStore(); @@ -95,6 +101,9 @@ const AmountInput: FC = ({ isDisabled={isDisabled} {...baseInputOverrides} actions={actions} + wrapperClassName={wrapperClassName} + bodyClassName={bodyClassName} + dropdownBodyClassName={dropdownBodyClassName} > void; + selectedSourceChain: ChainConfig; + setSelectedSourceChain: (chain: ChainConfig) => void; sourceChainOptions: ChainConfig[]; - destinationChain: ChainConfig; - setDestinationChain: (chain: ChainConfig) => void; + + selectedDestinationChain: ChainConfig; + setSelectedDestinationChain: (chain: ChainConfig) => void; destinationChainOptions: ChainConfig[]; + destinationAddress: string; setDestinationAddress: (address: string) => void; + amount: BN | null; setAmount: (amount: BN | null) => void; + + selectedToken: BridgeTokenType; + setSelectedToken: (token: BridgeTokenType) => void; + tokenOptions: BridgeTokenType[]; } const BridgeContext = createContext({ - sourceChain: BRIDGE_SUPPORTED_CHAINS[0], - setSourceChain: () => { + selectedSourceChain: BRIDGE_SUPPORTED_CHAINS[0], + setSelectedSourceChain: () => { return; }, - destinationChain: BRIDGE_SUPPORTED_CHAINS[1], - setDestinationChain: () => { + sourceChainOptions: BRIDGE_SUPPORTED_CHAINS, + + selectedDestinationChain: BRIDGE_SUPPORTED_CHAINS[1], + setSelectedDestinationChain: () => { return; }, - sourceChainOptions: [], + destinationChainOptions: [BRIDGE_SUPPORTED_CHAINS[1]], + destinationAddress: '', setDestinationAddress: () => { return; }, - destinationChainOptions: [], + amount: null, setAmount: () => { return; }, + + selectedToken: BRIDGE_SUPPORTED_TOKENS[0], + setSelectedToken: () => { + return; + }, + tokenOptions: [], }); export const useBridge = () => { @@ -53,46 +73,60 @@ export const useBridge = () => { }; const BridgeProvider: FC = ({ children }) => { - const [sourceChain, setSourceChain] = useState( + const [selectedSourceChain, setSelectedSourceChain] = useState( BRIDGE_SUPPORTED_CHAINS[0] ); - const [destinationChain, setDestinationChain] = useState( - BRIDGE_SUPPORTED_CHAINS[1] - ); + const [selectedDestinationChain, setSelectedDestinationChain] = + useState(BRIDGE_SUPPORTED_CHAINS[1]); + const [destinationAddress, setDestinationAddress] = useState(''); const [amount, setAmount] = useState(null); + const [selectedToken, setSelectedToken] = useState( + BRIDGE_SUPPORTED_TOKENS[0] + ); - const destinationChainOptions = useMemo( + const selectedDestinationChainOptions = useMemo( () => - BRIDGE_SUPPORTED_CHAINS.filter((chain) => chain.id !== sourceChain.id), - [sourceChain] + BRIDGE_SUPPORTED_CHAINS.filter( + (chain) => chain.id !== selectedSourceChain.id + ), + [selectedSourceChain] ); useEffect(() => { // If current destination chain is not in the destination chain options, // set the first option as the destination chain. if ( - !destinationChainOptions.find((chain) => chain.id === destinationChain.id) + !selectedDestinationChainOptions.find( + (chain) => chain.id === selectedDestinationChain.id + ) ) { - setDestinationChain(destinationChainOptions[0]); + setSelectedDestinationChain(selectedDestinationChainOptions[0]); } - }, [destinationChainOptions, destinationChain.id]); + }, [selectedDestinationChainOptions, selectedDestinationChain.id]); return ( {children} diff --git a/apps/tangle-dapp/types/index.ts b/apps/tangle-dapp/types/index.ts index a61d203cb7..0f4dca83ca 100644 --- a/apps/tangle-dapp/types/index.ts +++ b/apps/tangle-dapp/types/index.ts @@ -227,3 +227,9 @@ export type ExposureMap = Record< exposureMeta: SpStakingPagedExposureMetadata; } >; + +// TODO: might need to add more metadata here: name, decimals, etc. +export type BridgeTokenType = { + id: string; + symbol: string; +}; diff --git a/libs/webb-ui-components/src/components/buttons/ChainButton.tsx b/libs/webb-ui-components/src/components/buttons/DropdownButton.tsx similarity index 62% rename from libs/webb-ui-components/src/components/buttons/ChainButton.tsx rename to libs/webb-ui-components/src/components/buttons/DropdownButton.tsx index 4f2d93d6af..7e6e2f128f 100644 --- a/libs/webb-ui-components/src/components/buttons/ChainButton.tsx +++ b/libs/webb-ui-components/src/components/buttons/DropdownButton.tsx @@ -1,26 +1,35 @@ -import { ChainIcon, ChevronDown } from '@webb-tools/icons'; +import { ChainIcon, ChevronDown, TokenIcon } from '@webb-tools/icons'; import { getFlexBasic } from '@webb-tools/icons/utils'; import cx from 'classnames'; import { forwardRef, useMemo } from 'react'; import { twMerge } from 'tailwind-merge'; -import { ChainButtonProps } from './types'; +import { DropdownButtonProps } from './types'; -const ChainButton = forwardRef( +const DropdownButton = forwardRef( ( { className, - chain, + value, status, textClassName, disabled, placeholder = 'Select Chain', + iconType, ...props }, ref ) => { const textClsx = useMemo(() => { - return twMerge('font-bold', textClassName); - }, [textClassName]); + return twMerge( + 'font-bold', + iconType === 'token' ? 'uppercase' : '', + textClassName + ); + }, [iconType, textClassName]); + + const IconCmp = useMemo(() => { + return iconType === 'chain' ? ChainIcon : TokenIcon; + }, [iconType]); return (