import { useCallback, useEffect, useMemo, useState } from 'react'; import CopyToClipboard from 'react-copy-to-clipboard'; import classNames from 'classnames'; import { t, truncateByChars } from '@vegaprotocol/react-helpers'; import { Button, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuItemIndicator, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuTrigger, Icon, Drawer, DropdownMenuSeparator, } from '@vegaprotocol/ui-toolkit'; import type { PubKey } from '@vegaprotocol/wallet'; import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet'; import { Networks, useEnvironment } from '@vegaprotocol/environment'; import { WalletIcon } from '../icons/wallet'; import { useTransferDialog } from '@vegaprotocol/accounts'; const MobileWalletButton = ({ isConnected, activeKey, }: { isConnected?: boolean; activeKey?: PubKey; }) => { const { pubKeys, selectPubKey, disconnect } = useVegaWallet(); const openVegaWalletDialog = useVegaWalletDialogStore( (store) => store.openVegaWalletDialog ); const openTransferDialog = useTransferDialog((store) => store.open); const { VEGA_ENV } = useEnvironment(); const isYellow = VEGA_ENV === Networks.TESTNET; const [drawerOpen, setDrawerOpen] = useState(false); const mobileDisconnect = useCallback(() => { setDrawerOpen(false); disconnect(); }, [disconnect]); const openDrawer = useCallback(() => { if (!isConnected) { openVegaWalletDialog(); setDrawerOpen(false); } else { setDrawerOpen(!drawerOpen); } }, [drawerOpen, isConnected, openVegaWalletDialog]); const iconClass = drawerOpen ? 'hidden' : isYellow ? 'fill-black' : 'fill-white'; const [container, setContainer] = useState<HTMLElement | null>(null); const walletButton = ( <button className="my-2 transition-all flex flex-col justify-around gap-3 p-2 relative h-[34px]" onClick={openDrawer} data-testid="connect-vega-wallet-mobile" > <WalletIcon className={iconClass} /> </button> ); const onSelectItem = useCallback( (pubkey: string) => { setDrawerOpen(false); selectPubKey(pubkey); }, [selectPubKey] ); return ( <div className="lg:hidden overflow-hidden flex" ref={setContainer}> <Drawer dataTestId="wallets-drawer" open={drawerOpen} onChange={setDrawerOpen} container={container} trigger={walletButton} > <div className="border-l border-default p-2 gap-4 flex flex-col w-full h-full bg-white dark:bg-black dark:text-white justify-between"> <div className="flex h-5 justify-end"> <button className="transition-all flex flex-col justify-around gap-3 p-2 relative h-[34px]" onClick={() => setDrawerOpen(false)} data-testid="connect-vega-wallet-mobile-close" > <> <div className={classNames( 'w-[26px] h-[2px] bg-black dark:bg-white transition-all translate-y-[7.5px] rotate-45', { hidden: !drawerOpen, } )} /> <div className={classNames( 'w-[26px] h-[2px] bg-black dark:bg-white transition-all -translate-y-[7.5px] -rotate-45', { hidden: !drawerOpen, } )} /> </> </button> </div> <div className="grow my-4" role="list"> {(pubKeys || []).map((pk) => ( <KeypairListItem key={pk.publicKey} pk={pk} isActive={activeKey?.publicKey === pk.publicKey} onSelectItem={onSelectItem} /> ))} </div> <div className="flex flex-col gap-2 m-4"> <Button onClick={() => { setDrawerOpen(false); openTransferDialog(true); }} fill > {t('Transfer')} </Button> <Button onClick={mobileDisconnect} fill> {t('Disconnect')} </Button> </div> </div> </Drawer> </div> ); }; export const VegaWalletConnectButton = () => { const [dropdownOpen, setDropdownOpen] = useState(false); const openVegaWalletDialog = useVegaWalletDialogStore( (store) => store.openVegaWalletDialog ); const openTransferDialog = useTransferDialog((store) => store.open); const { pubKey, pubKeys, selectPubKey, disconnect } = useVegaWallet(); const isConnected = pubKey !== null; const activeKey = useMemo(() => { return pubKeys?.find((pk) => pk.publicKey === pubKey); }, [pubKey, pubKeys]); if (isConnected && pubKeys) { return ( <> <div className="hidden lg:block"> <DropdownMenu open={dropdownOpen} trigger={ <DropdownMenuTrigger data-testid="manage-vega-wallet" onClick={() => setDropdownOpen((curr) => !curr)} > {activeKey && ( <span className="uppercase">{activeKey.name}</span> )} {': '} {truncateByChars(pubKey)} </DropdownMenuTrigger> } > <DropdownMenuContent onInteractOutside={() => setDropdownOpen(false)} > <div className="min-w-[340px]" data-testid="keypair-list"> <DropdownMenuRadioGroup value={pubKey} onValueChange={(value) => { selectPubKey(value); }} > {pubKeys.map((pk) => ( <KeypairItem key={pk.publicKey} pk={pk} /> ))} </DropdownMenuRadioGroup> <DropdownMenuSeparator /> <DropdownMenuItem data-testid="wallet-transfer" onClick={() => openTransferDialog(true)} > {t('Transfer')} </DropdownMenuItem> <DropdownMenuItem data-testid="disconnect" onClick={disconnect}> {t('Disconnect')} </DropdownMenuItem> </div> </DropdownMenuContent> </DropdownMenu> </div> <MobileWalletButton isConnected activeKey={activeKey} /> </> ); } return ( <> <Button data-testid="connect-vega-wallet" onClick={openVegaWalletDialog} size="sm" className="hidden lg:block" > <span className="whitespace-nowrap">{t('Connect Vega wallet')}</span> </Button> <MobileWalletButton /> </> ); }; const KeypairItem = ({ pk }: { pk: PubKey }) => { const [copied, setCopied] = useState(false); useEffect(() => { // eslint-disable-next-line let timeout: any; if (copied) { timeout = setTimeout(() => { setCopied(false); }, 800); } return () => { clearTimeout(timeout); }; }, [copied]); return ( <DropdownMenuRadioItem value={pk.publicKey}> <div className="flex-1 mr-2" data-testid={`key-${pk.publicKey}`}> <span className="mr-2"> <span> <span className="uppercase">{pk.name}</span>:{' '} {truncateByChars(pk.publicKey)} </span> </span> <span> <CopyToClipboard text={pk.publicKey} onCopy={() => setCopied(true)}> <button data-testid="copy-vega-public-key" onClick={(e) => e.stopPropagation()} > <span className="sr-only">{t('Copy')}</span> <Icon name="duplicate" className="mr-2" /> </button> </CopyToClipboard> {copied && ( <span className="text-xs text-neutral-500">{t('Copied')}</span> )} </span> </div> <DropdownMenuItemIndicator /> </DropdownMenuRadioItem> ); }; const KeypairListItem = ({ pk, isActive, onSelectItem, }: { pk: PubKey; isActive: boolean; onSelectItem: (pk: string) => void; }) => { const [copied, setCopied] = useState(false); useEffect(() => { // eslint-disable-next-line let timeout: any; if (copied) { timeout = setTimeout(() => { setCopied(false); }, 800); } return () => { clearTimeout(timeout); }; }, [copied]); return ( <div className="flex flex-col w-full ml-4 mr-2 mb-4" data-testid={`key-${pk.publicKey}-mobile`} > <span className="mr-2"> <button onClick={() => onSelectItem(pk.publicKey)}> <span className="uppercase">{pk.name}</span> </button> {isActive && <Icon name="tick" className="ml-2" />} </span> <span className="text-neutral-500 dark:text-neutral-400"> {truncateByChars(pk.publicKey)}{' '} <CopyToClipboard text={pk.publicKey} onCopy={() => setCopied(true)}> <button data-testid="copy-vega-public-key" onClick={(e) => e.stopPropagation()} > <span className="sr-only">{t('Copy')}</span> <Icon name="duplicate" className="mr-2" /> </button> </CopyToClipboard> {copied && ( <span className="text-xs text-neutral-500">{t('Copied')}</span> )} </span> </div> ); };