From 457fd1ab7dcaed953918e759bed0a4048c3bf900 Mon Sep 17 00:00:00 2001 From: Kacper Szarkiewicz <43585069+Sharqiewicz@users.noreply.github.com> Date: Fri, 1 Nov 2024 16:32:26 +0100 Subject: [PATCH] Chore/reusable collapse (#615) * remove again TransactionDialog components cluster * add reusable Collapse * add reusable LabeledCopyablePublicKey * add BridgeFeeCollapse * simplify TransferDialog logic --- src/components/Collapse/index.tsx | 20 + .../LabeledCopyablePublicKey/index.tsx | 16 + .../transactions/TransactionDialog.tsx | 415 ------------------ .../BridgeFeeCollapse/index.tsx | 48 ++ .../TransferDialog/TransferDialog.tsx | 83 +--- src/pages/spacewalk/transactions/index.tsx | 22 +- 6 files changed, 107 insertions(+), 497 deletions(-) create mode 100644 src/components/Collapse/index.tsx create mode 100644 src/components/LabeledCopyablePublicKey/index.tsx delete mode 100644 src/pages/spacewalk/transactions/TransactionDialog.tsx create mode 100644 src/pages/spacewalk/transactions/TransferDialog/BridgeFeeCollapse/index.tsx diff --git a/src/components/Collapse/index.tsx b/src/components/Collapse/index.tsx new file mode 100644 index 00000000..26bf5713 --- /dev/null +++ b/src/components/Collapse/index.tsx @@ -0,0 +1,20 @@ +import React from 'react'; + +interface CollapseProps { + title: React.ReactNode; + children: React.ReactNode; + className?: string; + titleClassName?: string; + contentClassName?: string; + [key: string]: unknown; +} + +export function Collapse({ title, children, className, titleClassName, contentClassName, ...rest }: CollapseProps) { + return ( +
+ +
{title}
+
{children}
+
+ ); +} diff --git a/src/components/LabeledCopyablePublicKey/index.tsx b/src/components/LabeledCopyablePublicKey/index.tsx new file mode 100644 index 00000000..3989bbbb --- /dev/null +++ b/src/components/LabeledCopyablePublicKey/index.tsx @@ -0,0 +1,16 @@ +import { CopyablePublicKey } from '../PublicKey/CopyablePublicKey'; + +interface AddressDisplayProps { + label: string; + publicKey: string; + tenantName?: string; + showMemo?: boolean; + expectedStellarMemo?: string; +} + +export const AddressDisplay: React.FC = ({ label, publicKey }) => ( +
+
{label}
+ +
+); diff --git a/src/pages/spacewalk/transactions/TransactionDialog.tsx b/src/pages/spacewalk/transactions/TransactionDialog.tsx deleted file mode 100644 index afd081e2..00000000 --- a/src/pages/spacewalk/transactions/TransactionDialog.tsx +++ /dev/null @@ -1,415 +0,0 @@ -import { hexToU8a } from '@polkadot/util'; -import { DateTime } from 'luxon'; -import { useCallback, useEffect, useMemo, useState } from 'react'; -import { Divider, Link, Collapse } from 'react-daisyui'; -import { useGlobalState } from '../../../GlobalStateProvider'; -import CancelledDialogIcon from '../../../assets/dialog-status-cancelled'; -import PendingDialogIcon from '../../../assets/dialog-status-pending'; -import SuccessDialogIcon from '../../../assets/dialog-status-success'; -import WarningDialogIcon from '../../../assets/dialog-status-warning'; -import { CopyablePublicKey } from '../../../components/PublicKey/CopyablePublicKey'; -import TransferCountdown from '../../../components/TransferCountdown'; -import { - addSuffix, - calculateDeadline, - convertCurrencyToStellarAsset, - deriveShortenedRequestId, -} from '../../../helpers/spacewalk'; -import { convertRawHexKeyToPublicKey } from '../../../helpers/stellar'; -import { toTitle } from '../../../helpers/string'; -import { useSecurityPallet } from '../../../hooks/spacewalk/useSecurityPallet'; -import { useVaultRegistryPallet } from '../../../hooks/spacewalk/useVaultRegistryPallet'; -import { nativeToDecimal } from '../../..//shared/parseNumbers/metric'; -import { Dialog } from '../../../components/Dialog'; -import { TTransfer, TransferType } from './TransactionsColumns'; -import { PENDULUM_SUPPORT_CHAT_URL } from '../../../shared/constants'; - -interface BaseTransactionDialogProps { - id: string; - visible: boolean; - showMemo?: boolean; - transfer: TTransfer; - title?: string; - content: JSX.Element; - footer?: JSX.Element; - statusIcon: JSX.Element; - actions?: (onConfirm: (() => void) | undefined) => JSX.Element; - onClose?: () => void; - onConfirm?: () => void; -} - -function BaseTransactionDialog(props: BaseTransactionDialogProps) { - const { id, statusIcon, showMemo, transfer, visible, title, content, footer, actions, onClose, onConfirm } = props; - - const { tenantName } = useGlobalState(); - const tenantNameCapitalized = tenantName ? toTitle(tenantName) : 'Pendulum'; - - const [vaultStellarPublicKey, setVaultStellarPublicKey] = useState(undefined); - const [collapseVisibility, setCollapseVisibility] = useState(''); - - const toggle = useCallback(() => { - if (collapseVisibility === '') { - setCollapseVisibility('collapse-open'); - } else { - setCollapseVisibility(''); - const elem = document.activeElement; - if (elem && elem instanceof HTMLElement) { - elem.blur(); - } - } - }, [collapseVisibility, setCollapseVisibility]); - - // The `stellarAddress` contained in the request will either be the user's Stellar address or the vault's Stellar address (i.e. equal to `vaultStellarPublicKey`). - const destinationStellarAddress = convertRawHexKeyToPublicKey(transfer.original.stellarAddress.toHex()).publicKey(); - const { getVaultStellarPublicKey } = useVaultRegistryPallet(); - - useEffect(() => { - getVaultStellarPublicKey(transfer.original.vault.accountId) - .then((publicKey) => { - if (publicKey) { - setVaultStellarPublicKey(publicKey.publicKey()); - } - }) - .catch((e) => { - setVaultStellarPublicKey(undefined); - console.error(e); - }); - }, [getVaultStellarPublicKey, transfer.original.vault.accountId]); - - const expectedStellarMemo = useMemo(() => { - return deriveShortenedRequestId(hexToU8a(transfer.transactionId)); - }, [transfer]); - - const body = ( - <> -
- {statusIcon} -
-

{title}

- {content} - - - -
Bridge fee
-
{nativeToDecimal(transfer.original.fee.toNumber()).toString()}
-
- -
-
Destination Address (Stellar)
- -
-
-
Vault Address ({tenantNameCapitalized})
- -
- {vaultStellarPublicKey && ( -
-
Vault Address (Stellar)
- -
- )} - {showMemo && ( -
-
Memo
- -
- )} -
-
-
- {footer} - - ); - - const onCloseMock = () => { - return undefined; - }; - - return ( - {actions && actions(onConfirm)}} - visible={visible} - /> - ); -} - -interface TransactionDialogProps { - transfer: TTransfer; - visible: boolean; - onClose?: () => void; -} - -export function CompletedTransactionDialog(props: TransactionDialogProps) { - const { transfer, visible, onClose } = props; - const { tenantName } = useGlobalState(); - let stellarAsset = convertCurrencyToStellarAsset(transfer.original.asset)?.getCode(); - if (stellarAsset && transfer.type === TransferType.issue) { - stellarAsset = addSuffix(stellarAsset); - } - const content = ( - <> -
{`You have received ${transfer.amount} ${stellarAsset}`}
- -
-
-
Spacewalk transaction
- -
- - ); - return ( - } - onClose={onClose} - onConfirm={onClose} - /> - ); -} - -export function CancelledTransactionDialog(props: TransactionDialogProps) { - const { transfer, visible, onClose } = props; - const { tenantName } = useGlobalState(); - const stellarAsset = convertCurrencyToStellarAsset(transfer.original.asset)?.getCode(); - const amountToSend = nativeToDecimal(transfer.original.amount.add(transfer.original.fee).toNumber()).toNumber(); - const content = ( - <> -
- {`You did not send a Stellar transaction in time, or the transferred amount did not meet the requested amount of ${amountToSend} - ${stellarAsset}.`} -
-
- Contact the team for debugging if you think this is an error. -
- -
-
Spacewalk transaction
- -
- - ); - return ( - } - onClose={onClose} - onConfirm={onClose} - /> - ); -} - -export function ReimbursedTransactionDialog(props: TransactionDialogProps) { - const { transfer, visible, onClose } = props; - - const stellarAsset = convertCurrencyToStellarAsset(transfer.original.asset)?.getCode(); - const collateralAsset = transfer.original.vault.currencies.collateral; - - const content = ( - <> -
- {'Your redeem request failed but you decided to burn ' + - stellarAsset + - ' in return for ' + - convertCurrencyToStellarAsset(collateralAsset)?.getCode()} -
-

- {transfer.amount} {stellarAsset} -

-
- - ); - return ( - } - onClose={onClose} - onConfirm={onClose} - /> - ); -} - -export function PendingTransactionDialog(props: TransactionDialogProps) { - const { transfer, visible, onClose } = props; - const stellarAsset = convertCurrencyToStellarAsset(transfer.original.asset)?.getCode(); - const destinationStellarAddress = convertRawHexKeyToPublicKey(transfer.original.stellarAddress.toHex()).publicKey(); - const amountToSend = nativeToDecimal(transfer.original.amount.add(transfer.original.fee).toNumber()); - const { tenantName } = useGlobalState(); - const { getActiveBlockNumber } = useSecurityPallet(); - const [, setDeadline] = useState(); - - useEffect(() => { - getActiveBlockNumber().then((active) => { - setDeadline( - calculateDeadline(active as number, transfer.original.opentime.toNumber(), transfer.original.period.toNumber()), - ); - }); - }, [getActiveBlockNumber, setDeadline, transfer.original.opentime, transfer.original.period]); - - const expectedStellarMemo = useMemo(() => { - // For issue requests we use a shorter identifier for the memo - return deriveShortenedRequestId(hexToU8a(transfer.transactionId)); - }, [transfer]); - - const issueContent = ( - <> - <> -
{`Send ${amountToSend.toNumber()} ${stellarAsset}`}
-
-
-
With the text memo
- -
-
-
In a single transaction to
- -
-
- Within -
- - -
-
- Note: Estimated time for issuing is in a minute after submitting the Stellar payment to the vault, contact - - support - - if your transaction is still pending after 10 minutes. -
-
- - ); - const redeemContent = ( - <> -
{`${amountToSend} ${stellarAsset}`}
-
The vault has to complete the transaction in:
- -
- -
- - ); - return ( - } - onClose={onClose} - onConfirm={onClose} - /> - ); -} - -export function FailedTransactionDialog(props: TransactionDialogProps) { - const { transfer, visible, onClose } = props; - const { tenantName } = useGlobalState(); - const stellarAsset = convertCurrencyToStellarAsset(transfer.original.asset)?.getCode(); - const amountToSend = nativeToDecimal(transfer.original.amount.add(transfer.original.fee).toNumber()).toNumber(); - const compensation = 0.05; - const content = ( - <> -
{`${amountToSend} ${stellarAsset}`}
- - - ); - const footer = ( -
-
- The vault did not send PEN to you on time. Don`t worry - your funds are safe! You`ll get {compensation} PEN - compensation. -
-
-
- To redeem your {stellarAsset}, you must now pick one of the two options: -
-
- 1. Receive compensation of {compensation} {stellarAsset} and retry with another vault.{' '} - Compensate -
-
- 2. Burn {stellarAsset} and get {amountToSend} {stellarAsset} with added {compensation} {stellarAsset} as - compensation. Reimburse -
-
- ); - return ( - } - onClose={onClose} - onConfirm={onClose} - /> - ); -} - -export default BaseTransactionDialog; diff --git a/src/pages/spacewalk/transactions/TransferDialog/BridgeFeeCollapse/index.tsx b/src/pages/spacewalk/transactions/TransferDialog/BridgeFeeCollapse/index.tsx new file mode 100644 index 00000000..0b0da43b --- /dev/null +++ b/src/pages/spacewalk/transactions/TransferDialog/BridgeFeeCollapse/index.tsx @@ -0,0 +1,48 @@ +import { nativeToDecimal } from '../../../../../shared/parseNumbers/metric'; +import { Collapse } from '../../../../../components/Collapse'; +import { AddressDisplay } from '../../../../../components/LabeledCopyablePublicKey'; +import { TTransfer } from '../../TransactionsColumns'; + +interface BridgeFeeCollapseProps { + transfer: TTransfer; + showMemo?: boolean; + destinationStellarAddress: string; + tenantName: string; + vaultStellarPublicKey?: string; + expectedStellarMemo: string; +} + +export function BridgeFeeCollapse({ + transfer, + showMemo, + destinationStellarAddress, + tenantName, + vaultStellarPublicKey, + expectedStellarMemo, +}: BridgeFeeCollapseProps) { + const title = ( + <> +
Bridge fee
+
{nativeToDecimal(transfer.original.fee.toNumber()).toString()}
+ + ); + return ( + + <> + + + {vaultStellarPublicKey && } + {showMemo && } + + + ); +} diff --git a/src/pages/spacewalk/transactions/TransferDialog/TransferDialog.tsx b/src/pages/spacewalk/transactions/TransferDialog/TransferDialog.tsx index ebb52219..a67b4e4b 100644 --- a/src/pages/spacewalk/transactions/TransferDialog/TransferDialog.tsx +++ b/src/pages/spacewalk/transactions/TransferDialog/TransferDialog.tsx @@ -1,5 +1,5 @@ import { hexToU8a } from '@polkadot/util'; -import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import { Divider } from 'react-daisyui'; import { useGlobalState } from '../../../../GlobalStateProvider'; @@ -7,10 +7,9 @@ import { deriveShortenedRequestId } from '../../../../helpers/spacewalk'; import { convertRawHexKeyToPublicKey } from '../../../../helpers/stellar'; import { toTitle } from '../../../../helpers/string'; import { useVaultRegistryPallet } from '../../../../hooks/spacewalk/useVaultRegistryPallet'; -import { nativeToDecimal } from '../../../../shared/parseNumbers/metric'; -import { CopyablePublicKey } from '../../../../components/PublicKey/CopyablePublicKey'; import { Dialog } from '../../../../components/Dialog'; import { TTransfer } from '../TransactionsColumns'; +import { BridgeFeeCollapse } from './BridgeFeeCollapse'; export interface BaseTransferDialogProps { id: string; @@ -33,19 +32,6 @@ export function BaseTransferDialog(props: BaseTransferDialogProps) { const tenantNameCapitalized = tenantName ? toTitle(tenantName) : 'Pendulum'; const [vaultStellarPublicKey, setVaultStellarPublicKey] = useState(undefined); - const [collapseVisibility, setCollapseVisibility] = useState(''); - - const toggle = useCallback(() => { - if (collapseVisibility === '') { - setCollapseVisibility('collapse-open'); - } else { - setCollapseVisibility(''); - const elem = document.activeElement; - if (elem && elem instanceof HTMLElement) { - elem.blur(); - } - } - }, [collapseVisibility, setCollapseVisibility]); // The `stellarAddress` contained in the request will either be the user's Stellar address or the vault's Stellar address (i.e. equal to `vaultStellarPublicKey`). const destinationStellarAddress = convertRawHexKeyToPublicKey(transfer.original.stellarAddress.toHex()).publicKey(); @@ -73,62 +59,17 @@ export function BaseTransferDialog(props: BaseTransferDialogProps) {
{statusIcon}
-

{title}

+

{title}

{content} - -
-
-
Bridge fee
-
{nativeToDecimal(transfer.original.fee.toNumber()).toString()}
-
-
-
-
Destination Address (Stellar)
- -
-
-
Vault Address ({tenantNameCapitalized})
- -
- {vaultStellarPublicKey && ( -
-
Vault Address (Stellar)
- -
- )} - {showMemo && ( -
-
Memo
- -
- )} -
-
+ +
{footer} diff --git a/src/pages/spacewalk/transactions/index.tsx b/src/pages/spacewalk/transactions/index.tsx index 6ab15c70..6628d583 100644 --- a/src/pages/spacewalk/transactions/index.tsx +++ b/src/pages/spacewalk/transactions/index.tsx @@ -14,12 +14,12 @@ import { useSecurityPallet } from '../../../hooks/spacewalk/useSecurityPallet'; import { nativeToDecimal } from '../../../shared/parseNumbers/metric'; import { - CancelledTransactionDialog, - CompletedTransactionDialog, - FailedTransactionDialog, - PendingTransactionDialog, - ReimbursedTransactionDialog, -} from './TransactionDialog'; + CancelledTransferDialog, + CompletedTransferDialog, + FailedTransferDialog, + PendingTransferDialog, + ReimbursedTransferDialog, +} from './TransferDialog'; import { TTransfer, TransferType, @@ -132,11 +132,11 @@ function Transactions(): JSX.Element { ); const dialogs: Record = { - Pending: getDialog(PendingTransactionDialog), - Completed: getDialog(CompletedTransactionDialog), - Reimbursed: getDialog(ReimbursedTransactionDialog), - Cancelled: getDialog(CancelledTransactionDialog), - Failed: getDialog(FailedTransactionDialog), + Pending: getDialog(PendingTransferDialog), + Completed: getDialog(CompletedTransferDialog), + Reimbursed: getDialog(ReimbursedTransferDialog), + Cancelled: getDialog(CancelledTransferDialog), + Failed: getDialog(FailedTransferDialog), }; return (