From 90794774263aff97a39de9f998447b772756b477 Mon Sep 17 00:00:00 2001 From: Polybius93 <99192647+Polybius93@users.noreply.github.com> Date: Mon, 23 Sep 2024 16:19:51 +0200 Subject: [PATCH] feat: update vault card to match new design (#177) * feat: update vault card to match new design * feat: optimize useConfirmationsChecker --- src/app/app.tsx | 6 +- .../components/mint-unmint.layout.tsx | 2 +- .../transaction-summary.tsx | 40 ++++----- .../components/unmint-vault-selector.tsx | 11 +-- .../components/my-vaults-small.layout.tsx | 2 +- .../vault/components/vault-card.layout.tsx | 33 -------- .../vault/components/vault-expand-button.tsx | 25 ------ .../vault-expanded-information-row.tsx | 48 ----------- ...t-expanded-information-transaction-row.tsx | 32 -------- .../vault-expanded-information.tsx | 80 ------------------ .../vault/components/vault-information.tsx | 65 --------------- .../vault/components/vault-progress-bar.tsx | 42 ---------- .../vault.details.button-group.button.tsx | 29 +++++++ .../vault.details.button-group.tsx | 31 +++++++ ...ails.transaction-stack.transaction-row.tsx | 36 +++++++++ .../vault.details.transaction-stack.tsx | 23 ++++++ .../vault.detaills/vault.details.tsx | 72 +++++++++++++++++ .../components/vault.header.copy-button.tsx | 41 ++++++++++ .../components/vault.header/vault.header.tsx | 48 +++++++++++ .../vault/components/vault.layout.tsx | 23 ++++++ .../vault.main-stack.action-button.tsx | 39 +++++++++ ...tack.asset-information-stack.asset-row.tsx | 29 +++++++ ...ult.main-stack.asset-information-stack.tsx | 27 +++++++ .../vault.main-stack/vault-main-stack.tsx | 37 +++++++++ .../vault/components/vault.progress-bar.tsx | 50 ++++++++++++ src/app/components/vault/vault-card.tsx | 58 ------------- src/app/components/vault/vault.tsx | 57 +++++++++++++ .../vaults-list-group-container.tsx | 19 ++--- src/app/hooks/use-blockchain-height-query.ts | 11 ++- src/app/hooks/use-confirmation-checker.ts | 81 +++++++++++-------- src/app/providers/bitcoin-query-provider.tsx | 23 +++--- src/shared/constants/bitcoin.constants.ts | 2 + src/styles/app-theme.ts | 8 +- 33 files changed, 652 insertions(+), 478 deletions(-) delete mode 100644 src/app/components/vault/components/vault-card.layout.tsx delete mode 100644 src/app/components/vault/components/vault-expand-button.tsx delete mode 100644 src/app/components/vault/components/vault-expanded-information/components/vault-expanded-information-row.tsx delete mode 100644 src/app/components/vault/components/vault-expanded-information/components/vault-expanded-information-transaction-row.tsx delete mode 100644 src/app/components/vault/components/vault-expanded-information/vault-expanded-information.tsx delete mode 100644 src/app/components/vault/components/vault-information.tsx delete mode 100644 src/app/components/vault/components/vault-progress-bar.tsx create mode 100644 src/app/components/vault/components/vault.detaills/components/vault.details.button-group/components/vault.details.button-group.button.tsx create mode 100644 src/app/components/vault/components/vault.detaills/components/vault.details.button-group/vault.details.button-group.tsx create mode 100644 src/app/components/vault/components/vault.detaills/components/vault.details.transaction-stack/components/vault.details.transaction-stack.transaction-row.tsx create mode 100644 src/app/components/vault/components/vault.detaills/components/vault.details.transaction-stack/vault.details.transaction-stack.tsx create mode 100644 src/app/components/vault/components/vault.detaills/vault.details.tsx create mode 100644 src/app/components/vault/components/vault.header/components/vault.header.copy-button.tsx create mode 100644 src/app/components/vault/components/vault.header/vault.header.tsx create mode 100644 src/app/components/vault/components/vault.layout.tsx create mode 100644 src/app/components/vault/components/vault.main-stack/components/vault.main-stack.action-button.tsx create mode 100644 src/app/components/vault/components/vault.main-stack/components/vault.main-stack.asset-information-stack/components/vault.main-stack.asset-information-stack.asset-row.tsx create mode 100644 src/app/components/vault/components/vault.main-stack/components/vault.main-stack.asset-information-stack/vault.main-stack.asset-information-stack.tsx create mode 100644 src/app/components/vault/components/vault.main-stack/vault-main-stack.tsx create mode 100644 src/app/components/vault/components/vault.progress-bar.tsx delete mode 100644 src/app/components/vault/vault-card.tsx create mode 100644 src/app/components/vault/vault.tsx diff --git a/src/app/app.tsx b/src/app/app.tsx index b1ca9705..af4b38fd 100644 --- a/src/app/app.tsx +++ b/src/app/app.tsx @@ -9,7 +9,7 @@ import { MyVaults } from '@pages/my-vaults/my-vaults'; import { PointsPage } from '@pages/points/points-page'; import { ProofOfReservePage } from '@pages/proof-of-reserve/proof-of-reserve-page'; import { BalanceContextProvider } from '@providers/balance-context-provider'; -import { BlockchainHeightContextProvider } from '@providers/bitcoin-query-provider'; +import { BitcoinTransactionConfirmationsProvider } from '@providers/bitcoin-query-provider'; import { BitcoinWalletContextProvider } from '@providers/bitcoin-wallet-context-provider'; import { EthereumNetworkConfigurationContextProvider } from '@providers/ethereum-network-configuration.provider'; import { EthereumObserverProvider } from '@providers/ethereum-observer-provider'; @@ -32,7 +32,7 @@ export function App(): React.JSX.Element { - + @@ -50,7 +50,7 @@ export function App(): React.JSX.Element { - + diff --git a/src/app/components/mint-unmint/components/mint-unmint.layout.tsx b/src/app/components/mint-unmint/components/mint-unmint.layout.tsx index cf80537d..47b5a423 100644 --- a/src/app/components/mint-unmint/components/mint-unmint.layout.tsx +++ b/src/app/components/mint-unmint/components/mint-unmint.layout.tsx @@ -9,7 +9,7 @@ export function MintUnmintLayout({ children }: MintUnmintLayoutProps): React.JSX return ( } {flowPropertyMap[flow][currentStep[0]].title}: - - {currentStep[0] === 2 && ( + {currentVault && ( <> - - b) {flowPropertyMap[flow][currentStep[0]].subtitle}: - - + + {currentStep[0] === 2 && ( + <> + + b) {flowPropertyMap[flow][currentStep[0]].subtitle}: + + + + )} )} diff --git a/src/app/components/mint-unmint/components/unmint/components/unmint-vault-selector.tsx b/src/app/components/mint-unmint/components/unmint/components/unmint-vault-selector.tsx index 9dedaa97..e23fae37 100644 --- a/src/app/components/mint-unmint/components/unmint/components/unmint-vault-selector.tsx +++ b/src/app/components/mint-unmint/components/unmint/components/unmint-vault-selector.tsx @@ -49,11 +49,6 @@ export function UnmintVaultSelector({ const { ethereumNetworkConfiguration } = useContext(EthereumNetworkConfigurationContext); - function handleSelect(uuid: string): void { - const vault = fundedVaults.find(vault => vault.uuid === uuid); - if (vault) setSelectedVault(vault); - } - useEffect(() => { setSelectedVault(fundedVaults.find(vault => vault.uuid === unmintStep[1])); }, [fundedVaults, unmintStep]); @@ -124,11 +119,7 @@ export function UnmintVaultSelector({ Select vault to withdraw Bitcoin: - + )} diff --git a/src/app/components/my-vaults-small/components/my-vaults-small.layout.tsx b/src/app/components/my-vaults-small/components/my-vaults-small.layout.tsx index a3d76fca..e41b63db 100644 --- a/src/app/components/my-vaults-small/components/my-vaults-small.layout.tsx +++ b/src/app/components/my-vaults-small/components/my-vaults-small.layout.tsx @@ -6,7 +6,7 @@ export function MyVaultsSmallLayout({ children }: HasChildren): React.JSX.Elemen void; -} - -export function VaultCardLayout({ - children, - isSelectable = false, - handleClick, -}: VaultCardLayoutProps): React.JSX.Element { - return ( - {}} - > - {children} - - ); -} diff --git a/src/app/components/vault/components/vault-expand-button.tsx b/src/app/components/vault/components/vault-expand-button.tsx deleted file mode 100644 index 9b6fa91f..00000000 --- a/src/app/components/vault/components/vault-expand-button.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { ChevronDownIcon, ChevronUpIcon } from '@chakra-ui/icons'; -import { Button, Text } from '@chakra-ui/react'; - -interface VaultExpandButtonProps { - isExpanded: boolean; - handleClick: () => void; -} - -export function VaultExpandButton({ - isExpanded, - handleClick, -}: VaultExpandButtonProps): React.JSX.Element { - return ( - - ); -} diff --git a/src/app/components/vault/components/vault-expanded-information/components/vault-expanded-information-row.tsx b/src/app/components/vault/components/vault-expanded-information/components/vault-expanded-information-row.tsx deleted file mode 100644 index 7ed35398..00000000 --- a/src/app/components/vault/components/vault-expanded-information/components/vault-expanded-information-row.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { CheckCircleIcon, CopyIcon } from '@chakra-ui/icons'; -import { Button, HStack, Text, Tooltip } from '@chakra-ui/react'; -import { useCopyToClipboard } from '@hooks/use-copy-to-clipboard'; -import { truncateAddress } from 'dlc-btc-lib/utilities'; - -interface VaultExpandedInformationUUIDRowProps { - uuid: string; -} - -export function VaultExpandedInformationUUIDRow({ - uuid, -}: VaultExpandedInformationUUIDRowProps): React.JSX.Element { - const { hasCopied, copyToClipboard } = useCopyToClipboard(); - - return ( - - - - UUID - - - - - - - {truncateAddress(uuid)} - - - ); -} diff --git a/src/app/components/vault/components/vault-expanded-information/components/vault-expanded-information-transaction-row.tsx b/src/app/components/vault/components/vault-expanded-information/components/vault-expanded-information-transaction-row.tsx deleted file mode 100644 index 73230440..00000000 --- a/src/app/components/vault/components/vault-expanded-information/components/vault-expanded-information-transaction-row.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { HStack, Text } from '@chakra-ui/react'; - -interface VaultExpandedInformationTransactionRowProps { - label: string; - value: string; -} - -export function VaultExpandedInformationTransactionRow({ - label, - value, -}: VaultExpandedInformationTransactionRowProps): React.JSX.Element { - return ( - - - {label} - - - window.open(`${appConfiguration.bitcoinBlockchainExplorerURL}/tx/${value}`, '_blank') - } - _hover={{ cursor: 'pointer' }} - > - View in TX explorer - - - ); -} diff --git a/src/app/components/vault/components/vault-expanded-information/vault-expanded-information.tsx b/src/app/components/vault/components/vault-expanded-information/vault-expanded-information.tsx deleted file mode 100644 index 16c92588..00000000 --- a/src/app/components/vault/components/vault-expanded-information/vault-expanded-information.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import { useDispatch } from 'react-redux'; -import { useNavigate } from 'react-router-dom'; - -import { Button, Divider, SlideFade, Stack, VStack } from '@chakra-ui/react'; -import { Vault } from '@models/vault'; -import { mintUnmintActions } from '@store/slices/mintunmint/mintunmint.actions'; -import { VaultState } from 'dlc-btc-lib/models'; - -import { VaultExpandedInformationUUIDRow } from './components/vault-expanded-information-row'; -import { VaultExpandedInformationTransactionRow } from './components/vault-expanded-information-transaction-row'; - -interface VaultExpandedInformationProps { - vault: Vault; - isExpanded: boolean; - isSelected?: boolean; - close: () => void; -} - -export function VaultExpandedInformation({ - vault, - isExpanded, - isSelected, - close, -}: VaultExpandedInformationProps): React.JSX.Element { - const navigate = useNavigate(); - const dispatch = useDispatch(); - - const { fundingTX, withdrawDepositTX, state, uuid } = vault; - - function handleWithdraw() { - navigate('/mint-withdraw'); - if (vault.valueLocked === vault.valueMinted) { - dispatch(mintUnmintActions.setUnmintStep([0, uuid])); - } else { - dispatch(mintUnmintActions.setUnmintStep([1, uuid])); - } - close(); - } - - return ( - - - - - - - - {!!fundingTX && ( - - )} - {!!withdrawDepositTX && ( - - )} - - {[VaultState.READY, VaultState.FUNDED].includes(state) && !isSelected && ( - - )} - {state === VaultState.FUNDED && !isSelected && ( - - )} - - - - - ); -} diff --git a/src/app/components/vault/components/vault-information.tsx b/src/app/components/vault/components/vault-information.tsx deleted file mode 100644 index 6dbcc1b2..00000000 --- a/src/app/components/vault/components/vault-information.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { CheckIcon } from '@chakra-ui/icons'; -import { HStack, Image, Text, VStack } from '@chakra-ui/react'; -import { VaultState } from 'dlc-btc-lib/models'; - -import { VaultExpandButton } from './vault-expand-button'; - -const getAssetLogo = (state: VaultState) => { - return [VaultState.FUNDED, VaultState.CLOSING, VaultState.CLOSED, VaultState.PENDING].includes( - state - ) - ? '/images/logos/dlc-btc-logo.svg' - : '/images/logos/bitcoin-logo.svg'; -}; - -interface VaultInformationProps { - collateral: number; - state: VaultState; - timestamp: number; - isExpanded: boolean; - isSelectable?: boolean; - isSelected?: boolean; - handleClick: () => void; -} - -export function VaultInformation({ - state, - collateral, - timestamp, - isExpanded, - isSelectable, - isSelected, - handleClick, -}: VaultInformationProps): React.JSX.Element { - const date = new Date(timestamp * 1000).toLocaleDateString('en-US'); - - return ( - - - - {'Icon'} - - {collateral} - - - - {date} - - - {isSelectable ? ( - - {isSelected && } - - ) : ( - handleClick()} /> - )} - - ); -} diff --git a/src/app/components/vault/components/vault-progress-bar.tsx b/src/app/components/vault/components/vault-progress-bar.tsx deleted file mode 100644 index 211d3103..00000000 --- a/src/app/components/vault/components/vault-progress-bar.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { Box, Progress, Text, VStack } from '@chakra-ui/react'; -import { VaultState } from 'dlc-btc-lib/models'; - -interface VaultProgressBarProps { - confirmedBlocks: number; - vaultState: VaultState; -} - -export function VaultProgressBar({ - confirmedBlocks, - vaultState, -}: VaultProgressBarProps): React.JSX.Element | boolean { - const shouldBeIndeterminate = confirmedBlocks > 6 || Number.isNaN(confirmedBlocks); - - if (vaultState === VaultState.CLOSED && confirmedBlocks > 6) return false; - return ( - - - - - {shouldBeIndeterminate ? 'PROCESSING' : `WAITING FOR CONFIRMATIONS: ${confirmedBlocks}/6`} - - - - ); -} diff --git a/src/app/components/vault/components/vault.detaills/components/vault.details.button-group/components/vault.details.button-group.button.tsx b/src/app/components/vault/components/vault.detaills/components/vault.details.button-group/components/vault.details.button-group.button.tsx new file mode 100644 index 00000000..756d683a --- /dev/null +++ b/src/app/components/vault/components/vault.detaills/components/vault.details.button-group/components/vault.details.button-group.button.tsx @@ -0,0 +1,29 @@ +import { Button } from '@chakra-ui/react'; + +interface VaultExpandedInformationButtonProps { + label: string; + onClick: () => void; + isDisabled?: boolean; +} + +export function VaultExpandedInformationButton({ + label, + onClick, + isDisabled, +}: VaultExpandedInformationButtonProps): React.JSX.Element { + return ( + + ); +} diff --git a/src/app/components/vault/components/vault.detaills/components/vault.details.button-group/vault.details.button-group.tsx b/src/app/components/vault/components/vault.detaills/components/vault.details.button-group/vault.details.button-group.tsx new file mode 100644 index 00000000..0fad1c04 --- /dev/null +++ b/src/app/components/vault/components/vault.detaills/components/vault.details.button-group/vault.details.button-group.tsx @@ -0,0 +1,31 @@ +import { HStack } from '@chakra-ui/react'; +import { VaultState } from 'dlc-btc-lib/models'; + +import { VaultExpandedInformationButton } from './components/vault.details.button-group.button'; + +interface VaultExpandedInformationButtonGroupProps { + vaultState: VaultState; + vaultTotalLockedValue: number; + handleDepositClick: () => void; + handleWithdrawClick: () => void; +} + +export function VaultExpandedInformationButtonGroup({ + vaultState, + vaultTotalLockedValue, + handleDepositClick, + handleWithdrawClick, +}: VaultExpandedInformationButtonGroupProps): React.JSX.Element { + const isButtonDisabled = vaultState === VaultState.READY || vaultTotalLockedValue === 0; + + return ( + + + + + ); +} diff --git a/src/app/components/vault/components/vault.detaills/components/vault.details.transaction-stack/components/vault.details.transaction-stack.transaction-row.tsx b/src/app/components/vault/components/vault.detaills/components/vault.details.transaction-stack/components/vault.details.transaction-stack.transaction-row.tsx new file mode 100644 index 00000000..0c45488c --- /dev/null +++ b/src/app/components/vault/components/vault.detaills/components/vault.details.transaction-stack/components/vault.details.transaction-stack.transaction-row.tsx @@ -0,0 +1,36 @@ +import { HStack, Text } from '@chakra-ui/react'; + +interface VaultTransactionRowProps { + label: string; + value?: string; +} + +export function VaultTransactionRow({ + label, + value, +}: VaultTransactionRowProps): React.JSX.Element | false { + if (!value) return false; + + return ( + + + + {label} + + + + + window.open(`${appConfiguration.bitcoinBlockchainExplorerURL}/tx/${value}`, '_blank') + } + _hover={{ cursor: 'pointer' }} + > + View in TX explorer + + + + ); +} diff --git a/src/app/components/vault/components/vault.detaills/components/vault.details.transaction-stack/vault.details.transaction-stack.tsx b/src/app/components/vault/components/vault.detaills/components/vault.details.transaction-stack/vault.details.transaction-stack.tsx new file mode 100644 index 00000000..b0c30999 --- /dev/null +++ b/src/app/components/vault/components/vault.detaills/components/vault.details.transaction-stack/vault.details.transaction-stack.tsx @@ -0,0 +1,23 @@ +import { Divider, VStack } from '@chakra-ui/react'; + +import { VaultTransactionRow } from './components/vault.details.transaction-stack.transaction-row'; + +interface VaultTransactionStackProps { + vaultFundingTX?: string; + vaultWithdrawDepositTX?: string; +} + +export function VaultTransactionStack({ + vaultFundingTX, + vaultWithdrawDepositTX, +}: VaultTransactionStackProps): React.JSX.Element | false { + if (!vaultFundingTX && !vaultWithdrawDepositTX) return false; + + return ( + + + + + + ); +} diff --git a/src/app/components/vault/components/vault.detaills/vault.details.tsx b/src/app/components/vault/components/vault.detaills/vault.details.tsx new file mode 100644 index 00000000..21351ae2 --- /dev/null +++ b/src/app/components/vault/components/vault.detaills/vault.details.tsx @@ -0,0 +1,72 @@ +import { useDispatch } from 'react-redux'; +import { useNavigate } from 'react-router-dom'; + +import { Collapse, Divider, Stack, VStack } from '@chakra-ui/react'; +import { mintUnmintActions } from '@store/slices/mintunmint/mintunmint.actions'; +import { VaultState } from 'dlc-btc-lib/models'; + +import { VaultExpandedInformationButtonGroup } from './components/vault.details.button-group/vault.details.button-group'; +import { VaultTransactionStack } from './components/vault.details.transaction-stack/vault.details.transaction-stack'; + +interface VaultDetailsProps { + vaultUUID: string; + vaultState: VaultState; + vaultTotalLockedValue: number; + vaultTotalMintedValue: number; + isVaultExpanded: boolean; + vaultFundingTX?: string; + vaultWithdrawDepositTX?: string; +} + +export function VaultDetails({ + vaultUUID, + vaultState, + vaultFundingTX, + vaultWithdrawDepositTX, + vaultTotalLockedValue, + vaultTotalMintedValue, + isVaultExpanded, +}: VaultDetailsProps): React.JSX.Element { + const navigate = useNavigate(); + const dispatch = useDispatch(); + + function handleDepositClick() { + navigate('/mint-withdraw'); + dispatch(mintUnmintActions.setMintStep([1, vaultUUID])); + close(); + } + + function handleWithdrawClick() { + navigate('/mint-withdraw'); + if (vaultTotalLockedValue === vaultTotalMintedValue) { + dispatch(mintUnmintActions.setUnmintStep([0, vaultUUID])); + } else { + dispatch(mintUnmintActions.setUnmintStep([1, vaultUUID])); + } + close(); + } + + return ( + + + + + + + {vaultState !== VaultState.PENDING && ( + + )} + + + + + ); +} diff --git a/src/app/components/vault/components/vault.header/components/vault.header.copy-button.tsx b/src/app/components/vault/components/vault.header/components/vault.header.copy-button.tsx new file mode 100644 index 00000000..dd0acb9b --- /dev/null +++ b/src/app/components/vault/components/vault.header/components/vault.header.copy-button.tsx @@ -0,0 +1,41 @@ +import { CheckCircleIcon, CopyIcon } from '@chakra-ui/icons'; +import { HStack, IconButton, Tooltip } from '@chakra-ui/react'; + +interface VaultCopyButtonProps { + onCopyUUID: () => void; + hasCopiedUUID: boolean; +} + +export function VaultCopyButton({ + onCopyUUID, + hasCopiedUUID, +}: VaultCopyButtonProps): React.JSX.Element { + return ( + + + + ) : ( + + ) + } + /> + + + ); +} diff --git a/src/app/components/vault/components/vault.header/vault.header.tsx b/src/app/components/vault/components/vault.header/vault.header.tsx new file mode 100644 index 00000000..dfc067b8 --- /dev/null +++ b/src/app/components/vault/components/vault.header/vault.header.tsx @@ -0,0 +1,48 @@ +import { HStack, Stack, Text, useClipboard } from '@chakra-ui/react'; +import { truncateAddress } from 'dlc-btc-lib/utilities'; + +import { VaultCopyButton } from './components/vault.header.copy-button'; + +interface VaultHeaderProps { + vaultUUID: string; + vaultCreationTimestamp: number; +} + +export function VaultHeader({ + vaultUUID, + vaultCreationTimestamp, +}: VaultHeaderProps): React.JSX.Element { + const { onCopy, hasCopied } = useClipboard(vaultUUID); + + const vaultTruncatedUUID = truncateAddress(vaultUUID); + const vaultCreationDate = new Date(vaultCreationTimestamp * 1000).toLocaleDateString('en-US'); + + return ( + + + + + Vault + + + + + {vaultTruncatedUUID} + + + + + + + {vaultCreationDate} + + + + ); +} diff --git a/src/app/components/vault/components/vault.layout.tsx b/src/app/components/vault/components/vault.layout.tsx new file mode 100644 index 00000000..f3c28fd0 --- /dev/null +++ b/src/app/components/vault/components/vault.layout.tsx @@ -0,0 +1,23 @@ +import { ReactNode } from 'react'; + +import { VStack } from '@chakra-ui/react'; + +interface VaultLayoutProps { + children: ReactNode; +} + +export function VaultLayout({ children }: VaultLayoutProps): React.JSX.Element { + return ( + + + {children} + + + ); +} diff --git a/src/app/components/vault/components/vault.main-stack/components/vault.main-stack.action-button.tsx b/src/app/components/vault/components/vault.main-stack/components/vault.main-stack.action-button.tsx new file mode 100644 index 00000000..483d3afb --- /dev/null +++ b/src/app/components/vault/components/vault.main-stack/components/vault.main-stack.action-button.tsx @@ -0,0 +1,39 @@ +import { ChevronDownIcon, ChevronUpIcon } from '@chakra-ui/icons'; +import { Button, HStack, Text } from '@chakra-ui/react'; + +interface VaultActionButtonProps { + isExpanded: boolean; + handleClick: () => void; + variant?: 'select'; +} + +export function VaultActionButton({ + isExpanded, + handleClick, + variant, +}: VaultActionButtonProps): React.JSX.Element { + return ( + + ); +} diff --git a/src/app/components/vault/components/vault.main-stack/components/vault.main-stack.asset-information-stack/components/vault.main-stack.asset-information-stack.asset-row.tsx b/src/app/components/vault/components/vault.main-stack/components/vault.main-stack.asset-information-stack/components/vault.main-stack.asset-information-stack.asset-row.tsx new file mode 100644 index 00000000..7df15cf4 --- /dev/null +++ b/src/app/components/vault/components/vault.main-stack/components/vault.main-stack.asset-information-stack/components/vault.main-stack.asset-information-stack.asset-row.tsx @@ -0,0 +1,29 @@ +import { HStack, Image, Text } from '@chakra-ui/react'; + +interface VaultAssetRowProps { + assetLogo: string; + assetValue: number; + assetSymbol: string; +} + +export function VaultAssetRow({ + assetLogo, + assetValue, + assetSymbol, +}: VaultAssetRowProps): React.JSX.Element { + return ( + + + {'Asset + + {assetValue} + + + + + {assetSymbol} + + + + ); +} diff --git a/src/app/components/vault/components/vault.main-stack/components/vault.main-stack.asset-information-stack/vault.main-stack.asset-information-stack.tsx b/src/app/components/vault/components/vault.main-stack/components/vault.main-stack.asset-information-stack/vault.main-stack.asset-information-stack.tsx new file mode 100644 index 00000000..6983d49b --- /dev/null +++ b/src/app/components/vault/components/vault.main-stack/components/vault.main-stack.asset-information-stack/vault.main-stack.asset-information-stack.tsx @@ -0,0 +1,27 @@ +import { VStack } from '@chakra-ui/react'; + +import { VaultAssetRow } from './components/vault.main-stack.asset-information-stack.asset-row'; + +interface VaultAssetInformationStackProps { + vaultTotalLockedValue: number; + vaultTotalMintedValue: number; +} +export function VaultAssetInformationStack({ + vaultTotalLockedValue, + vaultTotalMintedValue, +}: VaultAssetInformationStackProps): React.JSX.Element { + return ( + + + + + ); +} diff --git a/src/app/components/vault/components/vault.main-stack/vault-main-stack.tsx b/src/app/components/vault/components/vault.main-stack/vault-main-stack.tsx new file mode 100644 index 00000000..b081dc82 --- /dev/null +++ b/src/app/components/vault/components/vault.main-stack/vault-main-stack.tsx @@ -0,0 +1,37 @@ +import { Divider, HStack } from '@chakra-ui/react'; + +import { VaultActionButton } from './components/vault.main-stack.action-button'; +import { VaultAssetInformationStack } from './components/vault.main-stack.asset-information-stack/vault.main-stack.asset-information-stack'; + +interface VaultMainStackProps { + vaultTotalLockedValue: number; + vaultTotalMintedValue: number; + isVaultExpanded: boolean; + handleButtonClick: () => void; + variant?: 'select'; +} + +export function VaultMainStack({ + vaultTotalLockedValue, + vaultTotalMintedValue, + isVaultExpanded, + handleButtonClick, + variant, +}: VaultMainStackProps): React.JSX.Element { + return ( + + + + + handleButtonClick()} + variant={variant} + /> + + + ); +} diff --git a/src/app/components/vault/components/vault.progress-bar.tsx b/src/app/components/vault/components/vault.progress-bar.tsx new file mode 100644 index 00000000..8ff912e0 --- /dev/null +++ b/src/app/components/vault/components/vault.progress-bar.tsx @@ -0,0 +1,50 @@ +import { Box, Progress, Text, VStack } from '@chakra-ui/react'; +import { VaultState } from 'dlc-btc-lib/models'; + +import { BITCOIN_BLOCK_CONFIRMATIONS } from '@shared/constants/bitcoin.constants'; + +interface VaultProgressBarProps { + bitcoinTransactionConfirmations?: number; + vaultState: VaultState; +} + +export function VaultProgressBar({ + bitcoinTransactionConfirmations, + vaultState, +}: VaultProgressBarProps): React.JSX.Element | false { + if (vaultState !== VaultState.PENDING) return false; + + const showProcessing = + bitcoinTransactionConfirmations === undefined || + bitcoinTransactionConfirmations === 0 || + bitcoinTransactionConfirmations >= BITCOIN_BLOCK_CONFIRMATIONS; + return ( + + + + + {showProcessing + ? 'PROCESSING' + : `WAITING FOR CONFIRMATIONS: ${bitcoinTransactionConfirmations}/${BITCOIN_BLOCK_CONFIRMATIONS}`} + + + + ); +} diff --git a/src/app/components/vault/vault-card.tsx b/src/app/components/vault/vault-card.tsx deleted file mode 100644 index 2fd824cd..00000000 --- a/src/app/components/vault/vault-card.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import React, { useState } from 'react'; - -import { CustomSkeleton } from '@components/custom-skeleton/custom-skeleton'; -import { useConfirmationChecker } from '@hooks/use-confirmation-checker'; -import { Vault } from '@models/vault'; -import { VaultState } from 'dlc-btc-lib/models'; - -import { VaultCardLayout } from './components/vault-card.layout'; -import { VaultExpandedInformation } from './components/vault-expanded-information/vault-expanded-information'; -import { VaultInformation } from './components/vault-information'; -import { VaultProgressBar } from './components/vault-progress-bar'; - -interface VaultCardProps { - vault?: Vault; - isSelected?: boolean; - isSelectable?: boolean; - handleSelect?: () => void; -} - -export function VaultCard({ - vault, - isSelected = false, - isSelectable = false, - handleSelect, -}: VaultCardProps): React.JSX.Element { - const [isExpanded, setIsExpanded] = useState(isSelected ? true : false); - - const confirmations = useConfirmationChecker(vault); - - if (!vault) return ; - - return ( - handleSelect && handleSelect()}> - setIsExpanded(!isExpanded)} - /> - {isExpanded && ( - setIsExpanded(false)} - /> - )} - {vault.state === VaultState.PENDING && ( - - )} - - ); -} diff --git a/src/app/components/vault/vault.tsx b/src/app/components/vault/vault.tsx new file mode 100644 index 00000000..46c6a6c1 --- /dev/null +++ b/src/app/components/vault/vault.tsx @@ -0,0 +1,57 @@ +import React, { useContext, useState } from 'react'; +import { useDispatch } from 'react-redux'; + +import { Vault as VaultModel } from '@models/vault'; +import { BitcoinTransactionConfirmationsContext } from '@providers/bitcoin-query-provider'; +import { mintUnmintActions } from '@store/slices/mintunmint/mintunmint.actions'; + +import { VaultDetails } from './components/vault.detaills/vault.details'; +import { VaultHeader } from './components/vault.header/vault.header'; +import { VaultLayout } from './components/vault.layout'; +import { VaultMainStack } from './components/vault.main-stack/vault-main-stack'; +import { VaultProgressBar } from './components/vault.progress-bar'; + +interface VaultProps { + vault: VaultModel; + variant?: 'select'; +} + +export function Vault({ vault, variant }: VaultProps): React.JSX.Element { + const dispatch = useDispatch(); + const [isVaultExpanded, setIsVaultExpanded] = useState(false); + + function handleMainButtonClick() { + if (variant === 'select') { + dispatch(mintUnmintActions.setUnmintStep([0, vault.uuid])); + } else { + setIsVaultExpanded(!isVaultExpanded); + } + } + + const confirmations = useContext( + BitcoinTransactionConfirmationsContext + ).bitcoinTransactionConfirmations.find(v => v[0] === vault.uuid)?.[1]; + + return ( + + + + + + + ); +} diff --git a/src/app/components/vaults-list/components/vaults-list-group-container.tsx b/src/app/components/vaults-list/components/vaults-list-group-container.tsx index ae979fc2..ea4d1f60 100644 --- a/src/app/components/vaults-list/components/vaults-list-group-container.tsx +++ b/src/app/components/vaults-list/components/vaults-list-group-container.tsx @@ -1,11 +1,12 @@ import { Button, HStack, Image, Spinner, Text, VStack } from '@chakra-ui/react'; -import { VaultCard } from '@components/vault/vault-card'; +import { Vault } from '@components/vault/vault'; import { useAddToken } from '@hooks/use-add-token'; -import { Vault } from '@models/vault'; +import { Vault as VaultModel } from '@models/vault'; interface VaultsListGroupContainerProps { label?: string; - vaults: Vault[]; + variant?: 'select'; + vaults: VaultModel[]; selectedVaultUUID?: string; isSelectable?: boolean; handleSelect?: (uuid: string) => void; @@ -13,10 +14,8 @@ interface VaultsListGroupContainerProps { export function VaultsListGroupContainer({ label, + variant, vaults, - selectedVaultUUID, - isSelectable = false, - handleSelect, }: VaultsListGroupContainerProps): React.JSX.Element | boolean { const addToken = useAddToken(); @@ -49,13 +48,7 @@ export function VaultsListGroupContainer({ )} {vaults.map((vault, index) => ( - handleSelect && handleSelect(vault.uuid)} - /> + ))} ); diff --git a/src/app/hooks/use-blockchain-height-query.ts b/src/app/hooks/use-blockchain-height-query.ts index 1e8e925d..3d204876 100644 --- a/src/app/hooks/use-blockchain-height-query.ts +++ b/src/app/hooks/use-blockchain-height-query.ts @@ -4,9 +4,14 @@ export function useBlockchainHeightQuery(): number | undefined { const bitcoinExplorerHeightURL = `${appConfiguration.bitcoinBlockchainURL}/blocks/tip/height`; async function getBlockchainHeight() { - const response = await fetch(bitcoinExplorerHeightURL); - if (!response.ok) throw new Error('Network response was not ok'); - return response.json(); + try { + const response = await fetch(bitcoinExplorerHeightURL); + if (!response.ok) throw new Error('Network response was not ok'); + return response.json(); + } catch (error) { + console.error('Error fetching blockchain height', error); + return undefined; + } } const { data: blockHeight } = useQuery({ diff --git a/src/app/hooks/use-confirmation-checker.ts b/src/app/hooks/use-confirmation-checker.ts index 66100033..62054ee9 100644 --- a/src/app/hooks/use-confirmation-checker.ts +++ b/src/app/hooks/use-confirmation-checker.ts @@ -1,49 +1,62 @@ -import { useContext, useEffect, useState } from 'react'; +import { useContext } from 'react'; import { Vault } from '@models/vault'; -import { BlockchainHeightContext } from '@providers/bitcoin-query-provider'; +import { VaultContext } from '@providers/vault-context-provider'; import { useQuery } from '@tanstack/react-query'; -import { VaultState } from 'dlc-btc-lib/models'; -export function useConfirmationChecker(vault?: Vault): number { - const { blockHeight } = useContext(BlockchainHeightContext); +import { useBlockchainHeightQuery } from './use-blockchain-height-query'; - const [blockHeightAtBroadcast, setBlockHeightAtBroadcast] = useState( - undefined - ); - const [transactionProgress, setTransactionProgress] = useState(0); +export function useConfirmationChecker(): [string, number][] { + const blockHeight = useBlockchainHeightQuery(); + const { pendingVaults } = useContext(VaultContext); - const bitcoinExplorerTXURL = `${appConfiguration.bitcoinBlockchainURL}/tx/${vault?.withdrawDepositTX}`; + async function fetchBitcoinTransactionBlockHeight(vault: Vault): Promise { + try { + const bitcoinExplorerTXURL = `${appConfiguration.bitcoinBlockchainURL}/tx/${vault?.withdrawDepositTX}`; - async function fetchTransactionDetails() { - const response = await fetch(bitcoinExplorerTXURL); - if (!response.ok) throw new Error('Network response was not ok'); - const transactionDetails = await response.json(); - return transactionDetails.status.block_height; - } + const bitcoinTransactionResponse = await fetch(bitcoinExplorerTXURL); + const bitcoinTransaction = await bitcoinTransactionResponse.json(); + const bitcoinTransactionBlockHeight: number = bitcoinTransaction.status.block_height; - const { data: txBlockHeightAtBroadcast } = useQuery({ - queryKey: ['transactionDetails', vault?.withdrawDepositTX], - queryFn: () => fetchTransactionDetails(), - enabled: - !!vault?.withdrawDepositTX && vault?.state === VaultState.PENDING && !blockHeightAtBroadcast, - refetchInterval: 10000, - }); + if (!bitcoinTransactionBlockHeight) + throw new Error('Could not fetch Bitcoin Transaction Block Height'); - useEffect(() => { - if (txBlockHeightAtBroadcast && typeof txBlockHeightAtBroadcast === 'number') { - setBlockHeightAtBroadcast(txBlockHeightAtBroadcast); + return bitcoinTransactionBlockHeight; + } catch (error) { + throw new Error('Error fetching Bitcoin Transaction Block Height'); } - }, [txBlockHeightAtBroadcast]); + } - useEffect(() => { - if (vault?.state != VaultState.PENDING || transactionProgress > 6) return; + async function fetchBitcoinTransactionConfirmations( + vault: Vault, + blockHeight: number + ): Promise { + try { + const bitcoinTransactionBlockHeight = await fetchBitcoinTransactionBlockHeight(vault); - const blockHeightDifference = (blockHeight as number) + 1 - (blockHeightAtBroadcast as number); - if (typeof blockHeightDifference === 'number' && blockHeightDifference >= 0) { - setTransactionProgress(blockHeightDifference); + return blockHeight - bitcoinTransactionBlockHeight; + } catch (error) { + return 0; } - }, [blockHeightAtBroadcast, blockHeight, vault?.state, transactionProgress]); + } + + async function fetchAllBitcoinTransactionConfirmations(): Promise<[string, number][]> { + if (!blockHeight) throw new Error('Block Height is not available'); + return await Promise.all( + pendingVaults.map(async vault => { + const confirmations = await fetchBitcoinTransactionConfirmations(vault, blockHeight + 1); + return [vault.uuid, confirmations] as [string, number]; + }) + ); + } + + const { data: bitcoinTransactionConfirmations } = useQuery({ + queryKey: ['transactionDetails'], + initialData: [], + queryFn: () => fetchAllBitcoinTransactionConfirmations(), + enabled: pendingVaults.length > 0, + refetchInterval: 10000, + }); - return transactionProgress; + return bitcoinTransactionConfirmations; } diff --git a/src/app/providers/bitcoin-query-provider.tsx b/src/app/providers/bitcoin-query-provider.tsx index 97a56fa4..06b1164c 100644 --- a/src/app/providers/bitcoin-query-provider.tsx +++ b/src/app/providers/bitcoin-query-provider.tsx @@ -1,22 +1,25 @@ import { createContext } from 'react'; -import { useBlockchainHeightQuery } from '@hooks/use-blockchain-height-query'; +import { useConfirmationChecker } from '@hooks/use-confirmation-checker'; import { HasChildren } from '@models/has-children'; -interface BlockchainHeightContextProviderType { - blockHeight: number | undefined; +interface BitcoinTransactionConfirmationsProviderType { + bitcoinTransactionConfirmations: [string, number][]; } -export const BlockchainHeightContext = createContext({ - blockHeight: undefined, -}); +export const BitcoinTransactionConfirmationsContext = + createContext({ + bitcoinTransactionConfirmations: [], + }); -export function BlockchainHeightContextProvider({ children }: HasChildren): React.JSX.Element { - const blockHeight = useBlockchainHeightQuery(); +export function BitcoinTransactionConfirmationsProvider({ + children, +}: HasChildren): React.JSX.Element { + const bitcoinTransactionConfirmations = useConfirmationChecker(); return ( - + {children} - + ); } diff --git a/src/shared/constants/bitcoin.constants.ts b/src/shared/constants/bitcoin.constants.ts index 740a83b3..87baea6e 100644 --- a/src/shared/constants/bitcoin.constants.ts +++ b/src/shared/constants/bitcoin.constants.ts @@ -5,3 +5,5 @@ export const BITCOIN_NETWORK_MAP = { testnet: testnet, regtest: regtest, }; + +export const BITCOIN_BLOCK_CONFIRMATIONS = 6; diff --git a/src/styles/app-theme.ts b/src/styles/app-theme.ts index fa0e69fb..645f493c 100644 --- a/src/styles/app-theme.ts +++ b/src/styles/app-theme.ts @@ -23,10 +23,10 @@ export const appTheme = extendTheme({ Progress: { baseStyle: { track: { - bg: 'white.03', + bg: 'white.04', }, filledTrack: { - bg: 'border.lightBlue.01', + bg: 'orange.01', }, }, }, @@ -43,15 +43,19 @@ export const appTheme = extendTheme({ }), }, colors: { + 'purple.01': 'rgba(104, 24, 173, 1)', + 'orange.01': 'rgba(255, 168, 0, 1)', 'background.website.01': 'rgba(0, 0, 0, 1)', 'background.container.01': 'rgba(18, 18, 18, 1)', 'background.content.01': 'rgba(51, 51, 51, 1)', + 'grey.01': 'rgba(181, 182, 187, 1)', 'border.lightBlue.01': 'rgba(50, 201, 247,0.75)', 'border.white.01': 'rgba(255,255,255,0.25)', 'accent.lightBlue.01': 'rgba(50, 201, 247, 1)', 'white.01': 'rgba(255,255,255,1)', 'white.02': 'rgba(255,255,255,0.75)', 'white.03': 'rgba(255,255,255,0.35)', + 'white.04': 'rgba(255,255,255,0.10)', 'warning.01': 'rgba(255,204,85, 1)', 'error.01': 'rgba(255,51,102, 1)', 'table.background.green': 'rgba(50, 201, 247, 0.1)',