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/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/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 1ca096ae..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,7 +50,6 @@ export function BurnTokenTransactionForm({ 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'); @@ -92,10 +91,11 @@ export function BurnTokenTransactionForm({ return ( - + - + state.mintunmint); const { risk, fetchUserAddressRisk, isLoading } = useRisk(); @@ -28,7 +23,7 @@ export function Mint(): React.JSX.Element { {[0].includes(mintStep[0]) && } - {[1].includes(mintStep[0]) && ( + {[1, 2].includes(mintStep[0]) && ( )} - {[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 d8d6c181..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 @@ -67,10 +67,11 @@ export function WithdrawScreen({ return ( - + )} - {[1].includes(unmintStep[0]) && ( + {[1, 2].includes(unmintStep[0]) && ( )} - {[2].includes(unmintStep[0]) && ( - - )} ); diff --git a/src/app/components/mint-unmint/components/walkthrough/components/walkthrough-header.tsx b/src/app/components/mint-unmint/components/walkthrough/components/walkthrough-header.tsx index f6de8568..f6558adc 100644 --- a/src/app/components/mint-unmint/components/walkthrough/components/walkthrough-header.tsx +++ b/src/app/components/mint-unmint/components/walkthrough/components/walkthrough-header.tsx @@ -14,7 +14,7 @@ export function WalkthroughHeader({ title, }: WalkthroughHeaderProps): React.JSX.Element { 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 new file mode 100644 index 00000000..d13c3aee --- /dev/null +++ b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.field-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/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.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.tsx new file mode 100644 index 00000000..358574bf --- /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.tsx @@ -0,0 +1,154 @@ +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'; +import { TransactionFormProgressStackItem } from '../../transaction-screen.transaction-form.progress-step-stack-item'; + +interface ProgressStackItemProps { + label: string; + assetLogo: string; + assetSymbol: string; +} + +const componentsMap = { + mint: { + A: { label: 'Deposit', assetLogo: '/images/logos/bitcoin-logo.svg', assetSymbol: 'BTC' }, + B: { label: 'Mint', assetLogo: '/images/logos/dlc-btc-logo.svg', assetSymbol: 'dlcBTC' }, + }, + burn: { + A: { label: 'Burn', assetLogo: '/images/logos/dlc-btc-logo.svg', assetSymbol: 'dlcBTC' }, + B: { label: 'Withdraw', assetLogo: '/images/logos/bitcoin-logo.svg', assetSymbol: 'BTC' }, + }, +}; + +interface ProgressStackProps { + formAPI: TransactionFormAPI; + isIncludeForm: boolean; + flow: 'mint' | 'burn'; + currentStep: number; + activeStackItem: 0 | 1; + currentBitcoinPrice: number; + components: { A: ProgressStackItemProps; B: ProgressStackItemProps }; +} + +const ProgressStack = ({ + formAPI, + isIncludeForm, + flow, + currentStep, + activeStackItem, + currentBitcoinPrice, + components, +}: ProgressStackProps): React.JSX.Element => { + const { A, B } = components; + + return ( + + {isIncludeForm ? ( + <> + + + + ) : ( + <> + + + + )} + + ); +}; + +interface ProgressStackByFlowProps { + formAPI: TransactionFormAPI; + flow: 'mint' | 'burn'; + currentStep: number; + confirmations?: number; + currentBitcoinPrice: number; +} + +const ProgressStackByFlow = ({ + formAPI, + flow, + currentStep, + confirmations = 0, + currentBitcoinPrice, +}: ProgressStackByFlowProps): React.JSX.Element => { + const components = componentsMap[flow]; + const isIncludeForm = flow === 'mint' ? currentStep === 1 : currentStep === 0; + const activeStackItem = isIncludeForm || (flow === 'mint' && confirmations < 6) ? 0 : 1; + + return ( + + ); +}; + +interface TransactionFormProgressStackBurnVariantAProps { + formAPI: TransactionFormAPI; + flow: 'mint' | 'burn'; + currentBitcoinPrice: number; + currentStep: number; + confirmations?: number; +} + +export const TransactionFormProgressStack = ({ + formAPI, + flow, + currentStep, + confirmations = 0, + 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-step-stack-item.tsx b/src/app/components/transaction-screen/transaction-screen.transaction-form/components/transaction-screen.transaction-form/components/transaction-screen.transaction-form.progress-step-stack-item.tsx new file mode 100644 index 00000000..c64267de --- /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-item.tsx @@ -0,0 +1,39 @@ +import { HStack, Image, Stack, Text } from '@chakra-ui/react'; + +interface TransactionFormProgressStackItemProps { + label: string; + assetLogo: string; + assetSymbol: string; + isActive: boolean; +} + +export function TransactionFormProgressStackItem({ + label, + assetLogo, + assetSymbol, + isActive, +}: TransactionFormProgressStackItemProps): 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..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,9 +1,12 @@ 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: 'deposit' | 'withdraw' | 'burn'; + flow: 'mint' | 'burn'; + vault: Vault; + currentStep: number; assetAmount?: number; bitcoinPrice?: number; protocolFeeBasisPoints?: number; @@ -22,13 +25,20 @@ function calculateProtocolFeeInUSD( } export function TransactionFormProtocolFeeStack({ - formType, + flow, + vault, + currentStep, assetAmount, bitcoinPrice, protocolFeeBasisPoints, isBitcoinWalletLoading, }: TransactionFormProtocolFeeStackProps): 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 ( - {`${ - 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 fc3ae77e..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: 'deposit' | 'withdraw' | '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 4e0d18ee..9b0a08b3 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,15 +1,19 @@ -import { Button, Text, VStack } from '@chakra-ui/react'; +import React, { useContext, useState } from '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 { TransactionFormFieldInput } from './components/transaction-screen.transaction-form.input'; -import { TransactionFormInputUSDText } from './components/transaction-screen.transaction-form.input-usd-text'; +import { TransactionFormNavigateButtonGroup } from './components/transaction-screen.transaction-form.navigate-button-group'; +import { TransactionFormProgressStack } from './components/transaction-screen.transaction-form.progress-stack/components/transaction-screen.transaction-form.progress-stack'; 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 { VaultTransactionFormWarning } from './components/transaction-screen.transaction-form.warning'; import { TransactionFormTransactionInformation } from './components/transaction-screent.transaction-form.transaction-information'; function validateDepositAmount( @@ -28,7 +32,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) { @@ -41,7 +45,7 @@ function validateWithdrawAmount(value: number, valueMinted: number): string | un function validateFormAmount( value: number, - type: 'deposit' | 'withdraw' | 'burn', + type: 'mint' | 'burn', depositLimit?: { minimumDeposit: number; maximumDeposit: number }, vault?: Vault ): string | undefined { @@ -52,61 +56,41 @@ function validateFormAmount( } switch (type) { - case 'deposit': + case 'mint': return validateDepositAmount(value, depositLimit); - case 'withdraw': - return validateWithdrawAmount(value, vault.valueMinted); case 'burn': - return undefined; + return validateBurnAmount(value, vault.valueMinted); } } -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'; - } - } -} +function getTransactionButtonGroup( + flow: 'mint' | 'burn', + currentStep: number, + formAPI: TransactionFormAPI, + userEthereumAddressRiskLevel: any, + bitcoinWalletContextState: BitcoinWalletContextState, + handleCancelButtonClick: () => void +): React.JSX.Element | false { + const showSubmitButtonGroup = + (flow === 'mint' && currentStep === 1) || (flow === 'burn' && [0, 1].includes(currentStep)); -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', - }, -}; + return showSubmitButtonGroup ? ( + + ) : ( + + ); +} interface VaultTransactionFormProps { - type: 'deposit' | 'withdraw' | 'burn'; + flow: 'mint' | 'burn'; + currentStep: number; bitcoinWalletContextState: BitcoinWalletContextState; isBitcoinWalletLoading: [boolean, string]; userEthereumAddressRiskLevel?: string; @@ -119,7 +103,8 @@ interface VaultTransactionFormProps { } export function VaultTransactionForm({ - type, + flow, + currentStep, bitcoinWalletContextState, isBitcoinWalletLoading, userEthereumAddressRiskLevel, @@ -130,32 +115,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 confirmations = useContext( + BitcoinTransactionConfirmationsContext + ).bitcoinTransactionConfirmations.find(v => v[0] === vault.uuid)?.[1]; + + 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).toNumber(); + const burnAmount = new Decimal(vault.valueLocked).minus(vault.valueMinted).toNumber(); + const isWithdrawStep = flow === 'burn' && currentStep !== 0; + + const amountToHandle = isWithdrawStep ? burnAmount : assetAmount; + + await handleButtonClick(amountToHandle); }, validators: { onChange: ({ value }) => { + setCurrentFieldValue(new Decimal(value.assetAmount).toNumber()); return { fields: { assetAmount: validateFormAmount( parseFloat(value.assetAmount), - type, + flow, depositLimit, vault ), @@ -170,49 +156,31 @@ export function VaultTransactionForm({ onSubmit={async e => { e.preventDefault(); e.stopPropagation(); - await handleSubmit(); + await form.handleSubmit(); }} > - - {type !== 'withdraw' && ( - - {field => ( - - - {formPropertyMap[type].label} - - - - - - )} - - )} + + {isUserEthereumAddressRiskLevelLoading && userEthereumAddressRiskLevel && ( @@ -222,30 +190,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 new file mode 100644 index 00000000..d97818c7 --- /dev/null +++ b/src/app/components/vault/components/vault-vertical-progress-bar.tsx @@ -0,0 +1,65 @@ +import { Divider, Image, Spinner, Stack, VStack } from '@chakra-ui/react'; + +interface VaultVerticalProgressBarProps { + flow: 'mint' | 'burn'; + currentStep: number; + confirmations?: number; + variant?: 'small'; +} + +const Status = { + ACTIVE: 'ACTIVE', + COMPLETED: 'COMPLETED', + INACTIVE: '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; + } +} + +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({ + flow, + currentStep, + confirmations = 0, +}: VaultVerticalProgressBarProps): React.JSX.Element { + const isMintFlow = flow === 'mint'; + const isBurnFlow = flow === 'burn'; + + const activeStatus = + (isMintFlow && [1, 2].includes(currentStep) && confirmations < 6) || + (isBurnFlow && currentStep === 0) + ? 0 + : 1; + + const height = + (isMintFlow && currentStep !== 1) || (isBurnFlow && currentStep !== 0) ? '125px' : '185px'; + + return ( + + + {getComponent(getStatus(activeStatus, 0))} + + {getComponent(getStatus(activeStatus, 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 (