feat(web3): withdrawal details (#3701)
This commit is contained in:
parent
4161a74eaf
commit
a4149cc55e
@ -16,6 +16,7 @@ import { AppStateProvider } from './contexts/app-state/app-state-provider';
|
|||||||
import { ContractsProvider } from './contexts/contracts/contracts-provider';
|
import { ContractsProvider } from './contexts/contracts/contracts-provider';
|
||||||
import { AppRouter } from './routes';
|
import { AppRouter } from './routes';
|
||||||
import type { EthereumConfig } from '@vegaprotocol/web3';
|
import type { EthereumConfig } from '@vegaprotocol/web3';
|
||||||
|
import { WithdrawalApprovalDialogContainer } from '@vegaprotocol/web3';
|
||||||
import {
|
import {
|
||||||
createConnectors,
|
createConnectors,
|
||||||
useEthTransactionManager,
|
useEthTransactionManager,
|
||||||
@ -43,7 +44,7 @@ import {
|
|||||||
} from '@vegaprotocol/environment';
|
} from '@vegaprotocol/environment';
|
||||||
import { ENV } from './config';
|
import { ENV } from './config';
|
||||||
import type { InMemoryCacheConfig } from '@apollo/client';
|
import type { InMemoryCacheConfig } from '@apollo/client';
|
||||||
import { WithdrawalDialog } from '@vegaprotocol/withdraws';
|
import { CreateWithdrawalDialog } from '@vegaprotocol/withdraws';
|
||||||
import { SplashLoader } from './components/splash-loader';
|
import { SplashLoader } from './components/splash-loader';
|
||||||
import { ToastsManager } from './toasts-manager';
|
import { ToastsManager } from './toasts-manager';
|
||||||
import {
|
import {
|
||||||
@ -158,7 +159,8 @@ const Web3Container = ({
|
|||||||
<InitializeHandlers />
|
<InitializeHandlers />
|
||||||
<VegaWalletDialogs />
|
<VegaWalletDialogs />
|
||||||
<TransactionModal />
|
<TransactionModal />
|
||||||
<WithdrawalDialog />
|
<CreateWithdrawalDialog />
|
||||||
|
<WithdrawalApprovalDialogContainer />
|
||||||
<TelemetryDialog />
|
<TelemetryDialog />
|
||||||
</>
|
</>
|
||||||
</BalanceManager>
|
</BalanceManager>
|
||||||
|
@ -6,7 +6,10 @@ import { VegaConnectDialog } from '@vegaprotocol/wallet';
|
|||||||
import { Connectors } from '../lib/vega-connectors';
|
import { Connectors } from '../lib/vega-connectors';
|
||||||
import { CreateWithdrawalDialog } from '@vegaprotocol/withdraws';
|
import { CreateWithdrawalDialog } from '@vegaprotocol/withdraws';
|
||||||
import { DepositDialog } from '@vegaprotocol/deposits';
|
import { DepositDialog } from '@vegaprotocol/deposits';
|
||||||
import { Web3ConnectUncontrolledDialog } from '@vegaprotocol/web3';
|
import {
|
||||||
|
Web3ConnectUncontrolledDialog,
|
||||||
|
WithdrawalApprovalDialogContainer,
|
||||||
|
} from '@vegaprotocol/web3';
|
||||||
import { WelcomeDialog } from '../components/welcome-dialog';
|
import { WelcomeDialog } from '../components/welcome-dialog';
|
||||||
import { TransferDialog } from '@vegaprotocol/accounts';
|
import { TransferDialog } from '@vegaprotocol/accounts';
|
||||||
|
|
||||||
@ -27,6 +30,7 @@ const DialogsContainer = () => {
|
|||||||
<Web3ConnectUncontrolledDialog />
|
<Web3ConnectUncontrolledDialog />
|
||||||
<CreateWithdrawalDialog />
|
<CreateWithdrawalDialog />
|
||||||
<TransferDialog />
|
<TransferDialog />
|
||||||
|
<WithdrawalApprovalDialogContainer />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -3,7 +3,8 @@ import styles from './toast.module.css';
|
|||||||
import type { IconName } from '@blueprintjs/icons';
|
import type { IconName } from '@blueprintjs/icons';
|
||||||
import { IconNames } from '@blueprintjs/icons';
|
import { IconNames } from '@blueprintjs/icons';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import type { HTMLAttributes, HtmlHTMLAttributes } from 'react';
|
import type { HTMLAttributes, HtmlHTMLAttributes, ReactNode } from 'react';
|
||||||
|
import { useState } from 'react';
|
||||||
import { forwardRef, useEffect } from 'react';
|
import { forwardRef, useEffect } from 'react';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { useLayoutEffect } from 'react';
|
import { useLayoutEffect } from 'react';
|
||||||
@ -11,6 +12,7 @@ import { useRef } from 'react';
|
|||||||
import { Intent } from '../../utils/intent';
|
import { Intent } from '../../utils/intent';
|
||||||
import { Icon } from '../icon';
|
import { Icon } from '../icon';
|
||||||
import { Loader } from '../loader';
|
import { Loader } from '../loader';
|
||||||
|
import { t } from '@vegaprotocol/i18n';
|
||||||
|
|
||||||
export type ToastContent = JSX.Element | undefined;
|
export type ToastContent = JSX.Element | undefined;
|
||||||
|
|
||||||
@ -63,11 +65,88 @@ export const Panel = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
type CollapsiblePanelProps = {
|
||||||
|
actions?: ReactNode;
|
||||||
|
};
|
||||||
|
export const CollapsiblePanel = forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
CollapsiblePanelProps & HTMLAttributes<HTMLDivElement>
|
||||||
|
>(({ children, className, actions, ...props }, ref) => {
|
||||||
|
const [collapsed, setCollapsed] = useState(true);
|
||||||
|
return (
|
||||||
|
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
|
||||||
|
<div
|
||||||
|
data-panel
|
||||||
|
ref={ref}
|
||||||
|
data-test
|
||||||
|
className={classNames(
|
||||||
|
'relative',
|
||||||
|
'p-2 rounded mt-[10px]',
|
||||||
|
'font-mono text-[12px] leading-[16px] font-normal',
|
||||||
|
'[&>h4]:font-bold',
|
||||||
|
'overflow-auto',
|
||||||
|
{
|
||||||
|
'h-[64px] overflow-hidden': collapsed,
|
||||||
|
'pb-4': !collapsed,
|
||||||
|
},
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
aria-expanded={!collapsed}
|
||||||
|
onDoubleClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setCollapsed(!collapsed);
|
||||||
|
}}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
{collapsed && (
|
||||||
|
<div
|
||||||
|
data-panel-curtain
|
||||||
|
className={classNames(
|
||||||
|
'bg-gradient-to-b from-transparent to-inherit',
|
||||||
|
'absolute bottom-0 left-0 h-8 w-full pointer-events-none'
|
||||||
|
)}
|
||||||
|
></div>
|
||||||
|
)}
|
||||||
|
<div
|
||||||
|
data-panel-actions
|
||||||
|
className={classNames(
|
||||||
|
'absolute bottom-0 right-0',
|
||||||
|
'p-2',
|
||||||
|
'rounded-tl',
|
||||||
|
'flex align-middle gap-1'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{actions}
|
||||||
|
<button
|
||||||
|
className="cursor-pointer"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setCollapsed(!collapsed);
|
||||||
|
}}
|
||||||
|
title={collapsed ? t('Expand') : t('Collapse')}
|
||||||
|
aria-label={collapsed ? t('Expand') : t('Collapse')}
|
||||||
|
>
|
||||||
|
{collapsed ? (
|
||||||
|
<Icon name="expand-all" size={3} />
|
||||||
|
) : (
|
||||||
|
<Icon name="collapse-all" size={3} />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
export const ToastHeading = forwardRef<
|
export const ToastHeading = forwardRef<
|
||||||
HTMLHeadingElement,
|
HTMLHeadingElement,
|
||||||
HtmlHTMLAttributes<HTMLHeadingElement>
|
HtmlHTMLAttributes<HTMLHeadingElement>
|
||||||
>(({ children, ...props }, ref) => (
|
>(({ children, className, ...props }, ref) => (
|
||||||
<h3 ref={ref} className="text-sm uppercase mb-1" {...props}>
|
<h3
|
||||||
|
ref={ref}
|
||||||
|
className={classNames('text-sm uppercase mb-1', className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</h3>
|
</h3>
|
||||||
));
|
));
|
||||||
@ -167,7 +246,7 @@ export const Toast = ({
|
|||||||
},
|
},
|
||||||
// panel's colours
|
// panel's colours
|
||||||
{
|
{
|
||||||
'[&_[data-panel]]:bg-vega-light-150 [&_[data-panel]]:dark:bg-vega-dark-150 ':
|
'[&_[data-panel]]:bg-vega-light-150 [&_[data-panel]]:dark:bg-vega-dark-150':
|
||||||
intent === Intent.None,
|
intent === Intent.None,
|
||||||
'[&_[data-panel]]:bg-vega-blue-350 [&_[data-panel]]:dark:bg-vega-blue-650':
|
'[&_[data-panel]]:bg-vega-blue-350 [&_[data-panel]]:dark:bg-vega-blue-650':
|
||||||
intent === Intent.Primary,
|
intent === Intent.Primary,
|
||||||
@ -178,6 +257,31 @@ export const Toast = ({
|
|||||||
'[&_[data-panel]]:bg-vega-pink-350 [&_[data-panel]]:dark:bg-vega-pink-650':
|
'[&_[data-panel]]:bg-vega-pink-350 [&_[data-panel]]:dark:bg-vega-pink-650':
|
||||||
intent === Intent.Danger,
|
intent === Intent.Danger,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'[&_[data-panel]]:to-vega-light-150 [&_[data-panel]]:dark:to-vega-dark-150':
|
||||||
|
intent === Intent.None,
|
||||||
|
'[&_[data-panel]]:to-vega-blue-350 [&_[data-panel]]:dark:to-vega-blue-650':
|
||||||
|
intent === Intent.Primary,
|
||||||
|
'[&_[data-panel]]:to-vega-green-350 [&_[data-panel]]:dark:to-vega-green-650':
|
||||||
|
intent === Intent.Success,
|
||||||
|
'[&_[data-panel]]:to-vega-orange-350 [&_[data-panel]]:dark:to-vega-orange-650':
|
||||||
|
intent === Intent.Warning,
|
||||||
|
'[&_[data-panel]]:to-vega-pink-350 [&_[data-panel]]:dark:to-vega-pink-650':
|
||||||
|
intent === Intent.Danger,
|
||||||
|
},
|
||||||
|
// panel's actions
|
||||||
|
{
|
||||||
|
'[&_[data-panel-actions]]:bg-vega-light-200 [&_[data-panel-actions]]:dark:bg-vega-dark-100 ':
|
||||||
|
intent === Intent.None,
|
||||||
|
'[&_[data-panel-actions]]:bg-vega-blue-400 [&_[data-panel-actions]]:dark:bg-vega-blue-600':
|
||||||
|
intent === Intent.Primary,
|
||||||
|
'[&_[data-panel-actions]]:bg-vega-green-400 [&_[data-panel-actions]]:dark:bg-vega-green-600':
|
||||||
|
intent === Intent.Success,
|
||||||
|
'[&_[data-panel-actions]]:bg-vega-orange-400 [&_[data-panel-actions]]:dark:bg-vega-orange-600':
|
||||||
|
intent === Intent.Warning,
|
||||||
|
'[&_[data-panel-actions]]:bg-vega-pink-400 [&_[data-panel-actions]]:dark:bg-vega-pink-600':
|
||||||
|
intent === Intent.Danger,
|
||||||
|
},
|
||||||
// panels's progress bar colours
|
// panels's progress bar colours
|
||||||
'[&_[data-progress-bar]]:mt-[10px] [&_[data-progress-bar]]:mb-[4px]',
|
'[&_[data-progress-bar]]:mt-[10px] [&_[data-progress-bar]]:mb-[4px]',
|
||||||
{
|
{
|
||||||
|
@ -9,20 +9,21 @@ export * from './lib/use-ethereum-config';
|
|||||||
export * from './lib/use-ethereum-read-contract';
|
export * from './lib/use-ethereum-read-contract';
|
||||||
export * from './lib/use-ethereum-transaction-manager';
|
export * from './lib/use-ethereum-transaction-manager';
|
||||||
export * from './lib/use-ethereum-transaction-store';
|
export * from './lib/use-ethereum-transaction-store';
|
||||||
|
export * from './lib/use-ethereum-transaction-toasts';
|
||||||
export * from './lib/use-ethereum-transaction-updater';
|
export * from './lib/use-ethereum-transaction-updater';
|
||||||
export * from './lib/use-ethereum-transaction';
|
export * from './lib/use-ethereum-transaction';
|
||||||
|
export * from './lib/use-ethereum-withdraw-approval-toasts';
|
||||||
export * from './lib/use-ethereum-withdraw-approvals-manager';
|
export * from './lib/use-ethereum-withdraw-approvals-manager';
|
||||||
export * from './lib/use-ethereum-withdraw-approvals-store';
|
export * from './lib/use-ethereum-withdraw-approvals-store';
|
||||||
export * from './lib/use-get-withdraw-delay';
|
export * from './lib/use-get-withdraw-delay';
|
||||||
export * from './lib/use-get-withdraw-threshold';
|
export * from './lib/use-get-withdraw-threshold';
|
||||||
export * from './lib/use-token-contract';
|
export * from './lib/use-token-contract';
|
||||||
export * from './lib/use-token-decimals';
|
export * from './lib/use-token-decimals';
|
||||||
|
export * from './lib/use-vega-transaction-toasts';
|
||||||
export * from './lib/use-web3-disconnect';
|
export * from './lib/use-web3-disconnect';
|
||||||
export * from './lib/web3-connect-dialog';
|
export * from './lib/web3-connect-dialog';
|
||||||
export * from './lib/web3-connect-store';
|
export * from './lib/web3-connect-store';
|
||||||
export * from './lib/web3-connectors';
|
export * from './lib/web3-connectors';
|
||||||
export * from './lib/web3-provider';
|
export * from './lib/web3-provider';
|
||||||
export * from './lib/use-vega-transaction-toasts';
|
export * from './lib/withdrawal-approval-dialog';
|
||||||
export * from './lib/use-ethereum-transaction-toasts';
|
|
||||||
export * from './lib/use-ethereum-withdraw-approval-toasts';
|
|
||||||
export * from './lib/withdrawal-approval-status';
|
export * from './lib/withdrawal-approval-status';
|
||||||
|
@ -50,11 +50,10 @@ const EthWithdrawalApprovalToastContent = ({
|
|||||||
</strong>
|
</strong>
|
||||||
</Panel>
|
</Panel>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{title.length > 0 && (
|
{title.length > 0 && <ToastHeading>{title}</ToastHeading>}
|
||||||
<ToastHeading className="font-bold">{title}</ToastHeading>
|
|
||||||
)}
|
|
||||||
<VerificationStatus state={tx} />
|
<VerificationStatus state={tx} />
|
||||||
{details}
|
{details}
|
||||||
</>
|
</>
|
||||||
|
@ -49,6 +49,7 @@ import { useMarketList } from '@vegaprotocol/markets';
|
|||||||
import type { Side } from '@vegaprotocol/types';
|
import type { Side } from '@vegaprotocol/types';
|
||||||
import { OrderStatusMapping } from '@vegaprotocol/types';
|
import { OrderStatusMapping } from '@vegaprotocol/types';
|
||||||
import { Size } from '@vegaprotocol/datagrid';
|
import { Size } from '@vegaprotocol/datagrid';
|
||||||
|
import { useWithdrawalApprovalDialog } from './withdrawal-approval-dialog';
|
||||||
|
|
||||||
const intentMap: { [s in VegaTxStatus]: Intent } = {
|
const intentMap: { [s in VegaTxStatus]: Intent } = {
|
||||||
Default: Intent.Primary,
|
Default: Intent.Primary,
|
||||||
@ -457,20 +458,41 @@ const VegaTxCompleteToastsContent = ({ tx }: VegaTxToastContentProps) => {
|
|||||||
</Button>
|
</Button>
|
||||||
</p>
|
</p>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const dialogTrigger = (
|
||||||
|
// It has to stay as <a> due to the word breaking issue
|
||||||
|
// eslint-disable-next-line jsx-a11y/anchor-is-valid
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="inline underline underline-offset-4 cursor-pointer text-inherit break-words"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (tx.withdrawal?.id) {
|
||||||
|
useWithdrawalApprovalDialog.getState().open(tx.withdrawal?.id);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('save your withdrawal details')}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ToastHeading>{t('Funds unlocked')}</ToastHeading>
|
<ToastHeading>{t('Funds unlocked')}</ToastHeading>
|
||||||
<p>{t('Your funds have been unlocked for withdrawal')}</p>
|
<p>{t('Your funds have been unlocked for withdrawal.')}</p>
|
||||||
{tx.txHash && (
|
{tx.txHash && (
|
||||||
<p className="break-all">
|
<ExternalLink
|
||||||
<ExternalLink
|
className="block mb-[5px] break-all"
|
||||||
href={explorerLink(EXPLORER_TX.replace(':hash', tx.txHash))}
|
href={explorerLink(EXPLORER_TX.replace(':hash', tx.txHash))}
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
>
|
>
|
||||||
{t('View in block explorer')}
|
{t('View in block explorer')}
|
||||||
</ExternalLink>
|
</ExternalLink>
|
||||||
</p>
|
|
||||||
)}
|
)}
|
||||||
|
{/* TODO: Delay message - This withdrawal is subject to a delay. Come back in 5 days to complete the withdrawal. */}
|
||||||
|
<p className="break-words">
|
||||||
|
{t('You can')} {dialogTrigger} {t('for extra security.')}
|
||||||
|
</p>
|
||||||
<VegaTransactionDetails tx={tx} />
|
<VegaTransactionDetails tx={tx} />
|
||||||
{completeWithdrawalButton}
|
{completeWithdrawalButton}
|
||||||
</>
|
</>
|
||||||
@ -638,8 +660,9 @@ export const useVegaTransactionToasts = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const fromVegaTransaction = (tx: VegaStoredTxState): Toast => {
|
const fromVegaTransaction = (tx: VegaStoredTxState): Toast => {
|
||||||
const closeAfter = isFinal(tx) ? CLOSE_AFTER : undefined;
|
|
||||||
const { intent, content } = getVegaTransactionContentIntent(tx);
|
const { intent, content } = getVegaTransactionContentIntent(tx);
|
||||||
|
const closeAfter =
|
||||||
|
isFinal(tx) && !isWithdrawTransaction(tx.body) ? CLOSE_AFTER : undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: `vega-${tx.id}`,
|
id: `vega-${tx.id}`,
|
||||||
@ -680,10 +703,22 @@ export const getVegaTransactionContentIntent = (tx: VegaStoredTxState) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Transaction can be successful but the order can be rejected by the network
|
// Transaction can be successful but the order can be rejected by the network
|
||||||
|
const intentForRejectedOrder =
|
||||||
|
tx.order &&
|
||||||
|
!isOrderAmendmentTransaction(tx.body) &&
|
||||||
|
getOrderToastIntent(tx.order.status);
|
||||||
|
|
||||||
|
// Although the transaction is completed on the vega network the whole
|
||||||
|
// withdrawal process is not - funds are only released at this point
|
||||||
|
const intentForCompletedWithdrawal =
|
||||||
|
tx.status === VegaTxStatus.Complete &&
|
||||||
|
isWithdrawTransaction(tx.body) &&
|
||||||
|
Intent.Warning;
|
||||||
|
|
||||||
const intent =
|
const intent =
|
||||||
(tx.order &&
|
intentForRejectedOrder ||
|
||||||
!isOrderAmendmentTransaction(tx.body) &&
|
intentForCompletedWithdrawal ||
|
||||||
getOrderToastIntent(tx.order.status)) ||
|
|
||||||
intentMap[tx.status];
|
intentMap[tx.status];
|
||||||
|
|
||||||
return { intent, content };
|
return { intent, content };
|
||||||
};
|
};
|
||||||
|
187
libs/web3/src/lib/withdrawal-approval-dialog.tsx
Normal file
187
libs/web3/src/lib/withdrawal-approval-dialog.tsx
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
import { t } from '@vegaprotocol/i18n';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
CopyWithTooltip,
|
||||||
|
Dialog,
|
||||||
|
Icon,
|
||||||
|
KeyValueTable,
|
||||||
|
KeyValueTableRow,
|
||||||
|
Splash,
|
||||||
|
SyntaxHighlighter,
|
||||||
|
VegaIcon,
|
||||||
|
VegaIconNames,
|
||||||
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
|
import { useWithdrawalApprovalQuery } from '@vegaprotocol/wallet';
|
||||||
|
import omit from 'lodash/omit';
|
||||||
|
import { create } from 'zustand';
|
||||||
|
|
||||||
|
type WithdrawalApprovalDialogProps = {
|
||||||
|
withdrawalId: string | undefined;
|
||||||
|
trigger?: HTMLElement | null;
|
||||||
|
open: boolean;
|
||||||
|
onChange: (open: boolean) => void;
|
||||||
|
asJson?: boolean;
|
||||||
|
};
|
||||||
|
export const WithdrawalApprovalDialog = ({
|
||||||
|
withdrawalId,
|
||||||
|
trigger,
|
||||||
|
open,
|
||||||
|
onChange,
|
||||||
|
asJson,
|
||||||
|
}: WithdrawalApprovalDialogProps) => {
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
title={t('Save withdrawal details')}
|
||||||
|
icon={<Icon name="info-sign"></Icon>}
|
||||||
|
open={open}
|
||||||
|
onChange={(isOpen) => onChange(isOpen)}
|
||||||
|
onCloseAutoFocus={(e) => {
|
||||||
|
/**
|
||||||
|
* This mimics radix's default behaviour that focuses the dialog's
|
||||||
|
* trigger after closing itself
|
||||||
|
*/
|
||||||
|
if (trigger) {
|
||||||
|
e.preventDefault();
|
||||||
|
trigger.focus();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="pr-8">
|
||||||
|
<p>
|
||||||
|
{t(
|
||||||
|
`If the network is reset or has an outage, records of your withdrawal
|
||||||
|
may be lost. We recommend you save these details in a safe place so
|
||||||
|
you can still complete your withdrawal.`
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
{withdrawalId ? (
|
||||||
|
<WithdrawalApprovalDialogContent
|
||||||
|
withdrawalId={withdrawalId}
|
||||||
|
asJson={Boolean(asJson)}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<NoDataContent />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="w-1/4">
|
||||||
|
<Button
|
||||||
|
data-testid="close-withdrawal-approval-dialog"
|
||||||
|
fill={true}
|
||||||
|
size="sm"
|
||||||
|
onClick={() => onChange(false)}
|
||||||
|
>
|
||||||
|
{t('Close')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
type WithdrawalApprovalDialogContentProps = {
|
||||||
|
withdrawalId: string;
|
||||||
|
asJson: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const NoDataContent = ({ msg = t('No data') }) => (
|
||||||
|
<div className="py-12" data-testid="splash">
|
||||||
|
<Splash>{msg}</Splash>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const WithdrawalApprovalDialogContent = ({
|
||||||
|
withdrawalId,
|
||||||
|
asJson,
|
||||||
|
}: WithdrawalApprovalDialogContentProps) => {
|
||||||
|
const { data, loading } = useWithdrawalApprovalQuery({
|
||||||
|
variables: {
|
||||||
|
withdrawalId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return <NoDataContent msg={t('Loading')} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data?.erc20WithdrawalApproval) {
|
||||||
|
const details = omit(data.erc20WithdrawalApproval, '__typename');
|
||||||
|
|
||||||
|
if (asJson) {
|
||||||
|
return (
|
||||||
|
<div className="py-4">
|
||||||
|
<SyntaxHighlighter size="smaller" data={details} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<div className="py-4 flex flex-col">
|
||||||
|
<div className="self-end mb-1">
|
||||||
|
<CopyWithTooltip text={JSON.stringify(details, undefined, 2)}>
|
||||||
|
<Button
|
||||||
|
className="flex gap-1 items-center no-underline"
|
||||||
|
size="xs"
|
||||||
|
variant="primary"
|
||||||
|
>
|
||||||
|
<VegaIcon name={VegaIconNames.COPY} size={14} />
|
||||||
|
<span className="text-sm no-underline">{t('Copy')}</span>
|
||||||
|
</Button>
|
||||||
|
</CopyWithTooltip>
|
||||||
|
</div>
|
||||||
|
<KeyValueTable>
|
||||||
|
{Object.entries(details).map(([key, value]) => (
|
||||||
|
<KeyValueTableRow key={key}>
|
||||||
|
<div data-testid={`${key}_label`}>{key}</div>
|
||||||
|
<div data-testid={`${key}_value`} className="break-all">
|
||||||
|
{value}
|
||||||
|
</div>
|
||||||
|
</KeyValueTableRow>
|
||||||
|
))}
|
||||||
|
</KeyValueTable>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return <NoDataContent />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type WithdrawalApprovalDialogStore = {
|
||||||
|
isOpen: boolean;
|
||||||
|
id: string;
|
||||||
|
trigger: HTMLElement | null | undefined;
|
||||||
|
asJson: boolean;
|
||||||
|
setOpen: (isOpen: boolean) => void;
|
||||||
|
open: (id: string, trigger?: HTMLElement | null, asJson?: boolean) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useWithdrawalApprovalDialog =
|
||||||
|
create<WithdrawalApprovalDialogStore>()((set) => ({
|
||||||
|
isOpen: false,
|
||||||
|
id: '',
|
||||||
|
trigger: null,
|
||||||
|
asJson: false,
|
||||||
|
setOpen: (isOpen) => {
|
||||||
|
set({ isOpen: isOpen });
|
||||||
|
},
|
||||||
|
open: (id, trigger?, asJson = false) => {
|
||||||
|
set({
|
||||||
|
isOpen: true,
|
||||||
|
id,
|
||||||
|
trigger,
|
||||||
|
asJson,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const WithdrawalApprovalDialogContainer = () => {
|
||||||
|
const { isOpen, id, trigger, setOpen, asJson } =
|
||||||
|
useWithdrawalApprovalDialog();
|
||||||
|
return (
|
||||||
|
<WithdrawalApprovalDialog
|
||||||
|
withdrawalId={id}
|
||||||
|
trigger={trigger || null}
|
||||||
|
open={isOpen}
|
||||||
|
onChange={setOpen}
|
||||||
|
asJson={asJson}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
@ -1,13 +1,13 @@
|
|||||||
|
export * from './lib/__generated__/Erc20Approval';
|
||||||
|
export * from './lib/__generated__/Withdrawal';
|
||||||
export * from './lib/create-withdrawal-dialog';
|
export * from './lib/create-withdrawal-dialog';
|
||||||
export * from './lib/withdrawal-dialog';
|
|
||||||
export * from './lib/withdraw-form';
|
|
||||||
export * from './lib/withdraw-form-container';
|
|
||||||
export * from './lib/withdraw-manager';
|
|
||||||
export * from './lib/withdrawals-table';
|
|
||||||
export * from './lib/withdrawal-feedback';
|
|
||||||
export * from './lib/use-complete-withdraw';
|
export * from './lib/use-complete-withdraw';
|
||||||
export * from './lib/use-create-withdraw';
|
export * from './lib/use-create-withdraw';
|
||||||
export * from './lib/use-verify-withdrawal';
|
export * from './lib/use-verify-withdrawal';
|
||||||
|
export * from './lib/withdraw-form-container';
|
||||||
|
export * from './lib/withdraw-form';
|
||||||
|
export * from './lib/withdraw-manager';
|
||||||
|
export * from './lib/withdrawal-dialog';
|
||||||
|
export * from './lib/withdrawal-feedback';
|
||||||
export * from './lib/withdrawals-provider';
|
export * from './lib/withdrawals-provider';
|
||||||
export * from './lib/__generated__/Withdrawal';
|
export * from './lib/withdrawals-table';
|
||||||
export * from './lib/__generated__/Erc20Approval';
|
|
||||||
|
@ -9,7 +9,16 @@ import {
|
|||||||
} from '@vegaprotocol/utils';
|
} from '@vegaprotocol/utils';
|
||||||
import { useBottomPlaceholder } from '@vegaprotocol/datagrid';
|
import { useBottomPlaceholder } from '@vegaprotocol/datagrid';
|
||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import { ButtonLink } from '@vegaprotocol/ui-toolkit';
|
import {
|
||||||
|
ButtonLink,
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
Icon,
|
||||||
|
VegaIcon,
|
||||||
|
VegaIconNames,
|
||||||
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
import type {
|
import type {
|
||||||
TypedDataAgGrid,
|
TypedDataAgGrid,
|
||||||
VegaICellRendererParams,
|
VegaICellRendererParams,
|
||||||
@ -18,7 +27,10 @@ import type {
|
|||||||
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
|
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
|
||||||
import { EtherscanLink } from '@vegaprotocol/environment';
|
import { EtherscanLink } from '@vegaprotocol/environment';
|
||||||
import type { WithdrawalFieldsFragment } from './__generated__/Withdrawal';
|
import type { WithdrawalFieldsFragment } from './__generated__/Withdrawal';
|
||||||
import { useEthWithdrawApprovalsStore } from '@vegaprotocol/web3';
|
import {
|
||||||
|
useEthWithdrawApprovalsStore,
|
||||||
|
useWithdrawalApprovalDialog,
|
||||||
|
} from '@vegaprotocol/web3';
|
||||||
import * as Schema from '@vegaprotocol/types';
|
import * as Schema from '@vegaprotocol/types';
|
||||||
|
|
||||||
export const WithdrawalsTable = (
|
export const WithdrawalsTable = (
|
||||||
@ -119,6 +131,7 @@ export const WithdrawalsTable = (
|
|||||||
headerName={t('Transaction')}
|
headerName={t('Transaction')}
|
||||||
field="txHash"
|
field="txHash"
|
||||||
flex={2}
|
flex={2}
|
||||||
|
type="rightAligned"
|
||||||
cellRendererParams={{
|
cellRendererParams={{
|
||||||
complete: (withdrawal: WithdrawalFieldsFragment) => {
|
complete: (withdrawal: WithdrawalFieldsFragment) => {
|
||||||
createWithdrawApproval(withdrawal);
|
createWithdrawApproval(withdrawal);
|
||||||
@ -139,18 +152,51 @@ export type CompleteCellProps = {
|
|||||||
complete: (withdrawal: WithdrawalFieldsFragment) => void;
|
complete: (withdrawal: WithdrawalFieldsFragment) => void;
|
||||||
};
|
};
|
||||||
export const CompleteCell = ({ data, complete }: CompleteCellProps) => {
|
export const CompleteCell = ({ data, complete }: CompleteCellProps) => {
|
||||||
|
const open = useWithdrawalApprovalDialog((state) => state.open);
|
||||||
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return data.pendingOnForeignChain ? (
|
return data.pendingOnForeignChain ? (
|
||||||
'-'
|
'-'
|
||||||
) : (
|
) : (
|
||||||
<ButtonLink
|
<div className="flex justify-end gap-1">
|
||||||
data-testid="complete-withdrawal"
|
<ButtonLink
|
||||||
onClick={() => complete(data)}
|
data-testid="complete-withdrawal"
|
||||||
>
|
onClick={() => complete(data)}
|
||||||
{t('Complete withdrawal')}
|
>
|
||||||
</ButtonLink>
|
{t('Complete withdrawal')}
|
||||||
|
</ButtonLink>
|
||||||
|
|
||||||
|
<DropdownMenu
|
||||||
|
trigger={
|
||||||
|
<DropdownMenuTrigger
|
||||||
|
className="hover:bg-vega-light-200 dark:hover:bg-vega-dark-200 p-0.5 focus:rounded-full hover:rounded-full"
|
||||||
|
data-testid="dropdown-menu"
|
||||||
|
>
|
||||||
|
<VegaIcon name={VegaIconNames.KEBAB} />
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<DropdownMenuContent>
|
||||||
|
<DropdownMenuItem
|
||||||
|
key={'withdrawal-approval'}
|
||||||
|
data-testid="withdrawal-approval"
|
||||||
|
ref={ref}
|
||||||
|
onClick={() => {
|
||||||
|
if (data.id) {
|
||||||
|
open(data.id, ref.current, false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
<Icon name="info-sign" size={4} /> {t('View withdrawal details')}
|
||||||
|
</span>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user