325 lines
9.6 KiB
TypeScript
325 lines
9.6 KiB
TypeScript
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
import CopyToClipboard from 'react-copy-to-clipboard';
|
|
import classNames from 'classnames';
|
|
import { truncateByChars } from '@vegaprotocol/utils';
|
|
import { t } from '@vegaprotocol/i18n';
|
|
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-black dark: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, isReadOnly } =
|
|
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 />
|
|
{!isReadOnly && (
|
|
<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>
|
|
);
|
|
};
|