From e4f49c5f55db6b44c71f4ee0b4fca58af1cde792 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=B3za=20Nagy?= Date: Tue, 30 Jul 2024 15:24:43 +0200 Subject: [PATCH 01/10] feat: create vertical progress bar component --- public/images/check.svg | 3 +++ .../vault-vertical-progress-bar.tsx | 26 +++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 public/images/check.svg create mode 100644 src/app/components/vault/components/vault-vertical-progress-bar.tsx diff --git a/public/images/check.svg b/public/images/check.svg new file mode 100644 index 00000000..7478b634 --- /dev/null +++ b/public/images/check.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/app/components/vault/components/vault-vertical-progress-bar.tsx b/src/app/components/vault/components/vault-vertical-progress-bar.tsx new file mode 100644 index 00000000..2aab3d9e --- /dev/null +++ b/src/app/components/vault/components/vault-vertical-progress-bar.tsx @@ -0,0 +1,26 @@ +import { Divider, Image, Spinner, VStack } from '@chakra-ui/react'; + +interface VaultVerticalProgressBarProps { + stateA: boolean; + stateB: boolean; +} + +const StatusIndicator: React.FC<{ state: boolean }> = ({ state }) => + state ? ( + {'Icon'} + ) : ( + + ); + +export function VaultVerticalProgressBar({ + stateA, + stateB, +}: VaultVerticalProgressBarProps): React.JSX.Element { + return ( + + + + + + ); +} From 06b7cd499ae4f7ac2cba8e0a6b91689f1a39502f Mon Sep 17 00:00:00 2001 From: Polybius93 Date: Thu, 19 Sep 2024 17:43:38 +0200 Subject: [PATCH 02/10] feat: update vault card to match new design --- 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.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 +++++++++ ...rogress-bar.tsx => vault.progress-bar.tsx} | 22 +++-- 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-confirmation-checker.ts | 70 ++++++++-------- src/app/providers/bitcoin-query-provider.tsx | 23 +++--- src/styles/app-theme.ts | 8 +- 30 files changed, 593 insertions(+), 443 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 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 rename src/app/components/vault/components/{vault-progress-bar.tsx => vault.progress-bar.tsx} (53%) 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.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 similarity index 53% rename from src/app/components/vault/components/vault-progress-bar.tsx rename to src/app/components/vault/components/vault.progress-bar.tsx index 211d3103..d2675b7c 100644 --- a/src/app/components/vault/components/vault-progress-bar.tsx +++ b/src/app/components/vault/components/vault.progress-bar.tsx @@ -2,22 +2,26 @@ import { Box, Progress, Text, VStack } from '@chakra-ui/react'; import { VaultState } from 'dlc-btc-lib/models'; interface VaultProgressBarProps { - confirmedBlocks: number; + bitcoinTransactionConfirmations?: number; vaultState: VaultState; } export function VaultProgressBar({ - confirmedBlocks, + bitcoinTransactionConfirmations, vaultState, -}: VaultProgressBarProps): React.JSX.Element | boolean { - const shouldBeIndeterminate = confirmedBlocks > 6 || Number.isNaN(confirmedBlocks); +}: VaultProgressBarProps): React.JSX.Element | false { + if (vaultState !== VaultState.PENDING) return false; - if (vaultState === VaultState.CLOSED && confirmedBlocks > 6) return false; + const shouldBeIndeterminate = + bitcoinTransactionConfirmations === undefined || + bitcoinTransactionConfirmations === 0 || + bitcoinTransactionConfirmations > 6; return ( - + - {shouldBeIndeterminate ? 'PROCESSING' : `WAITING FOR CONFIRMATIONS: ${confirmedBlocks}/6`} + {shouldBeIndeterminate + ? 'PROCESSING' + : `WAITING FOR CONFIRMATIONS: ${bitcoinTransactionConfirmations}/6`} 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-confirmation-checker.ts b/src/app/hooks/use-confirmation-checker.ts index 66100033..4e3927b8 100644 --- a/src/app/hooks/use-confirmation-checker.ts +++ b/src/app/hooks/use-confirmation-checker.ts @@ -1,49 +1,47 @@ -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 fetchBitcoinTransactionConfirmations(vault: Vault): Promise { + 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); + if (!bitcoinTransactionResponse.ok) return 0; - const { data: txBlockHeightAtBroadcast } = useQuery({ - queryKey: ['transactionDetails', vault?.withdrawDepositTX], - queryFn: () => fetchTransactionDetails(), - enabled: - !!vault?.withdrawDepositTX && vault?.state === VaultState.PENDING && !blockHeightAtBroadcast, - refetchInterval: 10000, - }); + const bitcoinTransaction = await bitcoinTransactionResponse.json(); + const bitcoinTransactionBlockHeight = bitcoinTransaction.status.block_height; + + if (!bitcoinTransactionBlockHeight || !blockHeight) return 0; - useEffect(() => { - if (txBlockHeightAtBroadcast && typeof txBlockHeightAtBroadcast === 'number') { - setBlockHeightAtBroadcast(txBlockHeightAtBroadcast); - } - }, [txBlockHeightAtBroadcast]); + const bitcoinTransactionConfirmations = blockHeight + 1 - bitcoinTransactionBlockHeight; + return bitcoinTransactionConfirmations; + } - useEffect(() => { - if (vault?.state != VaultState.PENDING || transactionProgress > 6) return; + async function fetchAllBitcoinTransactionConfirmations(): Promise<[string, number][]> { + const bitcoinTransactionConfirmations: [string, number][] = await Promise.all( + pendingVaults.map(async vault => { + const confirmations = await fetchBitcoinTransactionConfirmations(vault); + return [vault.uuid, confirmations] as [string, number]; + }) + ); + return bitcoinTransactionConfirmations; + } - const blockHeightDifference = (blockHeight as number) + 1 - (blockHeightAtBroadcast as number); - if (typeof blockHeightDifference === 'number' && blockHeightDifference >= 0) { - setTransactionProgress(blockHeightDifference); - } - }, [blockHeightAtBroadcast, blockHeight, vault?.state, transactionProgress]); + 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/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)', From 7df13092ddb63870866fb994427fc1113c03ad95 Mon Sep 17 00:00:00 2001 From: Polybius93 Date: Fri, 20 Sep 2024 15:32:20 +0200 Subject: [PATCH 03/10] feat: optimize useConfirmationsChecker --- .../transaction-summary.tsx | 4 +- .../vault/components/vault.progress-bar.tsx | 12 +++-- src/app/hooks/use-blockchain-height-query.ts | 11 +++-- src/app/hooks/use-confirmation-checker.ts | 48 +++++++++++++++---- src/shared/constants/bitcoin.constants.ts | 2 + 5 files changed, 58 insertions(+), 19 deletions(-) diff --git a/src/app/components/mint-unmint/components/transaction-summary/transaction-summary.tsx b/src/app/components/mint-unmint/components/transaction-summary/transaction-summary.tsx index f93865cf..d333ceac 100644 --- a/src/app/components/mint-unmint/components/transaction-summary/transaction-summary.tsx +++ b/src/app/components/mint-unmint/components/transaction-summary/transaction-summary.tsx @@ -77,8 +77,8 @@ export function TransactionSummary({ flow === 'mint' ? depositAmount : parseInt( - new Decimal(currentVault?.valueLocked!) - .minus(currentVault?.valueMinted!) + new Decimal(currentVault.valueLocked) + .minus(currentVault.valueMinted) .toFixed(2) ) } diff --git a/src/app/components/vault/components/vault.progress-bar.tsx b/src/app/components/vault/components/vault.progress-bar.tsx index d2675b7c..8ff912e0 100644 --- a/src/app/components/vault/components/vault.progress-bar.tsx +++ b/src/app/components/vault/components/vault.progress-bar.tsx @@ -1,6 +1,8 @@ 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; @@ -12,14 +14,14 @@ export function VaultProgressBar({ }: VaultProgressBarProps): React.JSX.Element | false { if (vaultState !== VaultState.PENDING) return false; - const shouldBeIndeterminate = + const showProcessing = bitcoinTransactionConfirmations === undefined || bitcoinTransactionConfirmations === 0 || - bitcoinTransactionConfirmations > 6; + bitcoinTransactionConfirmations >= BITCOIN_BLOCK_CONFIRMATIONS; return ( - {shouldBeIndeterminate + {showProcessing ? 'PROCESSING' - : `WAITING FOR CONFIRMATIONS: ${bitcoinTransactionConfirmations}/6`} + : `WAITING FOR CONFIRMATIONS: ${bitcoinTransactionConfirmations}/${BITCOIN_BLOCK_CONFIRMATIONS}`} 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 4e3927b8..f8143688 100644 --- a/src/app/hooks/use-confirmation-checker.ts +++ b/src/app/hooks/use-confirmation-checker.ts @@ -1,4 +1,4 @@ -import { useContext } from 'react'; +import { useContext, useRef } from 'react'; import { Vault } from '@models/vault'; import { VaultContext } from '@providers/vault-context-provider'; @@ -10,19 +10,41 @@ export function useConfirmationChecker(): [string, number][] { const blockHeight = useBlockchainHeightQuery(); const { pendingVaults } = useContext(VaultContext); + const bitcoinTransactionBlockHeightMap = useRef(new Map()); + async function fetchBitcoinTransactionConfirmations(vault: Vault): Promise { - const bitcoinExplorerTXURL = `${appConfiguration.bitcoinBlockchainURL}/tx/${vault?.withdrawDepositTX}`; + try { + let bitcoinTransactionBlockHeight: number; - const bitcoinTransactionResponse = await fetch(bitcoinExplorerTXURL); - if (!bitcoinTransactionResponse.ok) return 0; + if (!bitcoinTransactionBlockHeightMap.current.has(vault.withdrawDepositTX)) { + const bitcoinExplorerTXURL = `${appConfiguration.bitcoinBlockchainURL}/tx/${vault?.withdrawDepositTX}`; - const bitcoinTransaction = await bitcoinTransactionResponse.json(); - const bitcoinTransactionBlockHeight = bitcoinTransaction.status.block_height; + const bitcoinTransactionResponse = await fetch(bitcoinExplorerTXURL); + if (!bitcoinTransactionResponse.ok) throw new Error('Could not fetch Bitcoin Transaction'); - if (!bitcoinTransactionBlockHeight || !blockHeight) return 0; + const bitcoinTransaction = await bitcoinTransactionResponse.json(); + bitcoinTransactionBlockHeight = bitcoinTransaction.status.block_height; - const bitcoinTransactionConfirmations = blockHeight + 1 - bitcoinTransactionBlockHeight; - return bitcoinTransactionConfirmations; + if (!bitcoinTransactionBlockHeight) + throw new Error('Could not fetch Bitcoin Transaction Block Height'); + bitcoinTransactionBlockHeightMap.current.set( + vault.withdrawDepositTX, + bitcoinTransactionBlockHeight + ); + } else { + bitcoinTransactionBlockHeight = bitcoinTransactionBlockHeightMap.current.get( + vault.withdrawDepositTX + )!; + } + + if (!blockHeight) throw new Error('Block Height is not available'); + + const bitcoinTransactionConfirmations = blockHeight + 1 - bitcoinTransactionBlockHeight; + return bitcoinTransactionConfirmations; + } catch (error) { + console.error('Error fetching Bitcoin Transaction Confirmations', error); + return 0; + } } async function fetchAllBitcoinTransactionConfirmations(): Promise<[string, number][]> { @@ -32,6 +54,14 @@ export function useConfirmationChecker(): [string, number][] { return [vault.uuid, confirmations] as [string, number]; }) ); + + const currentPendingVaultTXIDs = new Set(pendingVaults.map(vault => vault.withdrawDepositTX)); + + for (const key of bitcoinTransactionBlockHeightMap.current.keys()) { + if (!currentPendingVaultTXIDs.has(key)) { + bitcoinTransactionBlockHeightMap.current.delete(key); + } + } return bitcoinTransactionConfirmations; } 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; From b6e1588acaaed8e64af84320cd99938daa4b54d1 Mon Sep 17 00:00:00 2001 From: Polybius93 Date: Fri, 20 Sep 2024 16:36:53 +0200 Subject: [PATCH 04/10] feat: refactor useConfirmationChecker, remove unnecessary logic --- src/app/hooks/use-confirmation-checker.ts | 62 ++++++++++------------- 1 file changed, 28 insertions(+), 34 deletions(-) diff --git a/src/app/hooks/use-confirmation-checker.ts b/src/app/hooks/use-confirmation-checker.ts index f8143688..095d7e07 100644 --- a/src/app/hooks/use-confirmation-checker.ts +++ b/src/app/hooks/use-confirmation-checker.ts @@ -12,57 +12,51 @@ export function useConfirmationChecker(): [string, number][] { const bitcoinTransactionBlockHeightMap = useRef(new Map()); - async function fetchBitcoinTransactionConfirmations(vault: Vault): Promise { + async function fetchBitcoinTransactionBlockHeight(vault: Vault): Promise { try { - let bitcoinTransactionBlockHeight: number; + const bitcoinExplorerTXURL = `${appConfiguration.bitcoinBlockchainURL}/tx/${vault?.withdrawDepositTX}`; - if (!bitcoinTransactionBlockHeightMap.current.has(vault.withdrawDepositTX)) { - const bitcoinExplorerTXURL = `${appConfiguration.bitcoinBlockchainURL}/tx/${vault?.withdrawDepositTX}`; + const bitcoinTransactionResponse = await fetch(bitcoinExplorerTXURL); + const bitcoinTransaction = await bitcoinTransactionResponse.json(); + const bitcoinTransactionBlockHeight: number = bitcoinTransaction.status.block_height; - const bitcoinTransactionResponse = await fetch(bitcoinExplorerTXURL); - if (!bitcoinTransactionResponse.ok) throw new Error('Could not fetch Bitcoin Transaction'); + if (!bitcoinTransactionBlockHeight) + throw new Error('Could not fetch Bitcoin Transaction Block Height'); - const bitcoinTransaction = await bitcoinTransactionResponse.json(); - bitcoinTransactionBlockHeight = bitcoinTransaction.status.block_height; + bitcoinTransactionBlockHeightMap.current.set( + vault.withdrawDepositTX, + bitcoinTransactionBlockHeight + ); - if (!bitcoinTransactionBlockHeight) - throw new Error('Could not fetch Bitcoin Transaction Block Height'); - bitcoinTransactionBlockHeightMap.current.set( - vault.withdrawDepositTX, - bitcoinTransactionBlockHeight - ); - } else { - bitcoinTransactionBlockHeight = bitcoinTransactionBlockHeightMap.current.get( - vault.withdrawDepositTX - )!; - } + return bitcoinTransactionBlockHeight; + } catch (error) { + throw new Error('Error fetching Bitcoin Transaction Block Height'); + } + } - if (!blockHeight) throw new Error('Block Height is not available'); + async function fetchBitcoinTransactionConfirmations( + vault: Vault, + blockHeight: number + ): Promise { + try { + const bitcoinTransactionBlockHeight = + bitcoinTransactionBlockHeightMap.current.get(vault.withdrawDepositTX) ?? + (await fetchBitcoinTransactionBlockHeight(vault)); - const bitcoinTransactionConfirmations = blockHeight + 1 - bitcoinTransactionBlockHeight; - return bitcoinTransactionConfirmations; + return blockHeight - bitcoinTransactionBlockHeight; } catch (error) { - console.error('Error fetching Bitcoin Transaction Confirmations', error); return 0; } } async function fetchAllBitcoinTransactionConfirmations(): Promise<[string, number][]> { - const bitcoinTransactionConfirmations: [string, number][] = await Promise.all( + if (!blockHeight) throw new Error('Block Height is not available'); + return await Promise.all( pendingVaults.map(async vault => { - const confirmations = await fetchBitcoinTransactionConfirmations(vault); + const confirmations = await fetchBitcoinTransactionConfirmations(vault, blockHeight + 1); return [vault.uuid, confirmations] as [string, number]; }) ); - - const currentPendingVaultTXIDs = new Set(pendingVaults.map(vault => vault.withdrawDepositTX)); - - for (const key of bitcoinTransactionBlockHeightMap.current.keys()) { - if (!currentPendingVaultTXIDs.has(key)) { - bitcoinTransactionBlockHeightMap.current.delete(key); - } - } - return bitcoinTransactionConfirmations; } const { data: bitcoinTransactionConfirmations } = useQuery({ From 1b353415620ac6bb70baa8ec15ce15cd53b04074 Mon Sep 17 00:00:00 2001 From: Polybius93 Date: Fri, 20 Sep 2024 17:58:58 +0200 Subject: [PATCH 05/10] wip: add transaction form --- package.json | 1 + .../vault.timeline.transaction-form.tsx | 76 +++++++ src/app/components/vault/vault.tsx | 6 + src/styles/app-theme.ts | 16 +- yarn.lock | 205 +++++++++++++++++- 5 files changed, 301 insertions(+), 3 deletions(-) create mode 100644 src/app/components/vault/components/vault.timeline/components/vault.timeline.transaction-form/vault.timeline.transaction-form.tsx diff --git a/package.json b/package.json index a609d892..bb419e69 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "@ledgerhq/hw-transport-webusb": "^6.28.6", "@netlify/functions": "^2.8.1", "@reduxjs/toolkit": "^1.9.7", + "@tanstack/react-form": "^0.33.0", "@tanstack/react-query": "^5.51.16", "@trivago/prettier-plugin-sort-imports": "^4.2.1", "@types/chrome": "^0.0.248", diff --git a/src/app/components/vault/components/vault.timeline/components/vault.timeline.transaction-form/vault.timeline.transaction-form.tsx b/src/app/components/vault/components/vault.timeline/components/vault.timeline.transaction-form/vault.timeline.transaction-form.tsx new file mode 100644 index 00000000..de67aea5 --- /dev/null +++ b/src/app/components/vault/components/vault.timeline/components/vault.timeline.transaction-form/vault.timeline.transaction-form.tsx @@ -0,0 +1,76 @@ +import { HStack, Image, Input, Text, VStack } from '@chakra-ui/react'; +import { useForm } from '@tanstack/react-form'; + +interface VaultTransactionFormProps { + label: string; + assetLogo: string; + currentBitcoinPrice: number; +} + +export function VaultTransactionForm({ + label, + assetLogo, + currentBitcoinPrice, +}: VaultTransactionFormProps): React.JSX.Element { + const form = useForm({ + defaultValues: { + assetAmount: 0.01, + }, + onSubmit: async ({ value }) => { + // Do something with form data + console.log(value); + }, + }); + + return ( +
{ + e.preventDefault(); + e.stopPropagation(); + form.handleSubmit(); + }} + > + + {field => ( + + + {label} + + + + {'Asset + field.handleChange(e.target.valueAsNumber)} + onWheel={(e: React.WheelEvent) => + (e.target as HTMLInputElement).blur() + } + /> + + + dlcBTC + + + + {`~ $${Math.floor(field.state.value * currentBitcoinPrice).toLocaleString('en-US')} USD`} + + + )} + +
+ ); +} diff --git a/src/app/components/vault/vault.tsx b/src/app/components/vault/vault.tsx index 46c6a6c1..0b908cd2 100644 --- a/src/app/components/vault/vault.tsx +++ b/src/app/components/vault/vault.tsx @@ -10,6 +10,7 @@ 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'; +import { VaultTransactionForm } from './components/vault.timeline/components/vault.timeline.transaction-form/vault.timeline.transaction-form'; interface VaultProps { vault: VaultModel; @@ -51,6 +52,11 @@ export function Vault({ vault, variant }: VaultProps): React.JSX.Element { vaultFundingTX={vault.fundingTX} vaultWithdrawDepositTX={vault.withdrawDepositTX} /> + ); diff --git a/src/styles/app-theme.ts b/src/styles/app-theme.ts index 645f493c..b57d27d8 100644 --- a/src/styles/app-theme.ts +++ b/src/styles/app-theme.ts @@ -1,4 +1,4 @@ -import { extendTheme } from '@chakra-ui/react'; +import { border, extendTheme } from '@chakra-ui/react'; // Supports weights 100-900 import '@fontsource-variable/onest'; @@ -20,6 +20,20 @@ export const appTheme = extendTheme({ Text: textTheme, Tabs: tabsTheme, Divider: dividerTheme, + Input: { + baseStyle: { + field: { + fontWeight: 'bold', + color: 'white.01', + borderColor: 'red', + border: '35px solid', + focusBorderColor: 'accent.lightBlue.01', + _focus: { + borderColor: 'black', + }, + }, + }, + }, Progress: { baseStyle: { track: { diff --git a/yarn.lock b/yarn.lock index 14801d84..f085f385 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2473,11 +2473,85 @@ redux-thunk "^2.4.2" reselect "^4.1.8" +"@remix-run/node@^2.12.0": + version "2.12.1" + resolved "https://registry.yarnpkg.com/@remix-run/node/-/node-2.12.1.tgz#ab9ec3319682f935427a70d916f4621ee8162d64" + integrity sha512-d+IHvEEU3qziporgpEyKFvKdmNaDu+a/9pIxBkNKVWdKx2JR0VRFIaUxxpxISWtkJcoNuERhW2xYa6YvtFp4ig== + dependencies: + "@remix-run/server-runtime" "2.12.1" + "@remix-run/web-fetch" "^4.4.2" + "@web3-storage/multipart-parser" "^1.0.0" + cookie-signature "^1.1.0" + source-map-support "^0.5.21" + stream-slice "^0.1.2" + undici "^6.11.1" + "@remix-run/router@1.15.3": version "1.15.3" resolved "https://registry.npmjs.org/@remix-run/router/-/router-1.15.3.tgz" integrity sha512-Oy8rmScVrVxWZVOpEF57ovlnhpZ8CCPlnIIumVcV9nFdiSIrus99+Lw78ekXyGvVDlIsFJbSfmSovJUhCWYV3w== +"@remix-run/router@1.19.2": + version "1.19.2" + resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.19.2.tgz#0c896535473291cb41f152c180bedd5680a3b273" + integrity sha512-baiMx18+IMuD1yyvOGaHM9QrVUPGGG0jC+z+IPHnRJWUAUvaKuWKyE8gjDj2rzv3sz9zOGoRSPgeBVHRhZnBlA== + +"@remix-run/server-runtime@2.12.1": + version "2.12.1" + resolved "https://registry.yarnpkg.com/@remix-run/server-runtime/-/server-runtime-2.12.1.tgz#01868158da541697cd65d43f9916718580dedc61" + integrity sha512-iuj9ju34f0LztPpd5dVuTXgt4x/MJeRsBiLuEx02nDSMGoNCAIx2LdeNYvE+XXdsf1Ht2NMlpRU+HBPCz3QLZg== + dependencies: + "@remix-run/router" "1.19.2" + "@types/cookie" "^0.6.0" + "@web3-storage/multipart-parser" "^1.0.0" + cookie "^0.6.0" + set-cookie-parser "^2.4.8" + source-map "^0.7.3" + turbo-stream "2.4.0" + +"@remix-run/web-blob@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@remix-run/web-blob/-/web-blob-3.1.0.tgz#e0c669934c1eb6028960047e57a13ed38bbfb434" + integrity sha512-owGzFLbqPH9PlKb8KvpNJ0NO74HWE2euAn61eEiyCXX/oteoVzTVSN8mpLgDjaxBf2btj5/nUllSUgpyd6IH6g== + dependencies: + "@remix-run/web-stream" "^1.1.0" + web-encoding "1.1.5" + +"@remix-run/web-fetch@^4.4.2": + version "4.4.2" + resolved "https://registry.yarnpkg.com/@remix-run/web-fetch/-/web-fetch-4.4.2.tgz#ce7aedef72cc26e15060e8cf84674029f92809b6" + integrity sha512-jgKfzA713/4kAW/oZ4bC3MoLWyjModOVDjFPNseVqcJKSafgIscrYL9G50SurEYLswPuoU3HzSbO0jQCMYWHhA== + dependencies: + "@remix-run/web-blob" "^3.1.0" + "@remix-run/web-file" "^3.1.0" + "@remix-run/web-form-data" "^3.1.0" + "@remix-run/web-stream" "^1.1.0" + "@web3-storage/multipart-parser" "^1.0.0" + abort-controller "^3.0.0" + data-uri-to-buffer "^3.0.1" + mrmime "^1.0.0" + +"@remix-run/web-file@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@remix-run/web-file/-/web-file-3.1.0.tgz#07219021a2910e90231bc30ca1ce693d0e9d3825" + integrity sha512-dW2MNGwoiEYhlspOAXFBasmLeYshyAyhIdrlXBi06Duex5tDr3ut2LFKVj7tyHLmn8nnNwFf1BjNbkQpygC2aQ== + dependencies: + "@remix-run/web-blob" "^3.1.0" + +"@remix-run/web-form-data@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@remix-run/web-form-data/-/web-form-data-3.1.0.tgz#47f9ad8ce8bf1c39ed83eab31e53967fe8e3df6a" + integrity sha512-NdeohLMdrb+pHxMQ/Geuzdp0eqPbea+Ieo8M8Jx2lGC6TBHsgHzYcBvr0LyPdPVycNRDEpWpiDdCOdCryo3f9A== + dependencies: + web-encoding "1.1.5" + +"@remix-run/web-stream@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@remix-run/web-stream/-/web-stream-1.1.0.tgz#b93a8f806c2c22204930837c44d81fdedfde079f" + integrity sha512-KRJtwrjRV5Bb+pM7zxcTJkhIqWWSy+MYsIxHK+0m5atcznsf15YwUBWHWulZerV2+vvHH1Lp1DD7pw6qKW8SgA== + dependencies: + web-streams-polyfill "^3.1.1" + "@rollup/pluginutils@^5.0.5": version "5.1.0" resolved "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz" @@ -2769,11 +2843,28 @@ "@stablelib/random" "^1.0.2" "@stablelib/wipe" "^1.0.1" +"@tanstack/form-core@0.33.0": + version "0.33.0" + resolved "https://registry.yarnpkg.com/@tanstack/form-core/-/form-core-0.33.0.tgz#a8f894c309f3373a25bb5986248bb9dc364f10d9" + integrity sha512-ouu1JVwLZgfPkIdIq3TWIZs++KpLOo8Bx4tXYOc/1KwA9qUQ0Iv/+Y6GfiC4wMcUzGCtLKOww84eZ8Qdge3ZmQ== + dependencies: + "@tanstack/store" "^0.5.5" + "@tanstack/query-core@5.51.16": version "5.51.16" resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.51.16.tgz#01428b8b83016faf0fb0e4773d7b4794a2c53f18" integrity sha512-zfV+WAtBGm1dUIbL0w/x8qTqVLKU1/Bo1p19J9LF02MmIc4FxzMImMXhFzYJQl5Hx8Wit6RiQ4tB/DvN8y9zaQ== +"@tanstack/react-form@^0.33.0": + version "0.33.0" + resolved "https://registry.yarnpkg.com/@tanstack/react-form/-/react-form-0.33.0.tgz#97a0e6a3aa22a01b076f1172620687bb6ffa793e" + integrity sha512-+292gWfP67uAu3zYeIQAQYEk6T7IvQUiuKwanCyATROG92FsxQrYeJdqDk4Bkh0MB8uy13CilrdxZOoEMdEKSQ== + dependencies: + "@remix-run/node" "^2.12.0" + "@tanstack/form-core" "0.33.0" + "@tanstack/react-store" "^0.5.5" + decode-formdata "^0.8.0" + "@tanstack/react-query@^5.51.16": version "5.51.16" resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.51.16.tgz#3350d97180d9fda4eab21e1b04d457ef4bea7d1f" @@ -2781,6 +2872,19 @@ dependencies: "@tanstack/query-core" "5.51.16" +"@tanstack/react-store@^0.5.5": + version "0.5.5" + resolved "https://registry.yarnpkg.com/@tanstack/react-store/-/react-store-0.5.5.tgz#152e7b95de326886f7650f3a56d578369c061bd5" + integrity sha512-1orYXGatBqXCYKuroFwV8Ll/6aDa5E3pU6RR4h7RvRk7TmxF1+zLCsWALZaeijXkySNMGmvawSbUXRypivg2XA== + dependencies: + "@tanstack/store" "0.5.5" + use-sync-external-store "^1.2.2" + +"@tanstack/store@0.5.5", "@tanstack/store@^0.5.5": + version "0.5.5" + resolved "https://registry.yarnpkg.com/@tanstack/store/-/store-0.5.5.tgz#84d78568c4a71c885cb15754bf51eca42a776efe" + integrity sha512-EOSrgdDAJExbvRZEQ/Xhh9iZchXpMN+ga1Bnk8Nmygzs8TfiE6hbzThF+Pr2G19uHL6+DTDTHhJ8VQiOd7l4tA== + "@trivago/prettier-plugin-sort-imports@^4.2.1": version "4.3.0" resolved "https://registry.npmjs.org/@trivago/prettier-plugin-sort-imports/-/prettier-plugin-sort-imports-4.3.0.tgz" @@ -2846,6 +2950,11 @@ "@types/filesystem" "*" "@types/har-format" "*" +"@types/cookie@^0.6.0": + version "0.6.0" + resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.6.0.tgz#eac397f28bf1d6ae0ae081363eca2f425bedf0d5" + integrity sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA== + "@types/d3-array@*", "@types/d3-array@^3.0.1": version "3.2.1" resolved "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz" @@ -3650,6 +3759,11 @@ "@walletconnect/window-getters" "^1.0.1" tslib "1.14.1" +"@web3-storage/multipart-parser@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@web3-storage/multipart-parser/-/multipart-parser-1.0.0.tgz#6b69dc2a32a5b207ba43e556c25cc136a56659c4" + integrity sha512-BEO6al7BYqcnfX15W2cnGR+Q566ACXAT9UQykORCWW80lmkpWsnEob6zJS1ZVBKsSJC8+7vJkHwlp+lXG1UCdw== + "@yr/monotone-cubic-spline@^1.0.3": version "1.0.3" resolved "https://registry.npmjs.org/@yr/monotone-cubic-spline/-/monotone-cubic-spline-1.0.3.tgz" @@ -3672,6 +3786,11 @@ dependencies: "@zag-js/dom-query" "0.16.0" +"@zxing/text-encoding@0.9.0": + version "0.9.0" + resolved "https://registry.yarnpkg.com/@zxing/text-encoding/-/text-encoding-0.9.0.tgz#fb50ffabc6c7c66a0c96b4c03e3d9be74864b70b" + integrity sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA== + abitype@1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/abitype/-/abitype-1.0.5.tgz#29d0daa3eea867ca90f7e4123144c1d1270774b6" @@ -4161,6 +4280,11 @@ bs58check@^3.0.1: "@noble/hashes" "^1.2.0" bs58 "^5.0.0" +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + buffer@^6.0.3: version "6.0.3" resolved "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz" @@ -4410,6 +4534,16 @@ cookie-es@^1.1.0: resolved "https://registry.yarnpkg.com/cookie-es/-/cookie-es-1.2.2.tgz#18ceef9eb513cac1cb6c14bcbf8bdb2679b34821" integrity sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg== +cookie-signature@^1.1.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.2.1.tgz#790dea2cce64638c7ae04d9fabed193bd7ccf3b4" + integrity sha512-78KWk9T26NhzXtuL26cIJ8/qNHANyJ/ZYrmEXFzUmhZdjpBv+DlWlOANRTGBt48YcyslsLrj0bMLFTmXvLRCOw== + +cookie@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051" + integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw== + copy-to-clipboard@3.3.3: version "3.3.3" resolved "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz" @@ -4818,6 +4952,11 @@ damerau-levenshtein@^1.0.8: resolved "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz" integrity sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA== +data-uri-to-buffer@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz#594b8973938c5bc2c33046535785341abc4f3636" + integrity sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og== + data-view-buffer@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz" @@ -4890,6 +5029,11 @@ decimal.js@^10.4.3: resolved "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz" integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA== +decode-formdata@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/decode-formdata/-/decode-formdata-0.8.0.tgz#1631d0252ee73de07cdd6ce04e22e6e8acadc987" + integrity sha512-iUzDgnWsw5ToSkFY7VPFA5Gfph6ROoOxOB7Ybna4miUSzLZ4KaSJk6IAB2AdW6+C9vCVWhjjNA4gjT6wF3eZHQ== + decode-uri-component@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9" @@ -6894,6 +7038,11 @@ mri@^1.2.0: resolved "https://registry.yarnpkg.com/mri/-/mri-1.2.0.tgz#6721480fec2a11a4889861115a48b6cbe7cc8f0b" integrity sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA== +mrmime@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mrmime/-/mrmime-1.0.1.tgz#5f90c825fad4bdd41dc914eff5d1a8cfdaf24f27" + integrity sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw== + ms@2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz" @@ -7953,6 +8102,11 @@ set-blocking@^2.0.0: resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== +set-cookie-parser@^2.4.8: + version "2.7.0" + resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.7.0.tgz#ef5552b56dc01baae102acb5fc9fb8cd060c30f9" + integrity sha512-lXLOiqpkUumhRdFF3k1osNXCy9akgx/dyPZ5p8qAg9seJzXr5ZrlqZuWIMuY6ejOsVLE6flJ5/h3lsn57fQ/PQ== + set-function-length@^1.2.1: version "1.2.2" resolved "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz" @@ -8065,12 +8219,25 @@ source-map-js@^1.2.0: resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz" integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg== +source-map-support@^0.5.21: + version "0.5.21" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + source-map@^0.5.0, source-map@^0.5.7: version "0.5.7" resolved "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz" integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ== -source-map@^0.7.4: +source-map@^0.6.0: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +source-map@^0.7.3, source-map@^0.7.4: version "0.7.4" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656" integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA== @@ -8105,6 +8272,11 @@ stream-shift@^1.0.2: resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.3.tgz#85b8fab4d71010fc3ba8772e8046cc49b8a3864b" integrity sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ== +stream-slice@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/stream-slice/-/stream-slice-0.1.2.tgz#2dc4f4e1b936fb13f3eb39a2def1932798d07a4b" + integrity sha512-QzQxpoacatkreL6jsxnVb7X5R/pGw9OUv2qWTYWnmLpg4NdN31snPy/f3TdQE1ZUXaThRvj1Zw4/OGg0ZkaLMA== + strict-uri-encode@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546" @@ -8443,6 +8615,11 @@ tsutils@^3.21.0: dependencies: tslib "^1.8.1" +turbo-stream@2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/turbo-stream/-/turbo-stream-2.4.0.tgz#1e4fca6725e90fa14ac4adb782f2d3759a5695f0" + integrity sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g== + type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" resolved "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz" @@ -8570,6 +8747,11 @@ undici-types@~5.26.4: resolved "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz" integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== +undici@^6.11.1: + version "6.19.8" + resolved "https://registry.yarnpkg.com/undici/-/undici-6.19.8.tgz#002d7c8a28f8cc3a44ff33c3d4be4d85e15d40e1" + integrity sha512-U8uCCl2x9TK3WANvmBavymRzxbfFYG+tAu+fgx3zxQy3qdagQqBLwJVrdyO1TBfUXvfKveMKJZhpvUYoOjM+4g== + unenv@^1.9.0: version "1.10.0" resolved "https://registry.yarnpkg.com/unenv/-/unenv-1.10.0.tgz#c3394a6c6e4cfe68d699f87af456fe3f0db39571" @@ -8656,6 +8838,11 @@ use-sync-external-store@1.2.0, use-sync-external-store@^1.0.0: resolved "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz" integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== +use-sync-external-store@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz#c3b6390f3a30eba13200d2302dcdf1e7b57b2ef9" + integrity sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw== + utf-8-validate@^5.0.2: version "5.0.10" resolved "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-5.0.10.tgz#d7d10ea39318171ca982718b6b96a8d2442571a2" @@ -8668,7 +8855,7 @@ util-deprecate@^1.0.1, util-deprecate@~1.0.1: resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== -util@^0.12.4: +util@^0.12.3, util@^0.12.4: version "0.12.5" resolved "https://registry.yarnpkg.com/util/-/util-0.12.5.tgz#5f17a6059b73db61a875668781a1c2b136bd6fbc" integrity sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA== @@ -8805,6 +8992,20 @@ wagmi@^2.12.2: "@wagmi/core" "2.13.1" use-sync-external-store "1.2.0" +web-encoding@1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/web-encoding/-/web-encoding-1.1.5.tgz#fc810cf7667364a6335c939913f5051d3e0c4864" + integrity sha512-HYLeVCdJ0+lBYV2FvNZmv3HJ2Nt0QYXqZojk3d9FJOLkwnuhzM9tmamh8d7HPM8QqjKH8DeHkFTx+CFlWpZZDA== + dependencies: + util "^0.12.3" + optionalDependencies: + "@zxing/text-encoding" "0.9.0" + +web-streams-polyfill@^3.1.1: + version "3.3.3" + resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz#2073b91a2fdb1fbfbd401e7de0ac9f8214cecb4b" + integrity sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw== + webauthn-p256@0.0.5: version "0.0.5" resolved "https://registry.yarnpkg.com/webauthn-p256/-/webauthn-p256-0.0.5.tgz#0baebd2ba8a414b21cc09c0d40f9dd0be96a06bd" From e0b008eb924c544fa7c403bba91b763ea29e2f47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=B3za=20Nagy?= Date: Mon, 23 Sep 2024 10:29:56 +0200 Subject: [PATCH 06/10] feat: create statusindicator component --- public/images/loader.svg | 3 ++ .../vault-vertical-progress-bar.tsx | 42 +++++++++++++------ 2 files changed, 32 insertions(+), 13 deletions(-) create mode 100644 public/images/loader.svg diff --git a/public/images/loader.svg b/public/images/loader.svg new file mode 100644 index 00000000..ff798b1d --- /dev/null +++ b/public/images/loader.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/app/components/vault/components/vault-vertical-progress-bar.tsx b/src/app/components/vault/components/vault-vertical-progress-bar.tsx index 2aab3d9e..71fce6e6 100644 --- a/src/app/components/vault/components/vault-vertical-progress-bar.tsx +++ b/src/app/components/vault/components/vault-vertical-progress-bar.tsx @@ -1,26 +1,42 @@ import { Divider, Image, Spinner, VStack } from '@chakra-ui/react'; +export enum Status { + Completed = 'completed', + Current = 'current', + Inactive = 'inactive', +} + interface VaultVerticalProgressBarProps { - stateA: boolean; - stateB: boolean; + statusA: Status; + statusB: Status; } -const StatusIndicator: React.FC<{ state: boolean }> = ({ state }) => - state ? ( - {'Icon'} - ) : ( - - ); +const StatusIndicator = { + status: Status.Inactive, + + getComponent: (status: Status) => { + switch (status) { + case Status.Completed: + return {'Icon'}; + case Status.Current: + return ; + case Status.Inactive: + return {'Inactive; // Customize spinner as needed + default: + return null; + } + }, +}; export function VaultVerticalProgressBar({ - stateA, - stateB, + statusA, + statusB, }: VaultVerticalProgressBarProps): React.JSX.Element { return ( - - - + {StatusIndicator.getComponent(statusA)} + + {StatusIndicator.getComponent(statusB)} ); } From 67e33d3c7fde8821d11f5d2e67192d63b57e71f0 Mon Sep 17 00:00:00 2001 From: Polybius93 Date: Mon, 23 Sep 2024 12:11:37 +0200 Subject: [PATCH 07/10] feat: modify transaction form --- .../attestor-approvement-pending-stack.tsx | 16 -- .../burn-transaction-screen.tsx | 110 ++++++++ .../deposit-transaction-screen.tsx} | 62 ++--- .../components/mint-unmint.layout.tsx | 2 +- .../mint-unmint/components/mint/mint.tsx | 4 +- .../risk-box/components/risk-box.layout.tsx | 9 +- .../components/risk-box/risk-box.tsx | 42 +-- .../components/ethereum-transaction-form.tsx | 86 ------ .../components/transaction-form.tsx | 144 ---------- .../components/transaction-form-input.tsx | 90 ------- .../components/transaction-form-warning.tsx | 18 -- .../components/unmint-vault-selector.tsx | 96 +------ .../unmint/components/withdraw-screen.tsx | 108 +++----- .../mint-unmint/components/unmint/unmint.tsx | 1 - .../components/my-vaults-small.layout.tsx | 2 +- .../my-vaults-small/my-vaults-small.tsx | 2 +- ...screen.transaction-form.input-usd-text.tsx | 23 ++ ...nsaction-screen.transaction-form.input.tsx | 45 ++++ ...een.transaction-form.protocol-fee-box.tsx} | 32 ++- ...en.transaction-form.wallet-information.tsx | 36 +++ ...action-screen.transaction-form.warning.tsx | 27 ++ ...ansaction-form.transaction-information.tsx | 29 ++ .../transaction-screen.transaction-form.tsx | 252 ++++++++++++++++++ .../components/vault-information.tsx | 68 ----- .../components/vault-mini-card.layout.tsx | 25 -- .../components/vault-mini/vault-mini-card.tsx | 21 -- .../vault.detaills/vault.details.tsx | 4 +- .../vault.main-stack.action-button.tsx | 2 +- ...ult.main-stack.asset-information-stack.tsx | 4 +- .../vault.main-stack/vault-main-stack.tsx | 2 +- .../vault.timeline.transaction-form.tsx | 76 ------ src/app/components/vault/vault.tsx | 12 +- src/app/hooks/use-copy-to-clipboard.ts | 21 -- src/styles/app-theme.ts | 18 +- 34 files changed, 671 insertions(+), 818 deletions(-) delete mode 100644 src/app/components/mint-unmint/components/attestor-approvement-pending-stack/attestor-approvement-pending-stack.tsx create mode 100644 src/app/components/mint-unmint/components/burn-transaction-screen/burn-transaction-screen.tsx rename src/app/components/mint-unmint/components/{sign-transaction-screen/sign-funding-transaction-screen.tsx => deposit-transaction-screen/deposit-transaction-screen.tsx} (59%) delete mode 100644 src/app/components/mint-unmint/components/sign-transaction-screen/components/ethereum-transaction-form.tsx delete mode 100644 src/app/components/mint-unmint/components/sign-transaction-screen/components/transaction-form.tsx delete mode 100644 src/app/components/mint-unmint/components/sign-transaction-screen/components/transaction-form/components/transaction-form-input.tsx delete mode 100644 src/app/components/mint-unmint/components/sign-transaction-screen/components/transaction-form/components/transaction-form-warning.tsx create mode 100644 src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.input-usd-text.tsx create mode 100644 src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.input.tsx rename src/app/components/{mint-unmint/components/sign-transaction-screen/components/protocol-fee.tsx => transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.protocol-fee-box.tsx} (56%) create mode 100644 src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.wallet-information.tsx create mode 100644 src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.warning.tsx create mode 100644 src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screent.transaction-form.transaction-information.tsx create mode 100644 src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/transaction-screen.transaction-form.tsx delete mode 100644 src/app/components/vault-mini/components/vault-information.tsx delete mode 100644 src/app/components/vault-mini/components/vault-mini-card.layout.tsx delete mode 100644 src/app/components/vault-mini/vault-mini-card.tsx delete mode 100644 src/app/components/vault/components/vault.timeline/components/vault.timeline.transaction-form/vault.timeline.transaction-form.tsx delete mode 100644 src/app/hooks/use-copy-to-clipboard.ts diff --git a/src/app/components/mint-unmint/components/attestor-approvement-pending-stack/attestor-approvement-pending-stack.tsx b/src/app/components/mint-unmint/components/attestor-approvement-pending-stack/attestor-approvement-pending-stack.tsx deleted file mode 100644 index bdf567e2..00000000 --- a/src/app/components/mint-unmint/components/attestor-approvement-pending-stack/attestor-approvement-pending-stack.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react'; - -import { HStack, Spinner, Text } from '@chakra-ui/react'; - -export function AttestorApprovementPendingStack(): React.JSX.Element { - return ( - - - - - Please wait while we verify your transaction. - - - - ); -} diff --git a/src/app/components/mint-unmint/components/burn-transaction-screen/burn-transaction-screen.tsx b/src/app/components/mint-unmint/components/burn-transaction-screen/burn-transaction-screen.tsx new file mode 100644 index 00000000..1ca096ae --- /dev/null +++ b/src/app/components/mint-unmint/components/burn-transaction-screen/burn-transaction-screen.tsx @@ -0,0 +1,110 @@ +import { useContext } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; + +import { VStack, useToast } from '@chakra-ui/react'; +import { VaultTransactionForm } from '@components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/transaction-screen.transaction-form'; +import { Vault } from '@components/vault/vault'; +import { useEthersSigner } from '@functions/configuration.functions'; +import { getAndFormatVault } from '@functions/vault.functions'; +import { BitcoinWalletContext } from '@providers/bitcoin-wallet-context-provider'; +import { EthereumNetworkConfigurationContext } from '@providers/ethereum-network-configuration.provider'; +import { ProofOfReserveContext } from '@providers/proof-of-reserve-context-provider'; +import { VaultContext } from '@providers/vault-context-provider'; +import { RootState } from '@store/index'; +import { mintUnmintActions } from '@store/slices/mintunmint/mintunmint.actions'; +import { vaultActions } from '@store/slices/vault/vault.actions'; +import { withdraw } from 'dlc-btc-lib/ethereum-functions'; +import { EthereumNetworkID } from 'dlc-btc-lib/models'; +import { shiftValue } from 'dlc-btc-lib/utilities'; +import { useAccount } from 'wagmi'; + +interface BurnTokenTransactionFormProps { + isBitcoinWalletLoading: [boolean, string]; + userEthereumAddressRiskLevel: string; + fetchUserEthereumAddressRiskLevel: () => Promise; + isUserEthereumAddressRiskLevelLoading: boolean; +} + +export function BurnTokenTransactionForm({ + isBitcoinWalletLoading, + userEthereumAddressRiskLevel, + fetchUserEthereumAddressRiskLevel, + isUserEthereumAddressRiskLevelLoading, +}: BurnTokenTransactionFormProps): React.JSX.Element { + const toast = useToast(); + const dispatch = useDispatch(); + + const { bitcoinWalletContextState } = useContext(BitcoinWalletContext); + + const { ethereumNetworkConfiguration } = useContext(EthereumNetworkConfigurationContext); + const { bitcoinPrice, depositLimit } = useContext(ProofOfReserveContext); + const { allVaults } = useContext(VaultContext); + + const { chainId } = useAccount(); + + const signer = useEthersSigner(); + + const { unmintStep } = useSelector((state: RootState) => state.mintunmint); + + const currentVault = allVaults.find(vault => vault.uuid === unmintStep[1]); + + async function handleButtonClick(withdrawAmount: number): Promise { + if (!currentVault) return; + + try { + const currentRisk = await fetchUserEthereumAddressRiskLevel(); + if (currentRisk === 'High') throw new Error('Risk Level is too high'); + const formattedWithdrawAmount = BigInt(shiftValue(withdrawAmount)); + + await withdraw( + ethereumNetworkConfiguration.dlcManagerContract.connect(signer!), + currentVault.uuid, + formattedWithdrawAmount + ); + + await getAndFormatVault(currentVault.uuid, ethereumNetworkConfiguration.dlcManagerContract) + .then(vault => { + dispatch( + vaultActions.swapVault({ + vaultUUID: currentVault.uuid, + updatedVault: vault, + networkID: chainId?.toString() as EthereumNetworkID, + }) + ); + }) + .then(() => { + dispatch(mintUnmintActions.setUnmintStep([1, currentVault.uuid])); + }); + } catch (error) { + toast({ + title: 'Failed to sign Transaction', + description: error instanceof Error ? error.message : '', + status: 'error', + duration: 9000, + isClosable: true, + }); + } + } + + function handleCancel() { + dispatch(mintUnmintActions.setUnmintStep([0, ''])); + } + + return ( + + + + + ); +} diff --git a/src/app/components/mint-unmint/components/sign-transaction-screen/sign-funding-transaction-screen.tsx b/src/app/components/mint-unmint/components/deposit-transaction-screen/deposit-transaction-screen.tsx similarity index 59% rename from src/app/components/mint-unmint/components/sign-transaction-screen/sign-funding-transaction-screen.tsx rename to src/app/components/mint-unmint/components/deposit-transaction-screen/deposit-transaction-screen.tsx index 89bf1aa5..f5e51a96 100644 --- a/src/app/components/mint-unmint/components/sign-transaction-screen/sign-funding-transaction-screen.tsx +++ b/src/app/components/mint-unmint/components/deposit-transaction-screen/deposit-transaction-screen.tsx @@ -1,17 +1,20 @@ -import { useContext, useState } from 'react'; +import { useContext } from 'react'; import { useDispatch, useSelector } from 'react-redux'; -import { useToast } from '@chakra-ui/react'; -import { BitcoinWalletContext } from '@providers/bitcoin-wallet-context-provider'; +import { VStack, useToast } from '@chakra-ui/react'; +import { VaultTransactionForm } from '@components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/transaction-screen.transaction-form'; +import { Vault } from '@components/vault/vault'; +import { + BitcoinWalletContext, + BitcoinWalletContextState, +} from '@providers/bitcoin-wallet-context-provider'; import { ProofOfReserveContext } from '@providers/proof-of-reserve-context-provider'; import { VaultContext } from '@providers/vault-context-provider'; import { RootState } from '@store/index'; import { mintUnmintActions } from '@store/slices/mintunmint/mintunmint.actions'; import { modalActions } from '@store/slices/modal/modal.actions'; -import { DepositBitcoinTransactionForm } from './components/transaction-form'; - -interface SignFundingTransactionScreenProps { +interface DepositTransactionScreenProps { handleSignFundingTransaction: (vaultUUID: string, depositAmount: number) => Promise; isBitcoinWalletLoading: [boolean, string]; userEthereumAddressRiskLevel: string; @@ -19,13 +22,13 @@ interface SignFundingTransactionScreenProps { isUserEthereumAddressRiskLevelLoading: boolean; } -export function SignFundingTransactionScreen({ +export function DepositTransactionScreen({ handleSignFundingTransaction, isBitcoinWalletLoading, userEthereumAddressRiskLevel, fetchUserEthereumAddressRiskLevel, isUserEthereumAddressRiskLevelLoading, -}: SignFundingTransactionScreenProps): React.JSX.Element { +}: DepositTransactionScreenProps): React.JSX.Element { const toast = useToast(); const dispatch = useDispatch(); @@ -34,10 +37,6 @@ export function SignFundingTransactionScreen({ const { bitcoinPrice, depositLimit } = useContext(ProofOfReserveContext); const { allVaults } = useContext(VaultContext); - const [isSubmitting, setIsSubmitting] = useState(false); - - const [isAttestorApprovePending, setIsAttestorApprovePending] = useState(false); - const { mintStep } = useSelector((state: RootState) => state.mintunmint); const currentVault = allVaults.find(vault => vault.uuid === mintStep[1]); @@ -46,14 +45,10 @@ export function SignFundingTransactionScreen({ if (!currentVault) return; try { - setIsSubmitting(true); const currentRisk = await fetchUserEthereumAddressRiskLevel(); if (currentRisk === 'High') throw new Error('Risk Level is too high'); await handleSignFundingTransaction(currentVault.uuid, depositAmount); - setIsAttestorApprovePending(true); } catch (error: any) { - setIsSubmitting(false); - setIsAttestorApprovePending(false); toast({ title: 'Failed to sign Deposit Transaction', description: error.message, @@ -73,20 +68,27 @@ export function SignFundingTransactionScreen({ dispatch(mintUnmintActions.setMintStep([0, ''])); } + async function handleButtonClick(assetAmount: number) { + bitcoinWalletContextState === BitcoinWalletContextState.READY + ? await handleDeposit(assetAmount) + : handleConnect(); + } + return ( - + + + + ); } 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 47b5a423..795722d9 100644 --- a/src/app/components/mint-unmint/components/mint-unmint.layout.tsx +++ b/src/app/components/mint-unmint/components/mint-unmint.layout.tsx @@ -10,7 +10,7 @@ export function MintUnmintLayout({ children }: MintUnmintLayoutProps): React.JSX {[0].includes(mintStep[0]) && } {[1].includes(mintStep[0]) && ( - + {children} ); diff --git a/src/app/components/mint-unmint/components/risk-box/risk-box.tsx b/src/app/components/mint-unmint/components/risk-box/risk-box.tsx index 2cd23be5..e93d7680 100644 --- a/src/app/components/mint-unmint/components/risk-box/risk-box.tsx +++ b/src/app/components/mint-unmint/components/risk-box/risk-box.tsx @@ -7,32 +7,38 @@ interface RiskBoxProps { isRiskLoading: boolean; } -export function RiskBox({ risk }: RiskBoxProps): React.JSX.Element { +export function RiskBox({ risk }: RiskBoxProps): React.JSX.Element | false { + if (!['High', 'Severe'].includes(risk)) return false; + return ( - - + + Address Risk Level: {risk} - - - Potential suspicious activity detected, redemptions are temporarily suspended. - - - - Get in touch - {' '} - with your DLC.Link representative to resolve this issue. - + + + + Potential suspicious activity detected, redemptions are temporarily suspended. + + + + + + Get in touch + {' '} + with your DLC.Link representative to resolve this issue. + + ); diff --git a/src/app/components/mint-unmint/components/sign-transaction-screen/components/ethereum-transaction-form.tsx b/src/app/components/mint-unmint/components/sign-transaction-screen/components/ethereum-transaction-form.tsx deleted file mode 100644 index e216ad17..00000000 --- a/src/app/components/mint-unmint/components/sign-transaction-screen/components/ethereum-transaction-form.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import { Button, FormControl, FormErrorMessage, VStack } from '@chakra-ui/react'; -import { VaultMiniCard } from '@components/vault-mini/vault-mini-card'; -import { Vault } from '@models/vault'; -import { Form, Formik } from 'formik'; - -import { RiskBox } from '../../risk-box/risk-box'; -import { ProtocolFeeBox } from './protocol-fee'; -import { TransactionFormInput } from './transaction-form/components/transaction-form-input'; - -interface BurnTokenTransactionFormValues { - amount: number; -} - -const initialValues: BurnTokenTransactionFormValues = { amount: 0.01 }; - -interface BurnTokenTransactionFormProps { - vault?: Vault; - bitcoinPrice?: number; - isSubmitting: boolean; - risk: string; - isRiskLoading: boolean; - handleBurn: (tokenAmount: number) => Promise; - handleCancel: () => void; -} - -export function BurnTokenTransactionForm({ - vault, - bitcoinPrice, - isSubmitting, - risk, - isRiskLoading, - handleBurn, - handleCancel, -}: BurnTokenTransactionFormProps): React.JSX.Element { - return ( - - { - await handleBurn(values.amount); - }} - > - {({ handleSubmit, errors, touched, values }) => ( -
- - - {vault && } - - - {errors.amount} - - - {risk === 'High' && } - - - - -
- )} -
-
- ); -} diff --git a/src/app/components/mint-unmint/components/sign-transaction-screen/components/transaction-form.tsx b/src/app/components/mint-unmint/components/sign-transaction-screen/components/transaction-form.tsx deleted file mode 100644 index 02ac2de4..00000000 --- a/src/app/components/mint-unmint/components/sign-transaction-screen/components/transaction-form.tsx +++ /dev/null @@ -1,144 +0,0 @@ -import { - Button, - FormControl, - FormErrorMessage, - HStack, - Spinner, - Text, - VStack, -} from '@chakra-ui/react'; -import { VaultMiniCard } from '@components/vault-mini/vault-mini-card'; -import { Vault } from '@models/vault'; -import { BitcoinWalletContextState } from '@providers/bitcoin-wallet-context-provider'; -import { Form, Formik } from 'formik'; - -import { AttestorApprovementPendingStack } from '../../attestor-approvement-pending-stack/attestor-approvement-pending-stack'; -import { RiskBox } from '../../risk-box/risk-box'; -import { ProtocolFeeBox } from './protocol-fee'; -import { TransactionFormInput } from './transaction-form/components/transaction-form-input'; -import { TransactionFormWarning } from './transaction-form/components/transaction-form-warning'; - -interface DepositBitcoinTransactionFormValues { - amount: number; -} - -const initialValues: DepositBitcoinTransactionFormValues = { amount: 0.01 }; - -interface DepositBitcoinTransactionFormProps { - vault?: Vault; - bitcoinWalletContextState: BitcoinWalletContextState; - isBitcoinWalletLoading: [boolean, string]; - bitcoinPrice?: number; - isSubmitting: boolean; - isAttestorApprovePending: boolean; - userEthereumAddressRiskLevel: string; - isUserEthereumAddressRiskLevelLoading: boolean; - depositLimit: { minimumDeposit: number; maximumDeposit: number } | undefined; - handleConnect: () => void; - handleDeposit: (depositAmount: number) => Promise; - handleCancel: () => void; -} - -export function DepositBitcoinTransactionForm({ - vault, - bitcoinWalletContextState, - isBitcoinWalletLoading, - bitcoinPrice, - isSubmitting, - isAttestorApprovePending, - userEthereumAddressRiskLevel, - isUserEthereumAddressRiskLevelLoading, - depositLimit, - handleConnect, - handleDeposit, - handleCancel, -}: DepositBitcoinTransactionFormProps): React.JSX.Element { - async function handleClick(depositAmount: number) { - if (bitcoinWalletContextState === BitcoinWalletContextState.READY) { - await handleDeposit(depositAmount); - } else { - handleConnect(); - } - } - - return ( - - await handleClick(values.amount)} - > - {({ handleSubmit, handleChange, errors, touched, values }) => ( -
- - - {vault && } - - - {errors.amount} - - - {!errors.amount && !isBitcoinWalletLoading[0] && ( - - )} - {isBitcoinWalletLoading[0] && ( - - - {isBitcoinWalletLoading[1]} - - - - )} - - {['High', 'Severe'].includes(userEthereumAddressRiskLevel) && ( - - )} - {isAttestorApprovePending ? ( - - ) : ( - <> - - - - )} - - -
- )} -
-
- ); -} diff --git a/src/app/components/mint-unmint/components/sign-transaction-screen/components/transaction-form/components/transaction-form-input.tsx b/src/app/components/mint-unmint/components/sign-transaction-screen/components/transaction-form/components/transaction-form-input.tsx deleted file mode 100644 index ba119fa4..00000000 --- a/src/app/components/mint-unmint/components/sign-transaction-screen/components/transaction-form/components/transaction-form-input.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import { HStack, Image, Input, Text, VStack } from '@chakra-ui/react'; -import { Field } from 'formik'; - -interface TransactionFormInputProps { - header: string; - type: 'mint' | 'burn'; - values: { amount: number }; - depositLimit?: { minimumDeposit: number; maximumDeposit: number } | undefined; - bitcoinPrice?: number; - lockedAmount?: number; -} - -export function TransactionFormInput({ - header, - values, - type, - depositLimit, - bitcoinPrice, - lockedAmount, -}: TransactionFormInputProps): React.JSX.Element { - function validateDepositAmount(value: number): string | undefined { - let error; - - if (!value) { - error = 'Please enter a valid amount of dlcBTC'; - } else if (depositLimit && value < depositLimit.minimumDeposit) { - error = `You can't mint less than ${depositLimit.minimumDeposit} dlcBTC`; - } else if (depositLimit && value > depositLimit.maximumDeposit) { - error = `You can't mint more than ${depositLimit.maximumDeposit} dlcBTC`; - } - return error; - } - - function validateWithdrawAmount(value: number): string | undefined { - let error; - - if (!value) { - error = 'Please enter a valid amount of dlcBTC'; - } else if (lockedAmount && value > lockedAmount) { - error = `You can't burn more than ${lockedAmount} dlcBTC`; - } - return error; - } - - return ( - - - {header} - - - - {'dlcBTC'} - ) => - (e.target as HTMLInputElement).blur() - } - /> - - - dlcBTC - - - - {bitcoinPrice && `~ ${Math.floor(values.amount * bitcoinPrice).toLocaleString('en-US')} $`} - - - ); -} diff --git a/src/app/components/mint-unmint/components/sign-transaction-screen/components/transaction-form/components/transaction-form-warning.tsx b/src/app/components/mint-unmint/components/sign-transaction-screen/components/transaction-form/components/transaction-form-warning.tsx deleted file mode 100644 index eb03c7ae..00000000 --- a/src/app/components/mint-unmint/components/sign-transaction-screen/components/transaction-form/components/transaction-form-warning.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { HStack, Text } from '@chakra-ui/react'; - -interface TransactionFormWarningProps { - assetAmount: number; -} - -export function TransactionFormWarning({ - assetAmount, -}: TransactionFormWarningProps): React.JSX.Element { - return ( - - - Make sure you have {assetAmount} BTC + (fees) - in your Bitcoin Wallet before proceeding to the next step. - - - ); -} 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 e23fae37..a10d8f65 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 @@ -1,24 +1,13 @@ -import { useContext, useEffect, useState } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; +import { useContext } from 'react'; +import { useSelector } from 'react-redux'; -import { Text, VStack, useToast } from '@chakra-ui/react'; +import { Text, VStack } from '@chakra-ui/react'; import { VaultsListGroupContainer } from '@components/vaults-list/components/vaults-list-group-container'; import { VaultsList } from '@components/vaults-list/vaults-list'; -import { useEthersSigner } from '@functions/configuration.functions'; -import { getAndFormatVault } from '@functions/vault.functions'; -import { Vault } from '@models/vault'; -import { EthereumNetworkConfigurationContext } from '@providers/ethereum-network-configuration.provider'; -import { ProofOfReserveContext } from '@providers/proof-of-reserve-context-provider'; import { VaultContext } from '@providers/vault-context-provider'; import { RootState } from '@store/index'; -import { mintUnmintActions } from '@store/slices/mintunmint/mintunmint.actions'; -import { vaultActions } from '@store/slices/vault/vault.actions'; -import { withdraw } from 'dlc-btc-lib/ethereum-functions'; -import { EthereumNetworkID } from 'dlc-btc-lib/models'; -import { shiftValue } from 'dlc-btc-lib/utilities'; -import { useAccount } from 'wagmi'; -import { BurnTokenTransactionForm } from '../../sign-transaction-screen/components/ethereum-transaction-form'; +import { BurnTokenTransactionForm } from '../../burn-transaction-screen/burn-transaction-screen'; interface UnmintVaultSelectorProps { userEthereumAddressRiskLevel: string; @@ -31,83 +20,18 @@ export function UnmintVaultSelector({ fetchUserEthereumAddressRiskLevel, isUserEthereumAddressRiskLevelLoading, }: UnmintVaultSelectorProps): React.JSX.Element { - const toast = useToast(); - const dispatch = useDispatch(); - - const { bitcoinPrice } = useContext(ProofOfReserveContext); const { fundedVaults } = useContext(VaultContext); - const [isSubmitting, setIsSubmitting] = useState(false); - const { unmintStep } = useSelector((state: RootState) => state.mintunmint); - const { chainId } = useAccount(); - - const [selectedVault, setSelectedVault] = useState(); - - const signer = useEthersSigner(); - - const { ethereumNetworkConfiguration } = useContext(EthereumNetworkConfigurationContext); - - useEffect(() => { - setSelectedVault(fundedVaults.find(vault => vault.uuid === unmintStep[1])); - }, [fundedVaults, unmintStep]); - - async function handleBurn(withdrawAmount: number): Promise { - if (selectedVault) { - try { - setIsSubmitting(true); - const currentRisk = await fetchUserEthereumAddressRiskLevel(); - if (currentRisk === 'High') throw new Error('Risk Level is too high'); - const formattedWithdrawAmount = BigInt(shiftValue(withdrawAmount)); - - await withdraw( - ethereumNetworkConfiguration.dlcManagerContract.connect(signer!), - selectedVault.uuid, - formattedWithdrawAmount - ); - - await getAndFormatVault(selectedVault.uuid, ethereumNetworkConfiguration.dlcManagerContract) - .then(vault => { - dispatch( - vaultActions.swapVault({ - vaultUUID: selectedVault.uuid, - updatedVault: vault, - networkID: chainId?.toString() as EthereumNetworkID, - }) - ); - }) - .then(() => { - dispatch(mintUnmintActions.setUnmintStep([1, selectedVault.uuid])); - }); - } catch (error) { - setIsSubmitting(false); - toast({ - title: 'Failed to sign Transaction', - description: error instanceof Error ? error.message : '', - status: 'error', - duration: 9000, - isClosable: true, - }); - } - } - } - - function handleCancel() { - setSelectedVault(undefined); - } - return ( <> - {selectedVault ? ( + {unmintStep[1] ? ( ) : fundedVaults.length == 0 ? ( @@ -118,7 +42,7 @@ export function UnmintVaultSelector({ Select vault to withdraw Bitcoin: - + diff --git a/src/app/components/mint-unmint/components/unmint/components/withdraw-screen.tsx b/src/app/components/mint-unmint/components/unmint/components/withdraw-screen.tsx index f85aadf4..d8d6c181 100644 --- a/src/app/components/mint-unmint/components/unmint/components/withdraw-screen.tsx +++ b/src/app/components/mint-unmint/components/unmint/components/withdraw-screen.tsx @@ -1,53 +1,44 @@ -import { useContext, useState } from 'react'; -import { useDispatch } from 'react-redux'; +import { useContext } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; -import { Button, HStack, Spinner, Text, VStack, useToast } from '@chakra-ui/react'; -import { VaultMiniCard } from '@components/vault-mini/vault-mini-card'; +import { VStack, useToast } from '@chakra-ui/react'; +import { VaultTransactionForm } from '@components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/transaction-screen.transaction-form'; +import { Vault } from '@components/vault/vault'; import { BitcoinWalletContext, BitcoinWalletContextState, } from '@providers/bitcoin-wallet-context-provider'; +import { ProofOfReserveContext } from '@providers/proof-of-reserve-context-provider'; import { VaultContext } from '@providers/vault-context-provider'; +import { RootState } from '@store/index'; +import { mintUnmintActions } from '@store/slices/mintunmint/mintunmint.actions'; import { modalActions } from '@store/slices/modal/modal.actions'; -import Decimal from 'decimal.js'; - -import { AttestorApprovementPendingStack } from '../../attestor-approvement-pending-stack/attestor-approvement-pending-stack'; interface WithdrawScreenProps { - currentStep: [number, string]; - isBitcoinWalletLoading: [boolean, string]; handleSignWithdrawTransaction: (vaultUUID: string, withdrawAmount: number) => Promise; + isBitcoinWalletLoading: [boolean, string]; } export function WithdrawScreen({ - currentStep, - isBitcoinWalletLoading, handleSignWithdrawTransaction, + isBitcoinWalletLoading, }: WithdrawScreenProps): React.JSX.Element { const dispatch = useDispatch(); const toast = useToast(); - const [isSubmitting, setIsSubmitting] = useState(false); - const [isAttestorApprovePending, setIsAttestorApprovePending] = useState(false); - const { bitcoinWalletContextState } = useContext(BitcoinWalletContext); + const { bitcoinWalletContextState, resetBitcoinWalletContext } = useContext(BitcoinWalletContext); + const { bitcoinPrice, depositLimit } = useContext(ProofOfReserveContext); const { allVaults } = useContext(VaultContext); - const currentVault = allVaults.find(vault => vault.uuid === currentStep[1]); - const withdrawValue = new Decimal(currentVault?.valueLocked!).minus(currentVault?.valueMinted!); + const { unmintStep } = useSelector((state: RootState) => state.mintunmint); + const currentVault = allVaults.find(vault => vault.uuid === unmintStep[1]); - async function handleWithdraw(): Promise { + async function handleWithdraw(withdrawAmount: number): Promise { if (currentVault) { try { - const withdrawAmount = new Decimal(currentVault.valueLocked).minus( - currentVault.valueMinted - ); - setIsSubmitting(true); - await handleSignWithdrawTransaction(currentVault.uuid, withdrawAmount.toNumber()); - setIsAttestorApprovePending(true); + await handleSignWithdrawTransaction(currentVault.uuid, withdrawAmount); } catch (error) { - setIsSubmitting(false); - setIsAttestorApprovePending(false); toast({ title: 'Failed to sign transaction', description: error instanceof Error ? error.message : '', @@ -63,51 +54,30 @@ export function WithdrawScreen({ dispatch(modalActions.toggleSelectBitcoinWalletModalVisibility()); } + function handleCancel() { + resetBitcoinWalletContext(); + dispatch(mintUnmintActions.setUnmintStep([0, ''])); + } + + async function handleButtonClick(assetAmount: number) { + bitcoinWalletContextState === BitcoinWalletContextState.READY + ? await handleWithdraw(assetAmount) + : handleConnect(); + } + return ( - - - - {`Sign the Bitcoin transaction below to withdraw`} - - - {`${withdrawValue.toNumber()} BTC from your Vault`} - - - {currentVault && } - {isBitcoinWalletLoading[0] && ( - - - {isBitcoinWalletLoading[1]} - - - - )} - {isAttestorApprovePending ? ( - - ) : ( - - )} + + + ); } diff --git a/src/app/components/mint-unmint/components/unmint/unmint.tsx b/src/app/components/mint-unmint/components/unmint/unmint.tsx index 1e92c4ad..4ff75294 100644 --- a/src/app/components/mint-unmint/components/unmint/unmint.tsx +++ b/src/app/components/mint-unmint/components/unmint/unmint.tsx @@ -32,7 +32,6 @@ export function Unmint(): React.JSX.Element { )} {[1].includes(unmintStep[0]) && ( 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 e41b63db..1a53bf9e 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 @@ -7,7 +7,7 @@ export function MyVaultsSmallLayout({ children }: HasChildren): React.JSX.Elemen py={'2.5px'} px={'25px'} w={'31.5%'} - h={'625px'} + h={'825px'} bg={'background.container.01'} border={'1px solid'} borderRadius={'md'} diff --git a/src/app/components/my-vaults-small/my-vaults-small.tsx b/src/app/components/my-vaults-small/my-vaults-small.tsx index c834ff0a..668d6147 100644 --- a/src/app/components/my-vaults-small/my-vaults-small.tsx +++ b/src/app/components/my-vaults-small/my-vaults-small.tsx @@ -26,7 +26,7 @@ export function MyVaultsSmall(): React.JSX.Element { 0} > diff --git a/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.input-usd-text.tsx b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.input-usd-text.tsx new file mode 100644 index 00000000..10a5162b --- /dev/null +++ b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.input-usd-text.tsx @@ -0,0 +1,23 @@ +import { Text } from '@chakra-ui/react'; +import { ValidationError } from '@tanstack/react-form'; +import Decimal from 'decimal.js'; + +interface TransactionFormInputUSDTextProps { + errors: ValidationError[]; + assetAmount?: string; + currentBitcoinPrice?: number; +} + +export function TransactionFormInputUSDText({ + errors, + assetAmount, + currentBitcoinPrice, +}: TransactionFormInputUSDTextProps): React.JSX.Element | false { + if (errors.length) return false; + + return ( + + {`~ $${assetAmount && currentBitcoinPrice ? new Decimal(assetAmount).mul(currentBitcoinPrice).toNumber().toLocaleString('en-US') : 0} USD`} + + ); +} diff --git a/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.input.tsx b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.input.tsx new file mode 100644 index 00000000..2ca59253 --- /dev/null +++ b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.input.tsx @@ -0,0 +1,45 @@ +import { HStack, Image, NumberInput, NumberInputField, Text } from '@chakra-ui/react'; +import { FieldApi } from '@tanstack/react-form'; + +interface TransactionFormFieldInputProps { + assetLogo: string; + assetSymbol: string; + formField: FieldApi< + { + assetAmount: string; + }, + 'assetAmount', + undefined, + undefined, + string + >; +} + +export function TransactionFormFieldInput({ + assetLogo, + assetSymbol, + formField, +}: TransactionFormFieldInputProps): React.JSX.Element { + return ( + + + {'Asset + 0} + onChange={e => formField.handleChange(e)} + > + + + + + {assetSymbol} + + + ); +} diff --git a/src/app/components/mint-unmint/components/sign-transaction-screen/components/protocol-fee.tsx b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.protocol-fee-box.tsx similarity index 56% rename from src/app/components/mint-unmint/components/sign-transaction-screen/components/protocol-fee.tsx rename to src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.protocol-fee-box.tsx index 52a3c727..4814767e 100644 --- a/src/app/components/mint-unmint/components/sign-transaction-screen/components/protocol-fee.tsx +++ b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.protocol-fee-box.tsx @@ -2,10 +2,12 @@ import { HStack, Text, VStack } from '@chakra-ui/react'; import Decimal from 'decimal.js'; import { getFeeAmount } from 'dlc-btc-lib/bitcoin-functions'; -interface ProtocolFeeBoxProps { +interface TransactionFormProtocolFeeStackProps { + formType: 'deposit' | 'withdraw' | 'burn'; assetAmount?: number; bitcoinPrice?: number; protocolFeeBasisPoints?: number; + isBitcoinWalletLoading: [boolean, string]; } function calculateProtocolFeeInUSD( @@ -19,29 +21,32 @@ function calculateProtocolFeeInUSD( return result.toNumber().toLocaleString('en-US'); } -export function ProtocolFeeBox({ +export function TransactionFormProtocolFeeStack({ + formType, assetAmount, bitcoinPrice, protocolFeeBasisPoints, -}: ProtocolFeeBoxProps): React.JSX.Element { + isBitcoinWalletLoading, +}: TransactionFormProtocolFeeStackProps): React.JSX.Element | false { + if (isBitcoinWalletLoading[0] || formType === 'burn') return false; return ( - + Protocol Fee {`${ - assetAmount && - protocolFeeBasisPoints && - getFeeAmount(assetAmount, protocolFeeBasisPoints) + assetAmount && protocolFeeBasisPoints + ? getFeeAmount(assetAmount, protocolFeeBasisPoints) + : 0 } BTC`} {' '} @@ -49,10 +54,9 @@ export function ProtocolFeeBox({ {`~ ${ - assetAmount && - bitcoinPrice && - protocolFeeBasisPoints && - calculateProtocolFeeInUSD(assetAmount, bitcoinPrice, protocolFeeBasisPoints) + assetAmount && bitcoinPrice && protocolFeeBasisPoints + ? calculateProtocolFeeInUSD(assetAmount, bitcoinPrice, protocolFeeBasisPoints) + : 0 } $`} diff --git a/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.wallet-information.tsx b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.wallet-information.tsx new file mode 100644 index 00000000..848fdcca --- /dev/null +++ b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.wallet-information.tsx @@ -0,0 +1,36 @@ +import { HStack, Text, keyframes } from '@chakra-ui/react'; + +const borderColorKeyframes = keyframes` + 0% { border-color: rgba(255, 168, 0, 0.11); } + 25% { border-color: rgba(255, 168, 0, 0.3); } + 50% { border-color: rgba(255, 168, 0, 0.5); } + 75% { border-color: rgba(255, 168, 0, 0.7); } + 100% { border-color: rgba(255, 168, 0, 0.1); } +`; + +interface TransactionScreenWalletInformationProps { + isBitcoinWalletLoading: [boolean, string]; +} + +export function TransactionScreenWalletInformation({ + isBitcoinWalletLoading, +}: TransactionScreenWalletInformationProps): React.JSX.Element | false { + if (!isBitcoinWalletLoading[0]) return false; + return ( + + + + {isBitcoinWalletLoading[1]} + + + + ); +} diff --git a/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.warning.tsx b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.warning.tsx new file mode 100644 index 00000000..5197f219 --- /dev/null +++ b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.warning.tsx @@ -0,0 +1,27 @@ +import { HStack, Text } from '@chakra-ui/react'; +import { ValidationError } from '@tanstack/react-form'; + +interface VaultTransactionFormWarningProps { + formErrors: ValidationError[]; +} + +export function VaultTransactionFormWarning({ + formErrors, +}: VaultTransactionFormWarningProps): React.JSX.Element | false { + if (!formErrors.length) return false; + return ( + + + {formErrors.join(', ')} + + + ); +} diff --git a/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screent.transaction-form.transaction-information.tsx b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screent.transaction-form.transaction-information.tsx new file mode 100644 index 00000000..fc3ae77e --- /dev/null +++ b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screent.transaction-form.transaction-information.tsx @@ -0,0 +1,29 @@ +import { HStack, Text } from '@chakra-ui/react'; + +interface TransactionFormWarningProps { + formType: 'deposit' | 'withdraw' | 'burn'; + assetAmount: number; + isBitcoinWalletLoading: [boolean, string]; +} + +export function TransactionFormTransactionInformation({ + formType, + assetAmount, + isBitcoinWalletLoading, +}: TransactionFormWarningProps): React.JSX.Element | false { + if (isBitcoinWalletLoading[0] || formType === 'burn') return false; + return ( + + + Make sure you have {assetAmount} BTC + (fees) + in your Bitcoin Wallet before proceeding to the next step. + + + ); +} diff --git a/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/transaction-screen.transaction-form.tsx b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/transaction-screen.transaction-form.tsx new file mode 100644 index 00000000..4e0d18ee --- /dev/null +++ b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/transaction-screen.transaction-form.tsx @@ -0,0 +1,252 @@ +import { Button, Text, VStack } from '@chakra-ui/react'; +import { RiskBox } from '@components/mint-unmint/components/risk-box/risk-box'; +import { Vault } from '@models/vault'; +import { BitcoinWalletContextState } from '@providers/bitcoin-wallet-context-provider'; +import { useForm } from '@tanstack/react-form'; +import Decimal from 'decimal.js'; + +import { TransactionFormFieldInput } from './components/transaction-screen.transaction-form.input'; +import { TransactionFormInputUSDText } from './components/transaction-screen.transaction-form.input-usd-text'; +import { TransactionFormProtocolFeeStack } from './components/transaction-screen.transaction-form.protocol-fee-box'; +import { TransactionScreenWalletInformation } from './components/transaction-screen.transaction-form.wallet-information'; +import { VaultTransactionFormWarning } from './components/transaction-screen.transaction-form.warning'; +import { TransactionFormTransactionInformation } from './components/transaction-screent.transaction-form.transaction-information'; + +function validateDepositAmount( + value: number, + depositLimit: { minimumDeposit: number; maximumDeposit: number } +): string | undefined { + let error; + + if (!value) { + error = 'Please enter a valid amount of BTC'; + } else if (depositLimit && value < depositLimit.minimumDeposit) { + error = `You can't deposit less than ${depositLimit.minimumDeposit} BTC`; + } else if (depositLimit && value > depositLimit.maximumDeposit) { + error = `You can't deposit more than ${depositLimit.maximumDeposit} BTC`; + } + return error; +} + +function validateWithdrawAmount(value: number, valueMinted: number): string | undefined { + let error; + + if (!value) { + error = 'Please enter a valid amount of dlcBTC'; + } else if (valueMinted && value > valueMinted) { + error = `You can't burn more than ${valueMinted} dlcBTC`; + } + return error; +} + +function validateFormAmount( + value: number, + type: 'deposit' | 'withdraw' | 'burn', + depositLimit?: { minimumDeposit: number; maximumDeposit: number }, + vault?: Vault +): string | undefined { + if (!vault) { + return 'Vault is not available'; + } else if (!depositLimit) { + return 'Deposit limits are not available'; + } + + switch (type) { + case 'deposit': + return validateDepositAmount(value, depositLimit); + case 'withdraw': + return validateWithdrawAmount(value, vault.valueMinted); + case 'burn': + return undefined; + } +} + +function getButtonLabel( + formType: 'deposit' | 'withdraw' | 'burn', + isSubmitting: boolean, + bitcoinWalletContextState: BitcoinWalletContextState +): string { + if (isSubmitting) { + return 'Processing'; + } else if (formType === 'burn') { + return 'Sign Burn Transaction'; + } else { + if (bitcoinWalletContextState === BitcoinWalletContextState.READY) { + switch (formType) { + case 'deposit': + return 'Sign Deposit Transaction'; + case 'withdraw': + return 'Sign Withdraw Transaction'; + } + } else { + return 'Connect Wallet'; + } + } +} + +const formPropertyMap = { + deposit: { + label: 'Deposit BTC', + logo: '/images/logos/bitcoin-logo.svg', + symbol: 'BTC', + color: 'orange.01', + }, + withdraw: { + label: 'Withdraw BTC', + logo: '/images/logos/bitcoin-logo.svg', + symbol: 'BTC', + color: 'orange.01', + }, + burn: { + label: 'Burn dlcBTC', + logo: '/images/logos/dlc-btc-logo.svg', + symbol: 'dlcBTC', + color: 'purple.01', + }, +}; + +interface VaultTransactionFormProps { + type: 'deposit' | 'withdraw' | 'burn'; + bitcoinWalletContextState: BitcoinWalletContextState; + isBitcoinWalletLoading: [boolean, string]; + userEthereumAddressRiskLevel?: string; + isUserEthereumAddressRiskLevelLoading?: boolean; + vault: Vault; + handleButtonClick: (assetValue: number) => Promise; + handleCancelButtonClick: () => void; + currentBitcoinPrice?: number; + depositLimit?: { minimumDeposit: number; maximumDeposit: number } | undefined; +} + +export function VaultTransactionForm({ + type, + bitcoinWalletContextState, + isBitcoinWalletLoading, + userEthereumAddressRiskLevel, + isUserEthereumAddressRiskLevelLoading, + vault, + handleButtonClick, + handleCancelButtonClick, + currentBitcoinPrice, + depositLimit, +}: VaultTransactionFormProps): React.JSX.Element { + const { + Field, + Subscribe, + handleSubmit, + state: { + isSubmitting, + values: { assetAmount }, + }, + } = useForm({ + defaultValues: { + assetAmount: '0.01', + }, + onSubmit: async ({ value }) => { + await handleButtonClick( + type === 'withdraw' + ? new Decimal(vault.valueLocked).minus(vault.valueMinted).toNumber() + : new Decimal(value.assetAmount).toNumber() + ); + }, + validators: { + onChange: ({ value }) => { + return { + fields: { + assetAmount: validateFormAmount( + parseFloat(value.assetAmount), + type, + depositLimit, + vault + ), + }, + }; + }, + }, + }); + + return ( +
{ + e.preventDefault(); + e.stopPropagation(); + await handleSubmit(); + }} + > + + {type !== 'withdraw' && ( + + {field => ( + + + {formPropertyMap[type].label} + + + + + + )} + + )} + + + {isUserEthereumAddressRiskLevelLoading && userEthereumAddressRiskLevel && ( + + )} + + [state.canSubmit, state.isSubmitting]} + children={([canSubmit, isSubmitting]) => ( + + )} + /> + + +
+ ); +} diff --git a/src/app/components/vault-mini/components/vault-information.tsx b/src/app/components/vault-mini/components/vault-information.tsx deleted file mode 100644 index e1044577..00000000 --- a/src/app/components/vault-mini/components/vault-information.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { CheckCircleIcon, CopyIcon } from '@chakra-ui/icons'; -import { Button, Divider, HStack, Image, Text, Tooltip } from '@chakra-ui/react'; -import { useCopyToClipboard } from '@hooks/use-copy-to-clipboard'; -import { VaultState } from 'dlc-btc-lib/models'; -import { truncateAddress } from 'dlc-btc-lib/utilities'; - -const getAssetLogo = (state: VaultState) => { - return [VaultState.FUNDED, VaultState.CLOSING, VaultState.CLOSED].includes(state) - ? '/images/logos/dlc-btc-logo.svg' - : '/images/logos/bitcoin-logo.svg'; -}; - -interface VaultMiniCardInformationProps { - collateral: number; - uuid: string; - state: VaultState; - timestamp: number; -} - -export function VaultMiniCardInformation({ - state, - uuid, - collateral, - timestamp, -}: VaultMiniCardInformationProps): React.JSX.Element { - const date = new Date(timestamp * 1000).toLocaleDateString('en-US'); - const { hasCopied, copyToClipboard } = useCopyToClipboard(); - - return ( - - - {'Icon'} - - {collateral} - - - - - - - - - {truncateAddress(uuid)} - - - - {date} - - - ); -} diff --git a/src/app/components/vault-mini/components/vault-mini-card.layout.tsx b/src/app/components/vault-mini/components/vault-mini-card.layout.tsx deleted file mode 100644 index 9fc942bb..00000000 --- a/src/app/components/vault-mini/components/vault-mini-card.layout.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { ReactNode } from 'react'; - -import { VStack } from '@chakra-ui/react'; - -interface VaultMiniCardLayoutProps { - children: ReactNode; -} - -export function VaultMiniCardLayout({ children }: VaultMiniCardLayoutProps): React.JSX.Element { - return ( - - {children} - - ); -} diff --git a/src/app/components/vault-mini/vault-mini-card.tsx b/src/app/components/vault-mini/vault-mini-card.tsx deleted file mode 100644 index 6a1f7a6e..00000000 --- a/src/app/components/vault-mini/vault-mini-card.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { Vault } from '@models/vault'; - -import { VaultMiniCardInformation } from './components/vault-information'; -import { VaultMiniCardLayout } from './components/vault-mini-card.layout'; - -interface VaultMiniCardProps { - vault: Vault; -} - -export function VaultMiniCard({ vault }: VaultMiniCardProps): React.JSX.Element { - 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 index 21351ae2..11fc8bca 100644 --- a/src/app/components/vault/components/vault.detaills/vault.details.tsx +++ b/src/app/components/vault/components/vault.detaills/vault.details.tsx @@ -16,6 +16,7 @@ interface VaultDetailsProps { isVaultExpanded: boolean; vaultFundingTX?: string; vaultWithdrawDepositTX?: string; + variant?: 'select' | 'selected'; } export function VaultDetails({ @@ -26,6 +27,7 @@ export function VaultDetails({ vaultTotalLockedValue, vaultTotalMintedValue, isVaultExpanded, + variant, }: VaultDetailsProps): React.JSX.Element { const navigate = useNavigate(); const dispatch = useDispatch(); @@ -56,7 +58,7 @@ export function VaultDetails({ vaultFundingTX={vaultFundingTX} vaultWithdrawDepositTX={vaultWithdrawDepositTX} /> - {vaultState !== VaultState.PENDING && ( + {(vaultState !== VaultState.PENDING || variant !== 'selected') && ( void; - variant?: 'select'; + variant?: 'select' | 'selected'; } export function VaultActionButton({ 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 index 6983d49b..196600f8 100644 --- 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 @@ -14,12 +14,12 @@ export function VaultAssetInformationStack({ 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 index b081dc82..192b5342 100644 --- 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 @@ -8,7 +8,7 @@ interface VaultMainStackProps { vaultTotalMintedValue: number; isVaultExpanded: boolean; handleButtonClick: () => void; - variant?: 'select'; + variant?: 'select' | 'selected'; } export function VaultMainStack({ diff --git a/src/app/components/vault/components/vault.timeline/components/vault.timeline.transaction-form/vault.timeline.transaction-form.tsx b/src/app/components/vault/components/vault.timeline/components/vault.timeline.transaction-form/vault.timeline.transaction-form.tsx deleted file mode 100644 index de67aea5..00000000 --- a/src/app/components/vault/components/vault.timeline/components/vault.timeline.transaction-form/vault.timeline.transaction-form.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import { HStack, Image, Input, Text, VStack } from '@chakra-ui/react'; -import { useForm } from '@tanstack/react-form'; - -interface VaultTransactionFormProps { - label: string; - assetLogo: string; - currentBitcoinPrice: number; -} - -export function VaultTransactionForm({ - label, - assetLogo, - currentBitcoinPrice, -}: VaultTransactionFormProps): React.JSX.Element { - const form = useForm({ - defaultValues: { - assetAmount: 0.01, - }, - onSubmit: async ({ value }) => { - // Do something with form data - console.log(value); - }, - }); - - return ( -
{ - e.preventDefault(); - e.stopPropagation(); - form.handleSubmit(); - }} - > - - {field => ( - - - {label} - - - - {'Asset - field.handleChange(e.target.valueAsNumber)} - onWheel={(e: React.WheelEvent) => - (e.target as HTMLInputElement).blur() - } - /> - - - dlcBTC - - - - {`~ $${Math.floor(field.state.value * currentBitcoinPrice).toLocaleString('en-US')} USD`} - - - )} - -
- ); -} diff --git a/src/app/components/vault/vault.tsx b/src/app/components/vault/vault.tsx index 0b908cd2..9a5ee4c5 100644 --- a/src/app/components/vault/vault.tsx +++ b/src/app/components/vault/vault.tsx @@ -10,11 +10,10 @@ 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'; -import { VaultTransactionForm } from './components/vault.timeline/components/vault.timeline.transaction-form/vault.timeline.transaction-form'; interface VaultProps { vault: VaultModel; - variant?: 'select'; + variant?: 'select' | 'selected'; } export function Vault({ vault, variant }: VaultProps): React.JSX.Element { @@ -23,7 +22,9 @@ export function Vault({ vault, variant }: VaultProps): React.JSX.Element { function handleMainButtonClick() { if (variant === 'select') { - dispatch(mintUnmintActions.setUnmintStep([0, vault.uuid])); + vault.valueLocked === vault.valueMinted + ? dispatch(mintUnmintActions.setUnmintStep([0, vault.uuid])) + : dispatch(mintUnmintActions.setUnmintStep([1, vault.uuid])); } else { setIsVaultExpanded(!isVaultExpanded); } @@ -52,11 +53,6 @@ export function Vault({ vault, variant }: VaultProps): React.JSX.Element { vaultFundingTX={vault.fundingTX} vaultWithdrawDepositTX={vault.withdrawDepositTX} /> - ); diff --git a/src/app/hooks/use-copy-to-clipboard.ts b/src/app/hooks/use-copy-to-clipboard.ts deleted file mode 100644 index d63c8ec1..00000000 --- a/src/app/hooks/use-copy-to-clipboard.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { useState } from 'react'; - -interface UseCopyToClipboardReturnType { - hasCopied: boolean; - copyToClipboard: (text: string) => Promise; -} - -export function useCopyToClipboard(): UseCopyToClipboardReturnType { - const [hasCopied, setHasCopied] = useState(false); - - async function copyToClipboard(text: string) { - await navigator.clipboard.writeText(text); - setHasCopied(true); - setTimeout(() => setHasCopied(false), 2500); - } - - return { - hasCopied, - copyToClipboard, - }; -} diff --git a/src/styles/app-theme.ts b/src/styles/app-theme.ts index b57d27d8..8e5a61ec 100644 --- a/src/styles/app-theme.ts +++ b/src/styles/app-theme.ts @@ -1,4 +1,4 @@ -import { border, extendTheme } from '@chakra-ui/react'; +import { extendTheme } from '@chakra-ui/react'; // Supports weights 100-900 import '@fontsource-variable/onest'; @@ -20,20 +20,6 @@ export const appTheme = extendTheme({ Text: textTheme, Tabs: tabsTheme, Divider: dividerTheme, - Input: { - baseStyle: { - field: { - fontWeight: 'bold', - color: 'white.01', - borderColor: 'red', - border: '35px solid', - focusBorderColor: 'accent.lightBlue.01', - _focus: { - borderColor: 'black', - }, - }, - }, - }, Progress: { baseStyle: { track: { @@ -60,7 +46,7 @@ export const appTheme = extendTheme({ '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.container.01': 'rgba(23, 24, 29, 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)', From 19ffee2c5bd5a426f6537f56eefdda822bfd01ae Mon Sep 17 00:00:00 2001 From: Polybius93 Date: Tue, 24 Sep 2024 16:16:15 +0200 Subject: [PATCH 08/10] wip: updating form --- .../burn-transaction-screen.tsx | 5 +- .../deposit-transaction-screen.tsx | 3 +- .../mint-unmint/components/mint/mint.tsx | 6 +- .../unmint/components/withdraw-screen.tsx | 6 +- .../mint-unmint/components/unmint/unmint.tsx | 10 +- ...n-screen.transaction-form.field-input.tsx} | 0 ...on-screen.transaction-form.input-field.tsx | 76 +++++++ ...ion-form.progress-stack.burn-variant-a.tsx | 35 +++ ...ion-form.progress-stack.burn-variant-b.tsx | 28 +++ ...ion-form.progress-stack.burn-variant-c.tsx | 28 +++ ...ion-form.progress-stack.mint-variant-a.tsx | 43 ++++ ...ion-form.progress-stack.mint-variant-b.tsx | 28 +++ ...ion-form.progress-stack.mint-variant-c.tsx | 28 +++ ...n.transaction-form.progress-step-stack.tsx | 37 +++ ...reen.transaction-form.protocol-fee-box.tsx | 7 +- ...ansaction-form.transaction-information.tsx | 2 +- .../transaction-screen.transaction-form.tsx | 211 +++++++++--------- .../vault-vertical-progress-bar.tsx | 67 +++--- ...ult.main-stack.asset-information-stack.tsx | 4 +- src/shared/models/form.models.ts | 15 ++ 20 files changed, 487 insertions(+), 152 deletions(-) rename src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/{transaction-screen.transaction-form.input.tsx => transaction-screen.transaction-form.field-input.tsx} (100%) create mode 100644 src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.input-field.tsx create mode 100644 src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.progress-stack/components/transaction-screen.transaction-form.progress-stack.burn-variant-a.tsx create mode 100644 src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.progress-stack/components/transaction-screen.transaction-form.progress-stack.burn-variant-b.tsx create mode 100644 src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.progress-stack/components/transaction-screen.transaction-form.progress-stack.burn-variant-c.tsx create mode 100644 src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.progress-stack/components/transaction-screen.transaction-form.progress-stack.mint-variant-a.tsx create mode 100644 src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.progress-stack/components/transaction-screen.transaction-form.progress-stack.mint-variant-b.tsx create mode 100644 src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.progress-stack/components/transaction-screen.transaction-form.progress-stack.mint-variant-c.tsx create mode 100644 src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.progress-step-stack.tsx create mode 100644 src/shared/models/form.models.ts diff --git a/src/app/components/mint-unmint/components/burn-transaction-screen/burn-transaction-screen.tsx b/src/app/components/mint-unmint/components/burn-transaction-screen/burn-transaction-screen.tsx index 1ca096ae..ba1529af 100644 --- a/src/app/components/mint-unmint/components/burn-transaction-screen/burn-transaction-screen.tsx +++ b/src/app/components/mint-unmint/components/burn-transaction-screen/burn-transaction-screen.tsx @@ -50,7 +50,9 @@ export function BurnTokenTransactionForm({ async function handleButtonClick(withdrawAmount: number): Promise { if (!currentVault) return; + console.log('currentVault', currentVault); + console.log('burnAmount', withdrawAmount); try { const currentRisk = await fetchUserEthereumAddressRiskLevel(); if (currentRisk === 'High') throw new Error('Risk Level is too high'); @@ -95,7 +97,8 @@ export function BurnTokenTransactionForm({ {[0].includes(mintStep[0]) && } - {[1].includes(mintStep[0]) && ( + {[1, 2].includes(mintStep[0]) && ( )} - {[2].includes(mintStep[0]) && ( + {/* {[2].includes(mintStep[0]) && ( - )} + )} */}
); diff --git a/src/app/components/mint-unmint/components/unmint/components/withdraw-screen.tsx b/src/app/components/mint-unmint/components/unmint/components/withdraw-screen.tsx index d8d6c181..ec394652 100644 --- a/src/app/components/mint-unmint/components/unmint/components/withdraw-screen.tsx +++ b/src/app/components/mint-unmint/components/unmint/components/withdraw-screen.tsx @@ -35,6 +35,8 @@ export function WithdrawScreen({ const currentVault = allVaults.find(vault => vault.uuid === unmintStep[1]); async function handleWithdraw(withdrawAmount: number): Promise { + console.log('currentVault', currentVault); + console.log('withdrawAmount', withdrawAmount); if (currentVault) { try { await handleSignWithdrawTransaction(currentVault.uuid, withdrawAmount); @@ -60,6 +62,7 @@ export function WithdrawScreen({ } async function handleButtonClick(assetAmount: number) { + console.log('assetAmount', assetAmount); bitcoinWalletContextState === BitcoinWalletContextState.READY ? await handleWithdraw(assetAmount) : handleConnect(); @@ -70,7 +73,8 @@ export function WithdrawScreen({ )} - {[1].includes(unmintStep[0]) && ( + {[1, 2].includes(unmintStep[0]) && ( )} - {[2].includes(unmintStep[0]) && ( - - )} ); diff --git a/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.input.tsx b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.field-input.tsx similarity index 100% rename from src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.input.tsx rename to src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.field-input.tsx diff --git a/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.input-field.tsx b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.input-field.tsx new file mode 100644 index 00000000..48f2b66b --- /dev/null +++ b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.input-field.tsx @@ -0,0 +1,76 @@ +import { HStack, Text, VStack } from '@chakra-ui/react'; +import { VaultVerticalProgressBar } from '@components/vault/components/vault-vertical-progress-bar'; +import { FormApi, ReactFormApi } from '@tanstack/react-form'; + +import { TransactionFormFieldInput } from './transaction-screen.transaction-form.field-input'; +import { TransactionFormInputUSDText } from './transaction-screen.transaction-form.input-usd-text'; +import { TransactionFormProgressStack } from './transaction-screen.transaction-form.progress-step-stack'; +import { VaultTransactionFormWarning } from './transaction-screen.transaction-form.warning'; + +export const formPropertyMap = { + mint: { + label: 'Deposit BTC', + logo: '/images/logos/bitcoin-logo.svg', + symbol: 'BTC', + color: 'orange.01', + }, + burn: { + label: 'Burn dlcBTC', + logo: '/images/logos/dlc-btc-logo.svg', + symbol: 'dlcBTC', + color: 'purple.01', + }, +}; + +interface TransactionFormInputFieldProps { + formAPI: FormApi< + { + assetAmount: string; + }, + undefined + > & + ReactFormApi< + { + assetAmount: string; + }, + undefined + >; + formType: 'mint' | 'burn'; + currentBitcoinPrice: number; +} + +export function TransactionFormInputField({ + formAPI, + currentBitcoinPrice, + formType, +}: TransactionFormInputFieldProps): React.JSX.Element { + return ( + + {field => ( + + + {formPropertyMap[formType].label} + + + + + + )} + + ); +} diff --git a/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.progress-stack/components/transaction-screen.transaction-form.progress-stack.burn-variant-a.tsx b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.progress-stack/components/transaction-screen.transaction-form.progress-stack.burn-variant-a.tsx new file mode 100644 index 00000000..07eeaf20 --- /dev/null +++ b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.progress-stack/components/transaction-screen.transaction-form.progress-stack.burn-variant-a.tsx @@ -0,0 +1,35 @@ +import { HStack, VStack } from '@chakra-ui/react'; +import { VaultVerticalProgressBar } from '@components/vault/components/vault-vertical-progress-bar'; +import { TransactionFormAPI } from '@models/form.models'; + +import { TransactionFormInputField } from '../../transaction-screen.transaction-form.input-field'; +import { TransactionFormProgressStack } from '../../transaction-screen.transaction-form.progress-step-stack'; + +interface TransactionFormProgressStackBurnVariantAProps { + formAPI: TransactionFormAPI; + currentBitcoinPrice: number; +} + +export function TransactionFormProgressStackBurnVariantA({ + formAPI, + currentBitcoinPrice, +}: TransactionFormProgressStackBurnVariantAProps): React.JSX.Element { + return ( + + + + + + + + ); +} diff --git a/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.progress-stack/components/transaction-screen.transaction-form.progress-stack.burn-variant-b.tsx b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.progress-stack/components/transaction-screen.transaction-form.progress-stack.burn-variant-b.tsx new file mode 100644 index 00000000..5f694fe3 --- /dev/null +++ b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.progress-stack/components/transaction-screen.transaction-form.progress-stack.burn-variant-b.tsx @@ -0,0 +1,28 @@ +import { HStack, VStack } from '@chakra-ui/react'; +import { VaultVerticalProgressBar } from '@components/vault/components/vault-vertical-progress-bar'; + +import { TransactionFormProgressStack } from '../../transaction-screen.transaction-form.progress-step-stack'; + +interface TransactionFormProgressStackMintVariantAProps {} + +export function TransactionFormProgressStackBurnVariantB(): React.JSX.Element { + return ( + + + + + + + + ); +} diff --git a/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.progress-stack/components/transaction-screen.transaction-form.progress-stack.burn-variant-c.tsx b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.progress-stack/components/transaction-screen.transaction-form.progress-stack.burn-variant-c.tsx new file mode 100644 index 00000000..1514f365 --- /dev/null +++ b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.progress-stack/components/transaction-screen.transaction-form.progress-stack.burn-variant-c.tsx @@ -0,0 +1,28 @@ +import { HStack, VStack } from '@chakra-ui/react'; +import { VaultVerticalProgressBar } from '@components/vault/components/vault-vertical-progress-bar'; + +import { TransactionFormProgressStack } from '../../transaction-screen.transaction-form.progress-step-stack'; + +interface TransactionFormProgressStackMintVariantCProps {} + +export function TransactionFormProgressStackBurnVariantC(): React.JSX.Element { + return ( + + + + + + + + ); +} diff --git a/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.progress-stack/components/transaction-screen.transaction-form.progress-stack.mint-variant-a.tsx b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.progress-stack/components/transaction-screen.transaction-form.progress-stack.mint-variant-a.tsx new file mode 100644 index 00000000..ad7faf03 --- /dev/null +++ b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.progress-stack/components/transaction-screen.transaction-form.progress-stack.mint-variant-a.tsx @@ -0,0 +1,43 @@ +import { HStack, VStack } from '@chakra-ui/react'; +import { VaultVerticalProgressBar } from '@components/vault/components/vault-vertical-progress-bar'; +import { TransactionFormAPI } from '@models/form.models'; + +import { TransactionFormInputField } from '../../transaction-screen.transaction-form.input-field'; +import { TransactionFormProgressStack } from '../../transaction-screen.transaction-form.progress-step-stack'; + +interface TransactionFormProgressStackMintVariantAProps { + formAPI: TransactionFormAPI; + currentBitcoinPrice: number; +} + +export function TransactionFormProgressStackMintVariantA({ + formAPI, + currentBitcoinPrice, +}: TransactionFormProgressStackMintVariantAProps): React.JSX.Element { + return ( + + + + + + + + ); +} diff --git a/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.progress-stack/components/transaction-screen.transaction-form.progress-stack.mint-variant-b.tsx b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.progress-stack/components/transaction-screen.transaction-form.progress-stack.mint-variant-b.tsx new file mode 100644 index 00000000..0e2f76d6 --- /dev/null +++ b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.progress-stack/components/transaction-screen.transaction-form.progress-stack.mint-variant-b.tsx @@ -0,0 +1,28 @@ +import { HStack, VStack } from '@chakra-ui/react'; +import { VaultVerticalProgressBar } from '@components/vault/components/vault-vertical-progress-bar'; + +import { TransactionFormProgressStack } from '../../transaction-screen.transaction-form.progress-step-stack'; + +interface TransactionFormProgressStackMintVariantBProps {} + +export function TransactionFormProgressStackMintVariantB(): React.JSX.Element { + return ( + + + + + + + + ); +} diff --git a/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.progress-stack/components/transaction-screen.transaction-form.progress-stack.mint-variant-c.tsx b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.progress-stack/components/transaction-screen.transaction-form.progress-stack.mint-variant-c.tsx new file mode 100644 index 00000000..1c270702 --- /dev/null +++ b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.progress-stack/components/transaction-screen.transaction-form.progress-stack.mint-variant-c.tsx @@ -0,0 +1,28 @@ +import { HStack, VStack } from '@chakra-ui/react'; +import { VaultVerticalProgressBar } from '@components/vault/components/vault-vertical-progress-bar'; + +import { TransactionFormProgressStack } from '../../transaction-screen.transaction-form.progress-step-stack'; + +interface TransactionFormProgressStackMintVariantCProps {} + +export function TransactionFormProgressStackMintVariantC(): React.JSX.Element { + return ( + + + + + + + + ); +} diff --git a/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.progress-step-stack.tsx b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.progress-step-stack.tsx new file mode 100644 index 00000000..762e19a5 --- /dev/null +++ b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.progress-step-stack.tsx @@ -0,0 +1,37 @@ +import { HStack, Image, Stack, Text, VStack } from '@chakra-ui/react'; + +interface TransactionFormProgressStackProps { + label: string; + assetLogo: string; + assetSymbol: string; + isActive: boolean; +} + +export function TransactionFormProgressStack({ + label, + assetLogo, + assetSymbol, + isActive, +}: TransactionFormProgressStackProps): React.JSX.Element { + return ( + + + {'Asset + + {label} + + + + {assetSymbol} + + + ); +} diff --git a/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.protocol-fee-box.tsx b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.protocol-fee-box.tsx index 4814767e..4bd9a718 100644 --- a/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.protocol-fee-box.tsx +++ b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.protocol-fee-box.tsx @@ -1,9 +1,11 @@ +import { useEffect } from 'react'; + import { HStack, Text, VStack } from '@chakra-ui/react'; import Decimal from 'decimal.js'; import { getFeeAmount } from 'dlc-btc-lib/bitcoin-functions'; interface TransactionFormProtocolFeeStackProps { - formType: 'deposit' | 'withdraw' | 'burn'; + formType: 'mint' | 'burn'; assetAmount?: number; bitcoinPrice?: number; protocolFeeBasisPoints?: number; @@ -28,6 +30,9 @@ export function TransactionFormProtocolFeeStack({ protocolFeeBasisPoints, isBitcoinWalletLoading, }: TransactionFormProtocolFeeStackProps): React.JSX.Element | false { + useEffect(() => { + console.log('assetAmount:', assetAmount); + }, [assetAmount]); if (isBitcoinWalletLoading[0] || formType === 'burn') return false; return ( + ); + case 2: + return ; + case 3: + return ; + } + break; + case 'burn': + switch (currentStep) { + case 0: + return ( + + ); + case 1: + return ; + case 2: + return ; + } + break; + } +} interface VaultTransactionFormProps { - type: 'deposit' | 'withdraw' | 'burn'; + flow: 'mint' | 'burn'; + currentStep: number; bitcoinWalletContextState: BitcoinWalletContextState; isBitcoinWalletLoading: [boolean, string]; userEthereumAddressRiskLevel?: string; @@ -119,7 +142,8 @@ interface VaultTransactionFormProps { } export function VaultTransactionForm({ - type, + flow, + currentStep, bitcoinWalletContextState, isBitcoinWalletLoading, userEthereumAddressRiskLevel, @@ -130,32 +154,33 @@ export function VaultTransactionForm({ currentBitcoinPrice, depositLimit, }: VaultTransactionFormProps): React.JSX.Element { - const { - Field, - Subscribe, - handleSubmit, - state: { - isSubmitting, - values: { assetAmount }, - }, - } = useForm({ + const [currentFieldValue, setCurrentFieldValue] = useState(depositLimit?.minimumDeposit!); + const form = useForm({ defaultValues: { - assetAmount: '0.01', + assetAmount: depositLimit?.minimumDeposit!.toString()!, }, onSubmit: async ({ value }) => { - await handleButtonClick( - type === 'withdraw' - ? new Decimal(vault.valueLocked).minus(vault.valueMinted).toNumber() - : new Decimal(value.assetAmount).toNumber() - ); + const assetAmount = new Decimal(value.assetAmount); + const burnAmount = new Decimal(vault.valueLocked).minus(vault.valueMinted).toNumber(); + + if (flow === 'burn') { + if (currentStep === 0) { + await handleButtonClick(assetAmount.toNumber()); + } else { + await handleButtonClick(burnAmount); + } + } else { + await handleButtonClick(assetAmount.toNumber()); + } }, validators: { onChange: ({ value }) => { + setCurrentFieldValue(new Decimal(value.assetAmount).toNumber()); return { fields: { assetAmount: validateFormAmount( parseFloat(value.assetAmount), - type, + flow, depositLimit, vault ), @@ -170,49 +195,27 @@ export function VaultTransactionForm({ onSubmit={async e => { e.preventDefault(); e.stopPropagation(); - await handleSubmit(); + await form.handleSubmit(); }} > - - {type !== 'withdraw' && ( - - {field => ( - - - {formPropertyMap[type].label} - - - - - - )} - - )} + + {getTransactionProgressStack(flow, currentStep, form, currentBitcoinPrice!)} {isUserEthereumAddressRiskLevelLoading && userEthereumAddressRiskLevel && ( @@ -222,7 +225,7 @@ export function VaultTransactionForm({ /> )} - [state.canSubmit, state.isSubmitting]} children={([canSubmit, isSubmitting]) => ( )} /> - diff --git a/src/app/components/vault/components/vault-vertical-progress-bar.tsx b/src/app/components/vault/components/vault-vertical-progress-bar.tsx index 71fce6e6..546faf58 100644 --- a/src/app/components/vault/components/vault-vertical-progress-bar.tsx +++ b/src/app/components/vault/components/vault-vertical-progress-bar.tsx @@ -1,42 +1,47 @@ -import { Divider, Image, Spinner, VStack } from '@chakra-ui/react'; - -export enum Status { - Completed = 'completed', - Current = 'current', - Inactive = 'inactive', -} +import { Divider, HStack, Image, Spinner, Stack, VStack } from '@chakra-ui/react'; interface VaultVerticalProgressBarProps { - statusA: Status; - statusB: Status; + currentStep: number; } +const Status = { + ACTIVE: 'ACTIVE', + COMPLETED: 'COMPLETED', + INACTIVE: 'INACTIVE', +}; -const StatusIndicator = { - status: Status.Inactive, +function getStatus(currentStep: number, stepIndex: number): string { + if (currentStep === stepIndex) { + return Status.ACTIVE; + } else if (currentStep > stepIndex) { + return Status.COMPLETED; + } else { + return Status.INACTIVE; + } +} - getComponent: (status: Status) => { - switch (status) { - case Status.Completed: - return {'Icon'}; - case Status.Current: - return ; - case Status.Inactive: - return {'Inactive; // Customize spinner as needed - default: - return null; - } - }, -}; +function getComponent(status: string): React.JSX.Element | false { + switch (status) { + case Status.ACTIVE: + return ; + case Status.COMPLETED: + return {'Icon'}; + case Status.INACTIVE: + return {'Inactive; + default: + return false; + } +} export function VaultVerticalProgressBar({ - statusA, - statusB, + currentStep, }: VaultVerticalProgressBarProps): React.JSX.Element { return ( - - {StatusIndicator.getComponent(statusA)} - - {StatusIndicator.getComponent(statusB)} - + + + {getComponent(getStatus(currentStep, 0))} + + {getComponent(getStatus(currentStep, 1))} + + ); } 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 index 196600f8..813f466a 100644 --- 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 @@ -15,12 +15,12 @@ export function VaultAssetInformationStack({ ); diff --git a/src/shared/models/form.models.ts b/src/shared/models/form.models.ts new file mode 100644 index 00000000..92b0dcd2 --- /dev/null +++ b/src/shared/models/form.models.ts @@ -0,0 +1,15 @@ +import { FormApi, ReactFormApi } from '@tanstack/react-form'; + +export interface TransactionFormAPI + extends FormApi< + { + assetAmount: string; + }, + undefined + >, + ReactFormApi< + { + assetAmount: string; + }, + undefined + > {} From d1e566efc844c16b4a5702020842d13b8ba2b0c7 Mon Sep 17 00:00:00 2001 From: Polybius93 Date: Wed, 25 Sep 2024 12:56:23 +0200 Subject: [PATCH 09/10] feat: add transaction timeline, modify observer, add buttons --- src/app/app.tsx | 12 +- .../burn-transaction-screen.tsx | 5 +- .../deposit-transaction-screen.tsx | 2 +- .../components/mint-unmint.layout.tsx | 4 +- .../mint-unmint/components/mint/mint.tsx | 16 +- .../transaction-summary-preview-card.tsx | 44 ---- .../transaction-summary.tsx | 129 ------------ .../components/unmint-vault-selector.tsx | 2 +- .../unmint/components/withdraw-screen.tsx | 5 +- .../mint-unmint/components/unmint/unmint.tsx | 1 - .../components/walkthrough-header.tsx | 2 +- .../components/walkthrough.layout.tsx | 2 +- .../modals/components/modal-container.tsx | 8 +- .../modals/components/modal.vault.layout.tsx | 35 ++++ .../successful-flow-modal.tsx | 45 ++++- .../components/my-vaults-small.layout.tsx | 4 +- .../my-vaults-small/my-vaults-small.tsx | 2 +- .../components/my-vaults-large.layout.tsx | 2 +- src/app/components/tab-button/tab-button.tsx | 7 +- ...on-screen.transaction-form.field-input.tsx | 2 +- ...on-screen.transaction-form.input-field.tsx | 76 ------- ...nsaction-screen.transaction-form.input.tsx | 114 +++++++---- ...transaction-form.navigate-button-group.tsx | 41 ++++ ...creen.transaction-form.navigate-button.tsx | 26 +++ ...ion-form.progress-stack.burn-variant-a.tsx | 15 +- ...ion-form.progress-stack.burn-variant-b.tsx | 14 +- ...ion-form.progress-stack.burn-variant-c.tsx | 14 +- ...ion-form.progress-stack.mint-variant-a.tsx | 8 +- ...ion-form.progress-stack.mint-variant-b.tsx | 18 +- ...ion-form.progress-stack.mint-variant-c.tsx | 18 +- ...n.transaction-form.progress-step-stack.tsx | 6 +- ...reen.transaction-form.protocol-fee-box.tsx | 27 +-- ...n.transaction-form.submit-button-group.tsx | 84 ++++++++ ...ansaction-form.transaction-information.tsx | 19 +- .../transaction-screen.transaction-form.tsx | 190 ++++++++---------- .../vault-vertical-progress-bar.tsx | 11 +- .../vault.details.button-group.button.tsx | 2 +- .../vault.details.button-group.tsx | 28 ++- .../vault.detaills/vault.details.tsx | 28 ++- .../vault/components/vault.progress-bar.tsx | 2 + src/app/components/vault/vault.tsx | 8 +- src/app/hooks/use-ethereum-observer.ts | 150 ++++++++------ src/app/store/slices/modal/modal.slice.ts | 13 +- src/styles/button-theme.ts | 4 +- src/styles/menu-theme.ts | 8 +- 45 files changed, 677 insertions(+), 576 deletions(-) delete mode 100644 src/app/components/mint-unmint/components/transaction-summary/components/transaction-summary-preview-card.tsx delete mode 100644 src/app/components/mint-unmint/components/transaction-summary/transaction-summary.tsx create mode 100644 src/app/components/modals/components/modal.vault.layout.tsx delete mode 100644 src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.input-field.tsx create mode 100644 src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.navigate-button-group.tsx create mode 100644 src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.navigate-button.tsx create mode 100644 src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.submit-button-group.tsx diff --git a/src/app/app.tsx b/src/app/app.tsx index af4b38fd..d2f0d892 100644 --- a/src/app/app.tsx +++ b/src/app/app.tsx @@ -29,9 +29,9 @@ export function App(): React.JSX.Element { - - - + + + @@ -51,9 +51,9 @@ export function App(): React.JSX.Element { - - - + + + diff --git a/src/app/components/mint-unmint/components/burn-transaction-screen/burn-transaction-screen.tsx b/src/app/components/mint-unmint/components/burn-transaction-screen/burn-transaction-screen.tsx index ba1529af..ba744f84 100644 --- a/src/app/components/mint-unmint/components/burn-transaction-screen/burn-transaction-screen.tsx +++ b/src/app/components/mint-unmint/components/burn-transaction-screen/burn-transaction-screen.tsx @@ -50,9 +50,6 @@ export function BurnTokenTransactionForm({ async function handleButtonClick(withdrawAmount: number): Promise { if (!currentVault) return; - console.log('currentVault', currentVault); - - console.log('burnAmount', withdrawAmount); try { const currentRisk = await fetchUserEthereumAddressRiskLevel(); if (currentRisk === 'High') throw new Error('Risk Level is too high'); @@ -94,7 +91,7 @@ export function BurnTokenTransactionForm({ return ( - + - + state.mintunmint); const { risk, fetchUserAddressRisk, isLoading } = useRisk(); @@ -37,15 +32,6 @@ export function Mint(): React.JSX.Element { isUserEthereumAddressRiskLevelLoading={isLoading} /> )} - {/* {[2].includes(mintStep[0]) && ( - - )} */} ); diff --git a/src/app/components/mint-unmint/components/transaction-summary/components/transaction-summary-preview-card.tsx b/src/app/components/mint-unmint/components/transaction-summary/components/transaction-summary-preview-card.tsx deleted file mode 100644 index adbaae36..00000000 --- a/src/app/components/mint-unmint/components/transaction-summary/components/transaction-summary-preview-card.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { HStack, Image, Text, VStack } from '@chakra-ui/react'; - -const blockchainPreviewCardMap = { - ethereum: { - tokenName: 'dlcBTC', - path: '/images/logos/dlc-btc-logo.svg', - }, - bitcoin: { - tokenName: 'BTC', - path: '/images/logos/bitcoin-logo.svg', - }, -}; - -interface TransactionSummaryPreviewCardProps { - blockchain: 'ethereum' | 'bitcoin'; - assetAmount?: number; -} - -export function TransactionSummaryPreviewCard({ - blockchain, - assetAmount, -}: TransactionSummaryPreviewCardProps): React.JSX.Element { - return ( - - - {'dlcBTC'} - - {assetAmount} {blockchainPreviewCardMap[blockchain].tokenName} - - - - ); -} diff --git a/src/app/components/mint-unmint/components/transaction-summary/transaction-summary.tsx b/src/app/components/mint-unmint/components/transaction-summary/transaction-summary.tsx deleted file mode 100644 index d333ceac..00000000 --- a/src/app/components/mint-unmint/components/transaction-summary/transaction-summary.tsx +++ /dev/null @@ -1,129 +0,0 @@ -import { useContext } from 'react'; -import { useDispatch } from 'react-redux'; -import { useNavigate } from 'react-router-dom'; - -import { Button, HStack, Spinner, Stack, Text, VStack } from '@chakra-ui/react'; -import { Vault } from '@components/vault/vault'; -import { VaultContext } from '@providers/vault-context-provider'; -import { mintUnmintActions } from '@store/slices/mintunmint/mintunmint.actions'; -import Decimal from 'decimal.js'; - -import { TransactionSummaryPreviewCard } from './components/transaction-summary-preview-card'; - -interface FlowPropertyMap { - [key: string]: { - [key: number]: { - title: string; - subtitle?: string; - }; - }; -} - -const flowPropertyMap: FlowPropertyMap = { - mint: { - 2: { title: 'a) Locking BTC in progress', subtitle: 'Minting dlcBTC' }, - 3: { title: 'Vault Updated' }, - }, - unmint: { - 2: { - title: 'a) Withdrawing from Vault in progress', - subtitle: 'BTC is being unlocked', - }, - 3: { title: 'Vault Updated' }, - }, -}; - -interface TransactionSummaryProps { - currentStep: [number, string]; - depositAmount?: number; - flow: 'mint' | 'unmint'; - blockchain: 'ethereum' | 'bitcoin'; - width: string; - handleClose?: () => void; -} - -export function TransactionSummary({ - depositAmount, - currentStep, - flow, - blockchain, - width, - handleClose, -}: TransactionSummaryProps): React.JSX.Element { - const navigate = useNavigate(); - const dispatch = useDispatch(); - - const { allVaults } = useContext(VaultContext); - - const currentVault = allVaults.find(vault => vault.uuid === currentStep[1]); - - return ( - - - {currentStep[0] === 2 && } - {flowPropertyMap[flow][currentStep[0]].title}: - - {currentVault && ( - <> - - {currentStep[0] === 2 && ( - <> - - b) {flowPropertyMap[flow][currentStep[0]].subtitle}: - - - - )} - - )} - - - View vault statuses in the My Vaults tab. - - - - - {((flow === 'mint' && currentStep[0] === 2) || - (flow === 'unmint' && currentStep[0] === 2)) && ( - - )} - - - ); -} 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 a10d8f65..63ae7a42 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 @@ -42,7 +42,7 @@ export function UnmintVaultSelector({ Select vault to withdraw Bitcoin: - + diff --git a/src/app/components/mint-unmint/components/unmint/components/withdraw-screen.tsx b/src/app/components/mint-unmint/components/unmint/components/withdraw-screen.tsx index ec394652..5de56280 100644 --- a/src/app/components/mint-unmint/components/unmint/components/withdraw-screen.tsx +++ b/src/app/components/mint-unmint/components/unmint/components/withdraw-screen.tsx @@ -35,8 +35,6 @@ export function WithdrawScreen({ const currentVault = allVaults.find(vault => vault.uuid === unmintStep[1]); async function handleWithdraw(withdrawAmount: number): Promise { - console.log('currentVault', currentVault); - console.log('withdrawAmount', withdrawAmount); if (currentVault) { try { await handleSignWithdrawTransaction(currentVault.uuid, withdrawAmount); @@ -62,7 +60,6 @@ export function WithdrawScreen({ } async function handleButtonClick(assetAmount: number) { - console.log('assetAmount', assetAmount); bitcoinWalletContextState === BitcoinWalletContextState.READY ? await handleWithdraw(assetAmount) : handleConnect(); @@ -70,7 +67,7 @@ export function WithdrawScreen({ return ( - + + Step {currentStep !== undefined && currentStep + 1} diff --git a/src/app/components/mint-unmint/components/walkthrough/components/walkthrough.layout.tsx b/src/app/components/mint-unmint/components/walkthrough/components/walkthrough.layout.tsx index 6f92af33..1f747a03 100644 --- a/src/app/components/mint-unmint/components/walkthrough/components/walkthrough.layout.tsx +++ b/src/app/components/mint-unmint/components/walkthrough/components/walkthrough.layout.tsx @@ -3,7 +3,7 @@ import { HasChildren } from '@models/has-children'; export function WalkthroughLayout({ children }: HasChildren): React.JSX.Element { return ( - + {children} ); diff --git a/src/app/components/modals/components/modal-container.tsx b/src/app/components/modals/components/modal-container.tsx index 990d8ad1..761fa989 100644 --- a/src/app/components/modals/components/modal-container.tsx +++ b/src/app/components/modals/components/modal-container.tsx @@ -35,9 +35,15 @@ export function ModalContainer(): React.JSX.Element { /> handleClosingModal(() => - modalActions.toggleSuccessfulFlowModalVisibility({ vaultUUID: '' }) + modalActions.toggleSuccessfulFlowModalVisibility({ + vaultUUID: '', + flow: 'mint', + assetAmount: 0, + }) ) } vaultUUID={isSuccesfulFlowModalOpen[1] ? isSuccesfulFlowModalOpen[1] : ''} diff --git a/src/app/components/modals/components/modal.vault.layout.tsx b/src/app/components/modals/components/modal.vault.layout.tsx new file mode 100644 index 00000000..6fc6c3c3 --- /dev/null +++ b/src/app/components/modals/components/modal.vault.layout.tsx @@ -0,0 +1,35 @@ +import { ReactNode } from 'react'; + +import { + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalHeader, + ModalOverlay, +} from '@chakra-ui/react'; + +interface ModalVaultLayoutProps { + title: string; + isOpen: boolean; + onClose: () => void; + children: ReactNode; +} + +export function ModalVaultLayout({ + title, + isOpen, + onClose, + children, +}: ModalVaultLayoutProps): React.JSX.Element { + return ( + + + + {title} + + {children} + + + ); +} diff --git a/src/app/components/modals/successful-flow-modal/successful-flow-modal.tsx b/src/app/components/modals/successful-flow-modal/successful-flow-modal.tsx index 87fac717..a267a37b 100644 --- a/src/app/components/modals/successful-flow-modal/successful-flow-modal.tsx +++ b/src/app/components/modals/successful-flow-modal/successful-flow-modal.tsx @@ -1,26 +1,49 @@ -import { TransactionSummary } from '@components/mint-unmint/components/transaction-summary/transaction-summary'; +import { useContext } from 'react'; + +import { HStack, Text, VStack } from '@chakra-ui/react'; +import { TransactionFormNavigateButtonGroup } from '@components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.navigate-button-group'; +import { Vault } from '@components/vault/vault'; +import { VaultContext } from '@providers/vault-context-provider'; import { ModalComponentProps } from '../components/modal-container'; -import { ModalLayout } from '../components/modal.layout'; +import { ModalVaultLayout } from '../components/modal.vault.layout'; interface SuccessfulFlowModalProps extends ModalComponentProps { vaultUUID: string; + flow: 'mint' | 'burn'; + assetAmount: number; +} + +function getModalText(flow: 'mint' | 'burn', assetAmount?: number): string { + if (flow === 'mint') { + return `You have succesfully deposited ${assetAmount} BTC into your Vault, and minted ${assetAmount} dlcBTC to your address.`; + } else { + return `You have succesfully burned ${assetAmount} dlcBTC from your address, and withdraw ${assetAmount} BTC from your Vault.`; + } } export function SuccessfulFlowModal({ isOpen, handleClose, vaultUUID, + flow, + assetAmount, }: SuccessfulFlowModalProps): React.JSX.Element { + const { allVaults } = useContext(VaultContext); + + const currentVault = allVaults.find(vault => vault.uuid === vaultUUID); + return ( - handleClose()}> - handleClose()} - /> - + handleClose()}> + + + + {getModalText(flow, assetAmount)} + + + + + + ); } 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 1a53bf9e..29e720e3 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 @@ -7,11 +7,11 @@ export function MyVaultsSmallLayout({ children }: HasChildren): React.JSX.Elemen py={'2.5px'} px={'25px'} w={'31.5%'} - h={'825px'} + h={'925px'} bg={'background.container.01'} border={'1px solid'} borderRadius={'md'} - borderColor={'border.lightBlue.01'} + borderColor={'white.03'} > {children} diff --git a/src/app/components/my-vaults-small/my-vaults-small.tsx b/src/app/components/my-vaults-small/my-vaults-small.tsx index 668d6147..54fbe30c 100644 --- a/src/app/components/my-vaults-small/my-vaults-small.tsx +++ b/src/app/components/my-vaults-small/my-vaults-small.tsx @@ -26,7 +26,7 @@ export function MyVaultsSmall(): React.JSX.Element { 0} > diff --git a/src/app/components/my-vaults/components/my-vaults-large.layout.tsx b/src/app/components/my-vaults/components/my-vaults-large.layout.tsx index aded3873..e63719e8 100644 --- a/src/app/components/my-vaults/components/my-vaults-large.layout.tsx +++ b/src/app/components/my-vaults/components/my-vaults-large.layout.tsx @@ -10,7 +10,7 @@ export function MyVaultsLargeLayout({ children }: HasChildren): React.JSX.Elemen bg={'background.container.01'} border={'1px solid'} borderRadius={'md'} - borderColor={'border.lightBlue.01'} + borderColor={'white.03'} > {children} diff --git a/src/app/components/tab-button/tab-button.tsx b/src/app/components/tab-button/tab-button.tsx index a7766e76..34c7033d 100644 --- a/src/app/components/tab-button/tab-button.tsx +++ b/src/app/components/tab-button/tab-button.tsx @@ -8,7 +8,12 @@ interface TabButtonProps { export function TabButton({ title, isActive, handleClick }: TabButtonProps): React.JSX.Element { return ( - ); diff --git a/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.field-input.tsx b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.field-input.tsx index 2ca59253..d13c3aee 100644 --- a/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.field-input.tsx +++ b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.field-input.tsx @@ -34,7 +34,7 @@ export function TransactionFormFieldInput({ isInvalid={formField.state.meta.errors.length > 0} onChange={e => formField.handleChange(e)} > - + diff --git a/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.input-field.tsx b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.input-field.tsx deleted file mode 100644 index 48f2b66b..00000000 --- a/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.input-field.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import { HStack, Text, VStack } from '@chakra-ui/react'; -import { VaultVerticalProgressBar } from '@components/vault/components/vault-vertical-progress-bar'; -import { FormApi, ReactFormApi } from '@tanstack/react-form'; - -import { TransactionFormFieldInput } from './transaction-screen.transaction-form.field-input'; -import { TransactionFormInputUSDText } from './transaction-screen.transaction-form.input-usd-text'; -import { TransactionFormProgressStack } from './transaction-screen.transaction-form.progress-step-stack'; -import { VaultTransactionFormWarning } from './transaction-screen.transaction-form.warning'; - -export const formPropertyMap = { - mint: { - label: 'Deposit BTC', - logo: '/images/logos/bitcoin-logo.svg', - symbol: 'BTC', - color: 'orange.01', - }, - burn: { - label: 'Burn dlcBTC', - logo: '/images/logos/dlc-btc-logo.svg', - symbol: 'dlcBTC', - color: 'purple.01', - }, -}; - -interface TransactionFormInputFieldProps { - formAPI: FormApi< - { - assetAmount: string; - }, - undefined - > & - ReactFormApi< - { - assetAmount: string; - }, - undefined - >; - formType: 'mint' | 'burn'; - currentBitcoinPrice: number; -} - -export function TransactionFormInputField({ - formAPI, - currentBitcoinPrice, - formType, -}: TransactionFormInputFieldProps): React.JSX.Element { - return ( - - {field => ( - - - {formPropertyMap[formType].label} - - - - - - )} - - ); -} diff --git a/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.input.tsx b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.input.tsx index 2ca59253..42194f64 100644 --- a/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.input.tsx +++ b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.input.tsx @@ -1,45 +1,83 @@ -import { HStack, Image, NumberInput, NumberInputField, Text } from '@chakra-ui/react'; -import { FieldApi } from '@tanstack/react-form'; +import { Text, VStack } from '@chakra-ui/react'; +import { TransactionFormAPI } from '@models/form.models'; -interface TransactionFormFieldInputProps { - assetLogo: string; - assetSymbol: string; - formField: FieldApi< - { - assetAmount: string; - }, - 'assetAmount', - undefined, - undefined, - string - >; +import { TransactionFormFieldInput } from './transaction-screen.transaction-form.field-input'; +import { TransactionFormInputUSDText } from './transaction-screen.transaction-form.input-usd-text'; +import { VaultTransactionFormWarning } from './transaction-screen.transaction-form.warning'; + +const bitcoinFormProperties = { + label: 'Deposit BTC', + logo: '/images/logos/bitcoin-logo.svg', + symbol: 'BTC', + color: 'orange.01', +}; +const tokenFormProperties = { + label: 'Burn dlcBTC', + logo: '/images/logos/dlc-btc-logo.svg', + symbol: 'dlcBTC', + color: 'purple.01', +}; + +export function getFormProperties( + flow: 'mint' | 'burn', + currentStep: number +): { + label: string; + logo: string; + symbol: string; + color: string; +} { + switch (flow) { + case 'mint': + return bitcoinFormProperties; + case 'burn': + return currentStep === 1 ? bitcoinFormProperties : tokenFormProperties; + default: + throw new Error('Invalid Flow Type'); + } +} + +interface TransactionFormInputFieldProps { + formAPI: TransactionFormAPI; + currentStep: number; + formType: 'mint' | 'burn'; + currentBitcoinPrice: number; } -export function TransactionFormFieldInput({ - assetLogo, - assetSymbol, - formField, -}: TransactionFormFieldInputProps): React.JSX.Element { +export function TransactionFormInputField({ + formAPI, + currentStep, + currentBitcoinPrice, + formType, +}: TransactionFormInputFieldProps): React.JSX.Element { + const formProperties = getFormProperties(formType, currentStep); return ( - - - {'Asset - 0} - onChange={e => formField.handleChange(e)} + + {field => ( + - - - - - {assetSymbol} - - + + {formProperties.label} + + + + + + )} + ); } diff --git a/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.navigate-button-group.tsx b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.navigate-button-group.tsx new file mode 100644 index 00000000..fad3510d --- /dev/null +++ b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.navigate-button-group.tsx @@ -0,0 +1,41 @@ +import { useDispatch } from 'react-redux'; +import { useNavigate } from 'react-router-dom'; + +import { ButtonGroup } from '@chakra-ui/react'; +import { mintUnmintActions } from '@store/slices/mintunmint/mintunmint.actions'; + +import { TransactionFormNavigateButton } from './transaction-screen.transaction-form.navigate-button'; + +interface TransactionFormNavigateButtonGroupProps { + flow: 'mint' | 'burn'; +} + +export function TransactionFormNavigateButtonGroup({ + flow, +}: TransactionFormNavigateButtonGroupProps): React.JSX.Element { + const navigate = useNavigate(); + const dispatch = useDispatch(); + + function handleClick() { + if (flow === 'mint') { + dispatch(mintUnmintActions.setActiveTab(0)); + dispatch(mintUnmintActions.setMintStep([0, ''])); + } else { + dispatch(mintUnmintActions.setActiveTab(1)); + dispatch(mintUnmintActions.setUnmintStep([0, ''])); + } + } + + return ( + + handleClick()} + /> + navigate('/my-vaults')} + /> + + ); +} diff --git a/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.navigate-button.tsx b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.navigate-button.tsx new file mode 100644 index 00000000..8aec83d8 --- /dev/null +++ b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.navigate-button.tsx @@ -0,0 +1,26 @@ +import { Button } from '@chakra-ui/react'; + +interface TransactionFormNavigateButtonProps { + label: string; + onClick: () => void; +} + +export function TransactionFormNavigateButton({ + label, + onClick, +}: TransactionFormNavigateButtonProps): React.JSX.Element { + return ( + + ); +} diff --git a/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.progress-stack/components/transaction-screen.transaction-form.progress-stack.burn-variant-a.tsx b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.progress-stack/components/transaction-screen.transaction-form.progress-stack.burn-variant-a.tsx index 07eeaf20..74184a66 100644 --- a/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.progress-stack/components/transaction-screen.transaction-form.progress-stack.burn-variant-a.tsx +++ b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.progress-stack/components/transaction-screen.transaction-form.progress-stack.burn-variant-a.tsx @@ -2,24 +2,35 @@ import { HStack, VStack } from '@chakra-ui/react'; import { VaultVerticalProgressBar } from '@components/vault/components/vault-vertical-progress-bar'; import { TransactionFormAPI } from '@models/form.models'; -import { TransactionFormInputField } from '../../transaction-screen.transaction-form.input-field'; +import { TransactionFormInputField } from '../../transaction-screen.transaction-form.input'; import { TransactionFormProgressStack } from '../../transaction-screen.transaction-form.progress-step-stack'; interface TransactionFormProgressStackBurnVariantAProps { formAPI: TransactionFormAPI; currentBitcoinPrice: number; + currentStep: number; } export function TransactionFormProgressStackBurnVariantA({ formAPI, + currentStep, currentBitcoinPrice, }: TransactionFormProgressStackBurnVariantAProps): React.JSX.Element { return ( - + diff --git a/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.progress-stack/components/transaction-screen.transaction-form.progress-stack.burn-variant-b.tsx b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.progress-stack/components/transaction-screen.transaction-form.progress-stack.burn-variant-b.tsx index 5f694fe3..313b735e 100644 --- a/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.progress-stack/components/transaction-screen.transaction-form.progress-stack.burn-variant-b.tsx +++ b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.progress-stack/components/transaction-screen.transaction-form.progress-stack.burn-variant-b.tsx @@ -3,12 +3,18 @@ import { VaultVerticalProgressBar } from '@components/vault/components/vault-ver import { TransactionFormProgressStack } from '../../transaction-screen.transaction-form.progress-step-stack'; -interface TransactionFormProgressStackMintVariantAProps {} - export function TransactionFormProgressStackBurnVariantB(): React.JSX.Element { return ( - - + + - + + - + + - + + {'Asset - {label} + + {label} + diff --git a/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.protocol-fee-box.tsx b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.protocol-fee-box.tsx index 4bd9a718..83d75a06 100644 --- a/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.protocol-fee-box.tsx +++ b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.protocol-fee-box.tsx @@ -1,11 +1,12 @@ -import { useEffect } from 'react'; - import { HStack, Text, VStack } from '@chakra-ui/react'; +import { Vault } from '@models/vault'; import Decimal from 'decimal.js'; import { getFeeAmount } from 'dlc-btc-lib/bitcoin-functions'; interface TransactionFormProtocolFeeStackProps { - formType: 'mint' | 'burn'; + flow: 'mint' | 'burn'; + vault: Vault; + currentStep: number; assetAmount?: number; bitcoinPrice?: number; protocolFeeBasisPoints?: number; @@ -24,16 +25,20 @@ function calculateProtocolFeeInUSD( } export function TransactionFormProtocolFeeStack({ - formType, + flow, + vault, + currentStep, assetAmount, bitcoinPrice, protocolFeeBasisPoints, isBitcoinWalletLoading, }: TransactionFormProtocolFeeStackProps): React.JSX.Element | false { - useEffect(() => { - console.log('assetAmount:', assetAmount); - }, [assetAmount]); - if (isBitcoinWalletLoading[0] || formType === 'burn') return false; + if (isBitcoinWalletLoading[0] || [0, 2].includes(currentStep)) return false; + + const amount = + flow === 'burn' && currentStep === 1 + ? new Decimal(vault.valueLocked).minus(vault.valueMinted).toNumber() + : assetAmount; return ( - {`${ - assetAmount && protocolFeeBasisPoints - ? getFeeAmount(assetAmount, protocolFeeBasisPoints) - : 0 - } + {`${amount && protocolFeeBasisPoints ? getFeeAmount(amount, protocolFeeBasisPoints) : 0} BTC`} {' '} diff --git a/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.submit-button-group.tsx b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.submit-button-group.tsx new file mode 100644 index 00000000..c4604044 --- /dev/null +++ b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.submit-button-group.tsx @@ -0,0 +1,84 @@ +import { Button, VStack } from '@chakra-ui/react'; +import { TransactionFormAPI } from '@models/form.models'; +import { BitcoinWalletContextState } from '@providers/bitcoin-wallet-context-provider'; + +import { getFormProperties } from './transaction-screen.transaction-form.input'; + +function getButtonLabel( + flow: 'mint' | 'burn', + currentStep: number, + isSubmitting: boolean, + walletState: BitcoinWalletContextState +): string { + if (isSubmitting) return 'Processing'; + + const isWalletReady = walletState === BitcoinWalletContextState.READY; + const isCurrentStepZero = currentStep === 0; + + switch (flow) { + case 'burn': + return isCurrentStepZero + ? 'Sign Burn Transaction' + : isWalletReady + ? 'Sign Withdraw Transaction' + : 'Connect Wallet'; + + case 'mint': + return isWalletReady ? 'Sign Deposit Transaction' : 'Connect Wallet'; + default: + throw new Error('Invalid Flow Type'); + } +} + +interface TransactionFormSubmitButtonGroupProps { + flow: 'mint' | 'burn'; + formAPI: TransactionFormAPI; + currentStep: number; + userEthereumAddressRiskLevel: string; + bitcoinWalletContextState: any; + handleCancelButtonClick: () => void; +} + +export function TransactionFormSubmitButtonGroup({ + flow, + formAPI, + currentStep, + userEthereumAddressRiskLevel, + bitcoinWalletContextState, + handleCancelButtonClick, +}: TransactionFormSubmitButtonGroupProps): React.JSX.Element { + const formProperties = getFormProperties(flow, currentStep); + return ( + + [state.canSubmit, state.isSubmitting]} + children={([canSubmit, isSubmitting]) => ( + + )} + /> + + + ); +} diff --git a/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screent.transaction-form.transaction-information.tsx b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screent.transaction-form.transaction-information.tsx index d0c5feeb..db73896d 100644 --- a/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screent.transaction-form.transaction-information.tsx +++ b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screent.transaction-form.transaction-information.tsx @@ -1,17 +1,28 @@ import { HStack, Text } from '@chakra-ui/react'; +import { Vault } from '@models/vault'; +import Decimal from 'decimal.js'; interface TransactionFormWarningProps { - formType: 'mint' | 'burn'; + flow: 'mint' | 'burn'; + vault: Vault; + currentStep: number; assetAmount: number; isBitcoinWalletLoading: [boolean, string]; } export function TransactionFormTransactionInformation({ - formType, + flow, + vault, + currentStep, assetAmount, isBitcoinWalletLoading, }: TransactionFormWarningProps): React.JSX.Element | false { - if (isBitcoinWalletLoading[0] || formType === 'burn') return false; + if (isBitcoinWalletLoading[0] || [0, 2].includes(currentStep)) return false; + + const amount = + flow === 'burn' && currentStep === 1 + ? new Decimal(vault.valueLocked).minus(vault.valueMinted).toNumber() + : assetAmount; return ( - Make sure you have {assetAmount} BTC + (fees) + Make sure you have {amount} BTC + (fees) in your Bitcoin Wallet before proceeding to the next step. diff --git a/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/transaction-screen.transaction-form.tsx b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/transaction-screen.transaction-form.tsx index 36625f92..418c7e60 100644 --- a/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/transaction-screen.transaction-form.tsx +++ b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/transaction-screen.transaction-form.tsx @@ -1,14 +1,15 @@ -import React, { useState } from 'react'; +import React, { useContext, useState } from 'react'; -import { Button, VStack } from '@chakra-ui/react'; +import { VStack } from '@chakra-ui/react'; import { RiskBox } from '@components/mint-unmint/components/risk-box/risk-box'; import { TransactionFormAPI } from '@models/form.models'; import { Vault } from '@models/vault'; +import { BitcoinTransactionConfirmationsContext } from '@providers/bitcoin-query-provider'; import { BitcoinWalletContextState } from '@providers/bitcoin-wallet-context-provider'; import { useForm } from '@tanstack/react-form'; import Decimal from 'decimal.js'; -import { formPropertyMap } from './components/transaction-screen.transaction-form.input-field'; +import { TransactionFormNavigateButtonGroup } from './components/transaction-screen.transaction-form.navigate-button-group'; import { TransactionFormProgressStackBurnVariantA } from './components/transaction-screen.transaction-form.progress-stack/components/transaction-screen.transaction-form.progress-stack.burn-variant-a'; import { TransactionFormProgressStackBurnVariantB } from './components/transaction-screen.transaction-form.progress-stack/components/transaction-screen.transaction-form.progress-stack.burn-variant-b'; import { TransactionFormProgressStackBurnVariantC } from './components/transaction-screen.transaction-form.progress-stack/components/transaction-screen.transaction-form.progress-stack.burn-variant-c'; @@ -16,6 +17,7 @@ import { TransactionFormProgressStackMintVariantA } from './components/transacti import { TransactionFormProgressStackMintVariantB } from './components/transaction-screen.transaction-form.progress-stack/components/transaction-screen.transaction-form.progress-stack.mint-variant-b'; import { TransactionFormProgressStackMintVariantC } from './components/transaction-screen.transaction-form.progress-stack/components/transaction-screen.transaction-form.progress-stack.mint-variant-c'; import { TransactionFormProtocolFeeStack } from './components/transaction-screen.transaction-form.protocol-fee-box'; +import { TransactionFormSubmitButtonGroup } from './components/transaction-screen.transaction-form.submit-button-group'; import { TransactionScreenWalletInformation } from './components/transaction-screen.transaction-form.wallet-information'; import { TransactionFormTransactionInformation } from './components/transaction-screent.transaction-form.transaction-information'; @@ -35,7 +37,7 @@ function validateDepositAmount( return error; } -function validateWithdrawAmount(value: number, valueMinted: number): string | undefined { +function validateBurnAmount(value: number, valueMinted: number): string | undefined { let error; if (!value) { @@ -52,7 +54,6 @@ function validateFormAmount( depositLimit?: { minimumDeposit: number; maximumDeposit: number }, vault?: Vault ): string | undefined { - console.log('value', value); if (!vault) { return 'Vault is not available'; } else if (!depositLimit) { @@ -63,68 +64,67 @@ function validateFormAmount( case 'mint': return validateDepositAmount(value, depositLimit); case 'burn': - return validateWithdrawAmount(value, vault.valueMinted); + return validateBurnAmount(value, vault.valueMinted); } } -function getButtonLabel( - formType: 'mint' | 'burn', +function getTransactionProgressStack( + flow: 'mint' | 'burn', currentStep: number, - isSubmitting: boolean, - bitcoinWalletContextState: BitcoinWalletContextState -): string { - if (isSubmitting) return 'Processing'; - - if (formType === 'burn') { - if (currentStep === 0) return 'Sign Burn Transaction'; - return bitcoinWalletContextState === BitcoinWalletContextState.READY - ? 'Sign Withdraw Transaction' - : 'Connect Wallet'; - } - - return bitcoinWalletContextState === BitcoinWalletContextState.READY - ? 'Sign Deposit Transaction' - : 'Connect Wallet'; + formAPI: TransactionFormAPI, + currentBitcoinPrice: number, + confirmations: number | undefined +): React.JSX.Element | undefined { + const mintSteps = [ + , + , + , + ]; + + const burnSteps = [ + , + , + , + ]; + + const stepComponents = flow === 'mint' ? mintSteps : burnSteps; + const step = + flow === 'mint' ? (confirmations! >= 6 ? currentStep : currentStep - 1) : currentStep; + + return stepComponents[step]; } -function getTransactionProgressStack( +function getTransactionButtonGroup( flow: 'mint' | 'burn', currentStep: number, formAPI: TransactionFormAPI, - currentBitcoinPrice: number -): React.JSX.Element | undefined { - switch (flow) { - case 'mint': - switch (currentStep) { - case 1: - return ( - - ); - case 2: - return ; - case 3: - return ; - } - break; - case 'burn': - switch (currentStep) { - case 0: - return ( - - ); - case 1: - return ; - case 2: - return ; - } - break; - } + userEthereumAddressRiskLevel: any, + bitcoinWalletContextState: BitcoinWalletContextState, + handleCancelButtonClick: () => void +): React.JSX.Element | false { + const showSubmitButtonGroup = + (flow === 'mint' && currentStep === 1) || (flow === 'burn' && [0, 1].includes(currentStep)); + + return showSubmitButtonGroup ? ( + + ) : ( + + ); } interface VaultTransactionFormProps { @@ -155,23 +155,23 @@ export function VaultTransactionForm({ depositLimit, }: VaultTransactionFormProps): React.JSX.Element { const [currentFieldValue, setCurrentFieldValue] = useState(depositLimit?.minimumDeposit!); + + const confirmations = useContext( + BitcoinTransactionConfirmationsContext + ).bitcoinTransactionConfirmations.find(v => v[0] === vault.uuid)?.[1]; + const form = useForm({ defaultValues: { assetAmount: depositLimit?.minimumDeposit!.toString()!, }, onSubmit: async ({ value }) => { - const assetAmount = new Decimal(value.assetAmount); + const assetAmount = new Decimal(value.assetAmount).toNumber(); const burnAmount = new Decimal(vault.valueLocked).minus(vault.valueMinted).toNumber(); + const isWithdrawStep = flow === 'burn' && currentStep !== 0; + + const amountToHandle = isWithdrawStep ? burnAmount : assetAmount; - if (flow === 'burn') { - if (currentStep === 0) { - await handleButtonClick(assetAmount.toNumber()); - } else { - await handleButtonClick(burnAmount); - } - } else { - await handleButtonClick(assetAmount.toNumber()); - } + await handleButtonClick(amountToHandle); }, validators: { onChange: ({ value }) => { @@ -198,23 +198,21 @@ export function VaultTransactionForm({ await form.handleSubmit(); }} > - - {getTransactionProgressStack(flow, currentStep, form, currentBitcoinPrice!)} + + {getTransactionProgressStack(flow, currentStep, form, currentBitcoinPrice!, confirmations)} @@ -225,34 +223,14 @@ export function VaultTransactionForm({ /> )} - [state.canSubmit, state.isSubmitting]} - children={([canSubmit, isSubmitting]) => ( - - )} - /> - + {getTransactionButtonGroup( + flow, + currentStep, + form, + userEthereumAddressRiskLevel, + bitcoinWalletContextState, + handleCancelButtonClick + )} ); diff --git a/src/app/components/vault/components/vault-vertical-progress-bar.tsx b/src/app/components/vault/components/vault-vertical-progress-bar.tsx index 546faf58..e0e627d0 100644 --- a/src/app/components/vault/components/vault-vertical-progress-bar.tsx +++ b/src/app/components/vault/components/vault-vertical-progress-bar.tsx @@ -1,8 +1,10 @@ -import { Divider, HStack, Image, Spinner, Stack, VStack } from '@chakra-ui/react'; +import { Divider, Image, Spinner, Stack, VStack } from '@chakra-ui/react'; interface VaultVerticalProgressBarProps { currentStep: number; + variant?: 'small'; } + const Status = { ACTIVE: 'ACTIVE', COMPLETED: 'COMPLETED', @@ -34,10 +36,15 @@ function getComponent(status: string): React.JSX.Element | false { export function VaultVerticalProgressBar({ currentStep, + variant, }: VaultVerticalProgressBarProps): React.JSX.Element { return ( - + {getComponent(getStatus(currentStep, 0))} {getComponent(getStatus(currentStep, 1))} 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 index 756d683a..c6347241 100644 --- 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 @@ -13,7 +13,7 @@ export function VaultExpandedInformationButton({ }: VaultExpandedInformationButtonProps): React.JSX.Element { return (