diff --git a/features/adjustment/mint/form/address-field/address-field.tsx b/features/adjustment/mint/form/address-field/address-field.tsx index 3cca8abc..8986f2fc 100644 --- a/features/adjustment/mint/form/address-field/address-field.tsx +++ b/features/adjustment/mint/form/address-field/address-field.tsx @@ -14,7 +14,6 @@ export const AddressField = () => { }); }; - // TODO: add error return ( { {isLoading && } {/*TODO: replace static by real data*/} - {data && {'$99.99'}} - {isError && Is not available} - {!isLoading && !data && !isError && {'$0'}} + {data?.result && !isLoading && {data?.result}} + {isError && !isLoading && Is not available} + {!isLoading && !data?.result && !isError && -} ); diff --git a/features/adjustment/mint/hooks/use-mint.ts b/features/adjustment/mint/hooks/use-mint.ts index 3f09d201..25a7cea0 100644 --- a/features/adjustment/mint/hooks/use-mint.ts +++ b/features/adjustment/mint/hooks/use-mint.ts @@ -2,8 +2,8 @@ import { useCallback } from 'react'; import { useConfig, useSimulateContract, - useWaitForTransactionReceipt, useWriteContract, + usePublicClient, UseSimulateContractParameters, } from 'wagmi'; import { Address } from 'viem'; @@ -11,12 +11,17 @@ import { Address } from 'viem'; import { dashboardAbi } from 'abi/dashboard-abi'; import { useDappStatus } from 'modules/web3/hooks/use-dapp-status'; import { useVaultInfo } from 'features/overview/contexts'; +import { + SubmitStep, + SubmitStepEnum, +} from 'shared/components/submit-modal/types'; +import invariant from 'tiny-invariant'; export const useMint = (onMutate = () => {}) => { const { chainId } = useDappStatus(); const wagmiConfig = useConfig(); const { activeVault } = useVaultInfo(); - + const publicClient = usePublicClient(); const { data: mintTx, writeContractAsync } = useWriteContract({ config: wagmiConfig, mutation: { @@ -24,27 +29,37 @@ export const useMint = (onMutate = () => {}) => { }, }); - const { data: mintReceipt } = useWaitForTransactionReceipt({ - hash: mintTx, - }); - const callMint = useCallback( - async (recipient: Address, amount: bigint, token: string) => { - return await writeContractAsync({ + async ( + recipient: Address, + amount: bigint, + token: string, + setModalState: (submitStep: { step: SubmitStep; tx?: Address }) => void, + ) => { + invariant(publicClient, '[useMintDashboard] publicClient is undefined'); + + setModalState({ step: SubmitStepEnum.confirming }); + const tx = await writeContractAsync({ abi: dashboardAbi, address: activeVault?.owner as Address, functionName: token === 'stETH' ? 'mintStETH' : 'mintWstETH', args: [recipient, amount], chainId, }); + + setModalState({ step: SubmitStepEnum.submitting, tx }); + await publicClient.waitForTransactionReceipt({ + hash: tx, + }); + + return tx; }, - [chainId, writeContractAsync, activeVault?.owner], + [chainId, writeContractAsync, activeVault?.owner, publicClient], ); return { callMint, mintTx, - mintReceipt, }; }; diff --git a/features/adjustment/mint/mint-form-context/mint-form-provider.tsx b/features/adjustment/mint/mint-form-context/mint-form-provider.tsx index 11110f0f..80c1c87a 100644 --- a/features/adjustment/mint/mint-form-context/mint-form-provider.tsx +++ b/features/adjustment/mint/mint-form-context/mint-form-provider.tsx @@ -5,6 +5,7 @@ import { useCallback, createContext, useContext, + useState, } from 'react'; import { FormProvider, useForm } from 'react-hook-form'; import invariant from 'tiny-invariant'; @@ -19,6 +20,12 @@ import { FormControllerContextValueType, } from 'shared/hook-form/form-controller'; import { MintFormSchema } from 'features/adjustment/mint/types'; +import { SubmitModal } from 'shared/components'; +import { + SubmitStep, + SubmitStepEnum, +} from 'shared/components/submit-modal/types'; +import { Address } from 'viem'; type MintDataContextValue = { mintableStETH: bigint; @@ -39,11 +46,15 @@ export const useMintFormData = () => { }; export const MintFormProvider: FC<{ children: ReactNode }> = ({ children }) => { + const [submitStep, setSubmitStep] = useState<{ + step: SubmitStep; + tx?: Address; + }>(() => ({ step: SubmitStepEnum.edit })); const formObject = useForm({ defaultValues: { amount: undefined, token: 'stETH', - recipient: undefined, + recipient: '' as Address, }, mode: 'all', reValidateMode: 'onChange', @@ -60,16 +71,36 @@ export const MintFormProvider: FC<{ children: ReactNode }> = ({ children }) => { }, [activeVault]); const { retryEvent, retryFire } = useFormControllerRetry(); + const setModalState = useCallback( + (submitStep: { step: SubmitStep; tx?: Address }) => { + setSubmitStep(submitStep); + }, + [], + ); const onSubmit = useCallback( async ({ recipient, amount, token }: MintFormSchema) => { if (amount && recipient) { - await callMint(recipient, amount, token); - return true; + try { + setModalState({ step: SubmitStepEnum.initiate }); + const tx = await callMint(recipient, amount, token, setModalState); + setModalState({ step: SubmitStepEnum.success, tx }); + return true; + } catch (err) { + if ( + err instanceof Error && + err.message.includes('User rejected the request') + ) { + setModalState({ step: SubmitStepEnum.reject }); + } else { + setModalState({ step: SubmitStepEnum.error }); + } + } } return false; }, + // eslint-disable-next-line react-hooks/exhaustive-deps [callMint], ); @@ -89,6 +120,7 @@ export const MintFormProvider: FC<{ children: ReactNode }> = ({ children }) => { {children} + diff --git a/features/adjustment/mint/types.ts b/features/adjustment/mint/types.ts index ff93bc83..a2051261 100644 --- a/features/adjustment/mint/types.ts +++ b/features/adjustment/mint/types.ts @@ -3,5 +3,5 @@ import { Address } from 'viem'; export type MintFormSchema = { amount: bigint | undefined; token: string; - recipient: Address | undefined; + recipient: Address; }; diff --git a/features/adjustment/repay/form/balance/balance.tsx b/features/adjustment/repay/form/balance/balance.tsx index b76e0c47..11aac132 100644 --- a/features/adjustment/repay/form/balance/balance.tsx +++ b/features/adjustment/repay/form/balance/balance.tsx @@ -29,7 +29,9 @@ export const Balance = () => { {formatBalance(balance).trimmed} {token} )} - {isError && stEth amount is not available} + {isError && !isLoading && ( + stEth amount is not available + )} ); diff --git a/features/adjustment/repay/form/feature-tx-info/feature-tx-info.tsx b/features/adjustment/repay/form/feature-tx-info/feature-tx-info.tsx index 41739ce4..dfb574a8 100644 --- a/features/adjustment/repay/form/feature-tx-info/feature-tx-info.tsx +++ b/features/adjustment/repay/form/feature-tx-info/feature-tx-info.tsx @@ -30,10 +30,9 @@ export const FeatureTxInfo = () => { Transaction cost {isLoading && } - {/*TODO: replace static by real data*/} - {data && {'$99.99'}} - {isError && Is not available} - {!isLoading && !data && !isError && {'$0'}} + {!!data?.result && {data?.result}} + {isError && !isLoading && Is not available} + {!isLoading && !data?.result && !isError && -} ); diff --git a/features/adjustment/repay/hooks/use-burn.ts b/features/adjustment/repay/hooks/use-burn.ts index 044f22ec..84d136f5 100644 --- a/features/adjustment/repay/hooks/use-burn.ts +++ b/features/adjustment/repay/hooks/use-burn.ts @@ -3,17 +3,24 @@ import { useConfig, useWaitForTransactionReceipt, useWriteContract, + usePublicClient, } from 'wagmi'; import { Address } from 'viem'; import { dashboardAbi } from 'abi/dashboard-abi'; import { useDappStatus } from 'modules/web3/hooks/use-dapp-status'; import { useVaultInfo } from 'features/overview/contexts'; +import { + SubmitStep, + SubmitStepEnum, +} from 'shared/components/submit-modal/types'; +import invariant from 'tiny-invariant'; export const useBurn = (onMutate = () => {}) => { const { chainId } = useDappStatus(); const wagmiConfig = useConfig(); const { activeVault } = useVaultInfo(); + const publicClient = usePublicClient(); const { data: burnTx, writeContractAsync } = useWriteContract({ config: wagmiConfig, @@ -27,16 +34,34 @@ export const useBurn = (onMutate = () => {}) => { }); const callBurn = useCallback( - async ({ token, amount }: { token: string; amount: bigint }) => { - return await writeContractAsync({ + async ({ + token, + amount, + setModalState, + }: { + token: string; + amount: bigint; + setModalState: (submitStep: { step: SubmitStep; tx?: Address }) => void; + }) => { + invariant(publicClient, '[useBurn] publicClient is undefined'); + + setModalState({ step: SubmitStepEnum.confirming }); + const tx = await writeContractAsync({ abi: dashboardAbi, address: activeVault?.owner as Address, functionName: token === 'stETH' ? 'burnStETH' : 'burnWstETH', args: [amount], chainId, }); + + setModalState({ step: SubmitStepEnum.submitting, tx }); + await publicClient.waitForTransactionReceipt({ + hash: tx, + }); + + return tx; }, - [chainId, activeVault?.owner, writeContractAsync], + [chainId, activeVault?.owner, writeContractAsync, publicClient], ); return { diff --git a/features/adjustment/repay/repay-form-context/repay-form-provider.tsx b/features/adjustment/repay/repay-form-context/repay-form-provider.tsx index d33e5e37..ed8888c1 100644 --- a/features/adjustment/repay/repay-form-context/repay-form-provider.tsx +++ b/features/adjustment/repay/repay-form-context/repay-form-provider.tsx @@ -4,6 +4,7 @@ import { useCallback, createContext, useContext, + useState, } from 'react'; import { FormProvider, useForm } from 'react-hook-form'; import invariant from 'tiny-invariant'; @@ -19,6 +20,9 @@ import { } from 'shared/hook-form/form-controller'; import { RepayFormSchema } from 'features/adjustment/repay/types'; +import { SubmitModal } from 'shared/components'; +import { SubmitStep, SubmitStepEnum } from 'shared/components/submit-modal'; +import { Address } from 'viem'; type RepayDataContextValue = { stEthBalance: bigint | undefined; @@ -45,6 +49,10 @@ export const useRepayFormData = () => { }; export const RepayFormProvider = ({ children }: { children: ReactNode }) => { + const [submitStep, setSubmitStep] = useState<{ + step: SubmitStep; + tx?: Address; + }>(() => ({ step: SubmitStepEnum.edit })); const formObject = useForm({ defaultValues: { amount: undefined, @@ -92,16 +100,38 @@ export const RepayFormProvider = ({ children }: { children: ReactNode }) => { ); const { retryEvent, retryFire } = useFormControllerRetry(); + const setModalState = useCallback( + (submitStep: { step: SubmitStep; tx?: Address }) => { + setSubmitStep(submitStep); + }, + [], + ); const onSubmit = useCallback( async ({ amount, token }: RepayFormSchema) => { - if (amount) { - await callBurn({ amount, token }); - return true; + try { + if (amount) { + setModalState({ step: SubmitStepEnum.initiate }); + const tx = await callBurn({ amount, token, setModalState }); + setModalState({ step: SubmitStepEnum.success, tx }); + return true; + } + + return false; + } catch (err) { + if ( + err instanceof Error && + err.message.includes('User rejected the request') + ) { + setModalState({ step: SubmitStepEnum.reject }); + } else { + setModalState({ step: SubmitStepEnum.error }); + } } return false; }, + // eslint-disable-next-line react-hooks/exhaustive-deps [callBurn], ); @@ -121,6 +151,7 @@ export const RepayFormProvider = ({ children }: { children: ReactNode }) => { {children} + diff --git a/features/claim/claim-form/claim-form-context/claim-form-provider.tsx b/features/claim/claim-form/claim-form-context/claim-form-provider.tsx index 21b63e1d..19b36b60 100644 --- a/features/claim/claim-form/claim-form-context/claim-form-provider.tsx +++ b/features/claim/claim-form/claim-form-context/claim-form-provider.tsx @@ -5,8 +5,9 @@ import { useCallback, createContext, useContext, + useState, } from 'react'; -import { Address, isAddress } from 'viem'; +import { Address, isAddress, ReadContractErrorType } from 'viem'; import { useReadContract } from 'wagmi'; import { FormProvider, useForm } from 'react-hook-form'; @@ -23,11 +24,17 @@ import { import { dashboardAbi } from 'abi/dashboard-abi'; import { ClaimFormSchema } from 'features/claim/claim-form/types'; import invariant from 'tiny-invariant'; +import { + SubmitStep, + SubmitStepEnum, +} from 'shared/components/submit-modal/types'; +import { SubmitModal } from '../submit-modal'; type ClaimDataContextValue = { availableToClaim: bigint | undefined; isLoadingClaimInfo: boolean; isErrorClaimInfo: boolean; + errorClaimInfo: ReadContractErrorType | null; }; const ClaimDataContext = createContext(null); @@ -46,12 +53,17 @@ export const useClaimFormData = () => { export const ClaimFormProvider: FC<{ children: ReactNode }> = ({ children, }) => { + const [submitStep, setSubmitStep] = useState<{ + step: SubmitStep; + tx?: Address; + }>(() => ({ step: SubmitStepEnum.edit })); const { activeVault } = useVaultInfo(); const { data: availableToClaim, isFetching: isLoadingClaimInfo, isError: isErrorClaimInfo, + error: errorClaimInfo, } = useReadContract({ abi: dashboardAbi, address: activeVault?.owner, @@ -66,8 +78,9 @@ export const ClaimFormProvider: FC<{ children: ReactNode }> = ({ availableToClaim, isLoadingClaimInfo, isErrorClaimInfo, + errorClaimInfo, }), - [availableToClaim, isLoadingClaimInfo, isErrorClaimInfo], + [availableToClaim, isLoadingClaimInfo, isErrorClaimInfo, errorClaimInfo], ); const formObject = useForm({ @@ -80,17 +93,36 @@ export const ClaimFormProvider: FC<{ children: ReactNode }> = ({ const { callClaim } = useClaim(); const { retryEvent, retryFire } = useFormControllerRetry(); + const setModalState = useCallback( + (submitStep: { step: SubmitStep; tx?: Address }) => { + setSubmitStep(submitStep); + }, + [], + ); const onSubmit = useCallback( async ({ recipient }: ClaimFormSchema) => { - if (isAddress(recipient as string)) { - // TODO: resolve recipient if ens domain - await callClaim(recipient as Address); - return true; + if (recipient && isAddress(recipient)) { + try { + setModalState({ step: SubmitStepEnum.initiate }); + const tx = await callClaim(recipient, setModalState); + setModalState({ step: SubmitStepEnum.success, tx }); + return true; + } catch (err) { + if ( + err instanceof Error && + err.message.includes('User rejected the request') + ) { + setModalState({ step: SubmitStepEnum.reject }); + } else { + setModalState({ step: SubmitStepEnum.error }); + } + } } return false; }, + // eslint-disable-next-line react-hooks/exhaustive-deps [callClaim], ); @@ -110,6 +142,7 @@ export const ClaimFormProvider: FC<{ children: ReactNode }> = ({ {children} + diff --git a/features/claim/claim-form/form/balance/balance.tsx b/features/claim/claim-form/form/balance/balance.tsx index 3517b466..9c6c5960 100644 --- a/features/claim/claim-form/form/balance/balance.tsx +++ b/features/claim/claim-form/form/balance/balance.tsx @@ -8,8 +8,9 @@ export const Balance = () => { const { isLoadingClaimInfo, availableToClaim, isErrorClaimInfo } = useClaimFormData(); + const isAvailableToClaim = typeof availableToClaim === 'bigint'; const isLoading = - (!isErrorClaimInfo && !availableToClaim) || isLoadingClaimInfo; + (!isErrorClaimInfo && !isAvailableToClaim) || isLoadingClaimInfo; return ( @@ -18,7 +19,7 @@ export const Balance = () => { Available to claim {isLoading && } - {!!availableToClaim && ( + {isAvailableToClaim && ( {formatBalance(availableToClaim).trimmed} ETH )} diff --git a/features/claim/claim-form/form/feature-tx-info/feature-tx-info.tsx b/features/claim/claim-form/form/feature-tx-info/feature-tx-info.tsx index 9d23480d..6faf8914 100644 --- a/features/claim/claim-form/form/feature-tx-info/feature-tx-info.tsx +++ b/features/claim/claim-form/form/feature-tx-info/feature-tx-info.tsx @@ -3,10 +3,11 @@ import { useFormContext } from 'react-hook-form'; import { Loader, Text } from '@lidofinance/lido-ui'; import { AmountInfo, InfoRow, Wrapper } from './styles'; import { useSimulationClaim } from 'features/claim/claim-form/hooks'; +import { Address } from 'viem'; export const FeatureTxInfo = () => { - const { getValues } = useFormContext(); - const { recipient } = getValues(); + const { watch } = useFormContext(); + const recipient: Address = watch('recipient'); const { data, isLoading, isError } = useSimulationClaim(recipient); return ( @@ -16,9 +17,13 @@ export const FeatureTxInfo = () => { Transaction cost {isLoading && } - {data && {'$99.99'}} - {isError && Is not available} - {!isLoading && !data && !isError && {'$0'}} + {/*TODO: get simulated data*/} + {/*TODO: show error message*/} + {!!data?.result && !isLoading && ( + {data?.result} + )} + {isError && !isLoading && -} + {!isLoading && !data && !isError && -} ); diff --git a/features/claim/claim-form/hooks/use-claim.ts b/features/claim/claim-form/hooks/use-claim.ts index 6846bc8c..c1108458 100644 --- a/features/claim/claim-form/hooks/use-claim.ts +++ b/features/claim/claim-form/hooks/use-claim.ts @@ -2,7 +2,7 @@ import { useCallback } from 'react'; import { useConfig, useSimulateContract, - useWaitForTransactionReceipt, + usePublicClient, useWriteContract, } from 'wagmi'; import { Address } from 'viem'; @@ -10,11 +10,17 @@ import { Address } from 'viem'; import { dashboardAbi } from 'abi/dashboard-abi'; import { useDappStatus } from 'modules/web3/hooks/use-dapp-status'; import { useVaultInfo } from 'features/overview/contexts'; +import invariant from 'tiny-invariant'; +import { + SubmitStep, + SubmitStepEnum, +} from 'shared/components/submit-modal/types'; export const useClaim = (onMutate = () => {}) => { const { chainId } = useDappStatus(); const wagmiConfig = useConfig(); const { activeVault } = useVaultInfo(); + const publicClient = usePublicClient(); const owner = activeVault?.owner; const { data: claimTx, writeContractAsync } = useWriteContract({ @@ -24,27 +30,39 @@ export const useClaim = (onMutate = () => {}) => { }, }); - const { data: claimReceipt } = useWaitForTransactionReceipt({ - hash: claimTx, - }); - const callClaim = useCallback( - async (recipient: Address) => { - return await writeContractAsync({ + async ( + recipient: Address, + setModalState: (submitStep: { step: SubmitStep; tx?: Address }) => void, + ) => { + invariant(owner, '[useClaimDashboard] owner is not available'); + invariant( + publicClient, + '[useClaimDashboard] publicClient is not available', + ); + + setModalState({ step: SubmitStepEnum.confirming }); + const tx = await writeContractAsync({ abi: dashboardAbi, - address: owner as Address, + address: owner, functionName: 'claimNodeOperatorFee', args: [recipient], chainId, }); + + setModalState({ step: SubmitStepEnum.submitting, tx }); + await publicClient.waitForTransactionReceipt({ + hash: tx, + }); + + return tx; }, - [chainId, writeContractAsync, owner], + [chainId, writeContractAsync, owner, publicClient], ); return { callClaim, claimTx, - claimReceipt, }; }; diff --git a/features/claim/claim-form/submit-modal/index.ts b/features/claim/claim-form/submit-modal/index.ts new file mode 100644 index 00000000..1c0e3509 --- /dev/null +++ b/features/claim/claim-form/submit-modal/index.ts @@ -0,0 +1 @@ +export { SubmitModal } from './submit-modal'; diff --git a/features/claim/claim-form/submit-modal/styles.ts b/features/claim/claim-form/submit-modal/styles.ts new file mode 100644 index 00000000..dfca0ac4 --- /dev/null +++ b/features/claim/claim-form/submit-modal/styles.ts @@ -0,0 +1,8 @@ +import styled from 'styled-components'; + +export const Content = styled.div` + display: flex; + flex-direction: column; + gap: ${({ theme }) => theme.spaceMap.xl}px; + margin-top: ${({ theme }) => theme.spaceMap.sm}px; +`; diff --git a/features/claim/claim-form/submit-modal/submit-modal.tsx b/features/claim/claim-form/submit-modal/submit-modal.tsx new file mode 100644 index 00000000..def6ae51 --- /dev/null +++ b/features/claim/claim-form/submit-modal/submit-modal.tsx @@ -0,0 +1,140 @@ +import { FC, useMemo } from 'react'; +import { useRouter } from 'next/router'; +import { useFormContext } from 'react-hook-form'; + +import { + Loader, + Modal, + Text, + Success, + Error, + type ModalProps, + Button, +} from '@lidofinance/lido-ui'; + +import { useFormControllerContext } from 'shared/hook-form/form-controller'; +import { ButtonLink, TxLinkEtherscan } from 'shared/components'; +import { Content } from './styles'; + +import { + SubmitStepEnum, + SubmitStep, +} from 'shared/components/submit-modal/types'; +import { AppPaths } from 'consts/urls'; +import { useVaultInfo } from 'features/overview/contexts'; +import { Address } from 'viem'; + +const getIconComponent = (step: SubmitStep) => { + switch (step) { + case SubmitStepEnum.success: + return ; + case SubmitStepEnum.reject: + return ; + case SubmitStepEnum.error: + return ; + default: + return ; + } +}; + +const getModalTitle = (step: SubmitStep) => { + switch (step) { + case SubmitStepEnum.success: + return 'Fees was getting successfully'; + case SubmitStepEnum.reject: + return 'Wallet tx signature'; + case SubmitStepEnum.error: + return 'Transaction error'; + default: + return 'You are requesting fees'; + } +}; + +const getModalSubTitle = (step: SubmitStep) => { + switch (step) { + case SubmitStepEnum.submitting: + return 'Awaiting wallet signature'; + case SubmitStepEnum.reject: + return 'User denied transaction signature'; + case SubmitStepEnum.error: + return 'Got error when called contract simulation or transaction'; + default: + return ''; + } +}; + +interface SubmitModalProps extends ModalProps { + submitStep: { + step: SubmitStep; + tx?: Address; + }; + setModalState: ({ step }: { step: SubmitStep }) => void; +} + +export const SubmitModal: FC = ({ + submitStep, + setModalState, +}) => { + const router = useRouter(); + const { + formState: { isSubmitting, isSubmitted }, + } = useFormContext(); + const { retryFire } = useFormControllerContext(); + const { step, tx } = submitStep ?? {}; + const { activeVault } = useVaultInfo(); + + const iconComponent = useMemo(() => getIconComponent(step), [step]); + const title = getModalTitle(step); + const subtitle = getModalSubTitle(step); + + const handleNavigateToVault = () => { + void router.push(`/${activeVault?.address}/${AppPaths.overview}`); + }; + + const handleCloseModal = () => { + setModalState({ step: SubmitStepEnum.edit }); + }; + + return ( + + + {step === SubmitStepEnum.initiate && ( + + Wait for wallet confirmation window + + )} + + {step === SubmitStepEnum.confirming && ( + + Confirm this transaction in your wallet + + )} + + {step === SubmitStepEnum.success && ( + + )} + + {(SubmitStepEnum.success === step || + SubmitStepEnum.submitting === step) && + tx && } + + {step === SubmitStepEnum.reject && ( + retry + )} + + {step === SubmitStepEnum.error && ( + close + )} + + + ); +}; diff --git a/features/claim/claim-form/types.ts b/features/claim/claim-form/types.ts index df91e904..590d92e1 100644 --- a/features/claim/claim-form/types.ts +++ b/features/claim/claim-form/types.ts @@ -1,3 +1,3 @@ export type ClaimFormSchema = { - recipient: string | null; + recipient: string; }; diff --git a/features/create-vault/create-vault-form/submit-modal/submit-modal.tsx b/features/create-vault/create-vault-form/submit-modal/submit-modal.tsx index 2bcec850..98bf204f 100644 --- a/features/create-vault/create-vault-form/submit-modal/submit-modal.tsx +++ b/features/create-vault/create-vault-form/submit-modal/submit-modal.tsx @@ -21,27 +21,42 @@ import { SubmitStepEnum, SubmitStep } from 'features/create-vault/types'; import { AppPaths } from '../../../../consts/urls'; const getIconComponent = (step: SubmitStep) => { - if (step === SubmitStepEnum.success) - return ; - if (step === SubmitStepEnum.reject) return ; - if (step === SubmitStepEnum.error) return ; - return ; + switch (step) { + case SubmitStepEnum.success: + return ; + case SubmitStepEnum.reject: + return ; + case SubmitStepEnum.error: + return ; + default: + return ; + } }; const getModalTitle = (step: SubmitStep) => { - if (step === SubmitStepEnum.success) return 'New vault has been created'; - if (step === SubmitStepEnum.reject) return 'Wallet tx signature'; - if (step === SubmitStepEnum.error) return 'Simulation error'; - return 'You are creating a new vault'; + switch (step) { + case SubmitStepEnum.success: + return 'New vault has been created'; + case SubmitStepEnum.reject: + return 'Wallet tx signature'; + case SubmitStepEnum.error: + return 'Simulation error'; + default: + return 'You are creating a new vault'; + } }; const getModalSubTitle = (step: SubmitStep) => { - if (step === SubmitStepEnum.submitting) return 'Awaiting wallet signature'; - if (step === SubmitStepEnum.reject) - return 'User denied transaction signature'; - if (step === SubmitStepEnum.error) - return 'Got error when called contract simulation'; - return ''; + switch (step) { + case SubmitStepEnum.submitting: + return 'Awaiting wallet signature'; + case SubmitStepEnum.reject: + return 'User denied transaction signature'; + case SubmitStepEnum.error: + return 'Got error when called contract simulation'; + default: + return ''; + } }; export const SubmitModal: FC = () => { diff --git a/features/home/all-vaults/all-vaults.tsx b/features/home/all-vaults/all-vaults.tsx index 321715e4..625e7b9a 100644 --- a/features/home/all-vaults/all-vaults.tsx +++ b/features/home/all-vaults/all-vaults.tsx @@ -1,17 +1,19 @@ import { Loader, Pagination } from '@lidofinance/lido-ui'; import { VaultTable } from 'features/home/components/vault-table'; import { AllVaultsWrapper } from './styles'; -import { useVaultsDataAll } from 'modules/vaults'; +import { useVaultsDataAll, VAULTS_PER_PAGE } from 'modules/vaults'; +import { useConnectedVaultsNumber } from 'features/home/hooks'; export const AllVaults = () => { const { vaults, isLoading: isLoadingAllVaults, - pagesCount, handlePagination, page, } = useVaultsDataAll(); - const showPagination = !!pagesCount; + const { data } = useConnectedVaultsNumber(); + const pagesCount = Math.ceil(Number(data ?? 0) / VAULTS_PER_PAGE); + const showPagination = !!vaults && vaults?.length > 0 && pagesCount > 1; return ( diff --git a/features/home/hooks/index.ts b/features/home/hooks/index.ts new file mode 100644 index 00000000..ff8b55a6 --- /dev/null +++ b/features/home/hooks/index.ts @@ -0,0 +1 @@ +export { useConnectedVaultsNumber } from './use-connected-vaults-number'; diff --git a/features/home/hooks/use-connected-vaults-number.ts b/features/home/hooks/use-connected-vaults-number.ts new file mode 100644 index 00000000..8a250cc6 --- /dev/null +++ b/features/home/hooks/use-connected-vaults-number.ts @@ -0,0 +1,19 @@ +import { usePublicClient, useReadContract } from 'wagmi'; +import { getContractAddress } from 'config'; +import { VaultHubAbi } from 'abi/vault-hub'; + +export const useConnectedVaultsNumber = () => { + const publicClient = usePublicClient(); + const address = publicClient?.chain.id + ? getContractAddress(publicClient.chain.id, 'vaultHub') + : undefined; + + return useReadContract({ + address, + abi: VaultHubAbi, + functionName: 'vaultsCount', + query: { + enabled: !!address, + }, + }); +}; diff --git a/features/settings/permissions/consts.ts b/features/settings/permissions/consts.ts index 600107dc..cfe3c26f 100644 --- a/features/settings/permissions/consts.ts +++ b/features/settings/permissions/consts.ts @@ -82,12 +82,12 @@ export const editPermissionsSchema = z.object({ export const adminPermissionsList: PermissionsRoles[] = [ { role: 'FUND_ROLE', - title: 'Mint ETH', - tooltip: 'Allows Funding ETH', + title: 'Supply ETH', + tooltip: 'Allows Supplying ETH', }, { role: 'WITHDRAW_ROLE', - title: 'Repay ETH', + title: 'Withdraw ETH', tooltip: 'Allows Withdrawing unlocked ETH from stVault', }, { @@ -126,8 +126,8 @@ export const adminPermissionsList: PermissionsRoles[] = [ }, { role: 'BURN_ROLE', - title: 'Burn stETH', - tooltip: 'Allows Burning stETH', + title: 'Repay stETH', + tooltip: 'Allows Repaying stETH', }, { role: 'VOLUNTARY_DISCONNECT_ROLE', diff --git a/features/supply/fund/form/balance/balance.tsx b/features/supply/fund/form/balance/balance.tsx index db4cd94c..defd0d9e 100644 --- a/features/supply/fund/form/balance/balance.tsx +++ b/features/supply/fund/form/balance/balance.tsx @@ -8,7 +8,9 @@ import { formatBalance } from 'utils'; export const Balance = () => { const { address } = useDappStatus(); - const { data, isLoading, isSuccess, isError } = useBalance({ address }); + const { data, isLoading, isSuccess, isError } = useBalance({ + address, + }); return ( @@ -20,7 +22,7 @@ export const Balance = () => { {isSuccess && !isLoading && ( {formatBalance(data.value).trimmed} ETH )} - {isError && Balance is not available} + {isError && !isLoading && -} ); diff --git a/features/supply/fund/form/feature-tx-info/feature-tx-info.tsx b/features/supply/fund/form/feature-tx-info/feature-tx-info.tsx index 46647392..c51b6cc9 100644 --- a/features/supply/fund/form/feature-tx-info/feature-tx-info.tsx +++ b/features/supply/fund/form/feature-tx-info/feature-tx-info.tsx @@ -1,32 +1,21 @@ import { Text, Loader } from '@lidofinance/lido-ui'; import { useFormContext } from 'react-hook-form'; import { useSimulationFund } from 'features/supply/fund/hooks'; +import { useVaultInfo } from 'features/overview/contexts'; import { AmountInfo, InfoRow, Wrapper } from './styles'; -import { useVaultInfo } from 'features/overview/contexts'; export const FeatureTxInfo = () => { - // TODO: simulate tx, add tx price, convert ETH to stEth - // TODO: add question info - const { getValues } = useFormContext(); - const { amount } = getValues(); + const { watch } = useFormContext(); + const amount: bigint | undefined = watch('amount'); const { activeVault } = useVaultInfo(); const { data, isLoading, isError } = useSimulationFund({ - address: activeVault?.address, - amount: amount ?? 0, + address: activeVault?.owner, + amount: amount ?? 0n, }); return ( - {/* - - You will receive - - - {'50 stETH'} - - - */} Transaction cost @@ -37,8 +26,10 @@ export const FeatureTxInfo = () => { Can't load gas simulation info )} - {data && {'$99.99'}} - {!data && !isError && !isLoading && -} + {!!data?.result && !isLoading && ( + {data?.result} + )} + {!data?.result && !isError && !isLoading && -} ); diff --git a/features/supply/fund/form/submit-button/submit-button.tsx b/features/supply/fund/form/submit-button/submit-button.tsx index 91ffa090..ba1f82ab 100644 --- a/features/supply/fund/form/submit-button/submit-button.tsx +++ b/features/supply/fund/form/submit-button/submit-button.tsx @@ -5,6 +5,7 @@ export const SubmitButton = () => { const { formState: { isSubmitting, isValid, isDirty }, } = useFormContext(); + return ( = ({ children }) => { + const [submitStep, setSubmitStep] = useState<{ + step: SubmitStep; + tx?: Address; + }>(() => ({ step: SubmitStepEnum.edit })); const formObject = useForm({ defaultValues: { amount: undefined, @@ -20,16 +30,38 @@ export const FundFormProvider: FC<{ children: ReactNode }> = ({ children }) => { const { callVaultFund } = useFund(); const { retryEvent, retryFire } = useFormControllerRetry(); + const setModalState = useCallback( + (submitStep: { step: SubmitStep; tx?: Address }) => { + setSubmitStep(submitStep); + }, + [], + ); const onSubmit = useCallback( async ({ amount }: FundFormSchema) => { - if (amount) { - await callVaultFund(amount); - return true; + try { + if (amount) { + setModalState({ step: SubmitStepEnum.initiate }); + const tx = await callVaultFund(amount, setModalState); + setModalState({ step: SubmitStepEnum.success, tx }); + return true; + } + } catch (err) { + if ( + err instanceof Error && + err.message.includes('User rejected the request') + ) { + setModalState({ step: SubmitStepEnum.reject }); + } else { + setModalState({ step: SubmitStepEnum.error }); + } + + return false; } return false; }, + // eslint-disable-next-line react-hooks/exhaustive-deps [callVaultFund], ); @@ -48,6 +80,7 @@ export const FundFormProvider: FC<{ children: ReactNode }> = ({ children }) => { {children} + ); diff --git a/features/supply/fund/hooks/use-fund.ts b/features/supply/fund/hooks/use-fund.ts index 390df902..cb6bbad5 100644 --- a/features/supply/fund/hooks/use-fund.ts +++ b/features/supply/fund/hooks/use-fund.ts @@ -1,9 +1,8 @@ import { useCallback } from 'react'; import { + usePublicClient, useSimulateContract, - useWaitForTransactionReceipt, useWriteContract, - UseSimulateContractParameters, useAccount, } from 'wagmi'; import { Address } from 'viem'; @@ -12,9 +11,14 @@ import { dashboardAbi } from 'abi/dashboard-abi'; import { useVaultInfo } from 'features/overview/contexts'; import invariant from 'tiny-invariant'; import { useVaultPermissions } from 'modules/vaults/hooks/use-vault-permissions'; +import { + SubmitStep, + SubmitStepEnum, +} from 'shared/components/submit-modal/types'; export const useFund = (onMutate = () => {}) => { const { activeVault } = useVaultInfo(); + const publicClient = usePublicClient(); const { data: fundTx, writeContractAsync } = useWriteContract({ mutation: { @@ -22,27 +26,41 @@ export const useFund = (onMutate = () => {}) => { }, }); - const { data: fundReceipt } = useWaitForTransactionReceipt({ - hash: fundTx, - }); - const callVaultFund = useCallback( - async (amount: bigint) => { - invariant(activeVault?.owner, 'activeVault?.owner is undefined'); - return writeContractAsync({ + async ( + amount: bigint, + setModalState: (submitStep: { step: SubmitStep; tx?: Address }) => void, + ) => { + invariant( + activeVault?.owner, + '[useFundWithDashboard] owner is undefined', + ); + invariant( + publicClient, + '[useFundWithDashboard] publicClient is undefined', + ); + + setModalState({ step: SubmitStepEnum.confirming }); + const tx = await writeContractAsync({ abi: dashboardAbi, address: activeVault.owner, functionName: 'fund', value: amount, }); + + setModalState({ step: SubmitStepEnum.submitting, tx }); + await publicClient.waitForTransactionReceipt({ + hash: tx, + }); + + return tx; }, - [writeContractAsync, activeVault?.owner], + [writeContractAsync, activeVault?.owner, publicClient], ); return { callVaultFund, fundTx, - fundReceipt, }; }; @@ -54,16 +72,15 @@ export type SimulationFundProps = { export const useSimulationFund = ({ address, amount }: SimulationFundProps) => { const { address: accountAddress } = useAccount(); const { hasPermission } = useVaultPermissions('funder'); - const simulationContractPayload: UseSimulateContractParameters = { + + return useSimulateContract({ abi: dashboardAbi, address, account: accountAddress, functionName: 'fund', value: amount, query: { - enabled: !!address && hasPermission, + enabled: !!address && hasPermission && !!amount, }, - }; - - return useSimulateContract(simulationContractPayload); + }); }; diff --git a/features/supply/withdraw/form/balance/balance.tsx b/features/supply/withdraw/form/balance/balance.tsx index 14fba951..9632dbff 100644 --- a/features/supply/withdraw/form/balance/balance.tsx +++ b/features/supply/withdraw/form/balance/balance.tsx @@ -24,7 +24,7 @@ export const Balance = () => { {formatBalance(withdrawableAmount).trimmed} ETH )} - {isWithdrawableError && ( + {isWithdrawableError && !isWithdrawableLoading && ( Balance is not available )} diff --git a/features/supply/withdraw/form/feature-tx-info/feature-tx-info.tsx b/features/supply/withdraw/form/feature-tx-info/feature-tx-info.tsx index 043ac549..a5c7250d 100644 --- a/features/supply/withdraw/form/feature-tx-info/feature-tx-info.tsx +++ b/features/supply/withdraw/form/feature-tx-info/feature-tx-info.tsx @@ -3,6 +3,7 @@ import { useSimulateWithdrawDashboard } from 'features/supply/withdraw/hooks'; import { Loader, Text } from '@lidofinance/lido-ui'; import { AmountInfo, InfoRow, Wrapper } from './styles'; +import { formatBalance } from 'utils'; export const FeatureTxInfo = () => { const { watch } = useFormContext(); @@ -15,10 +16,10 @@ export const FeatureTxInfo = () => { return ( - {/* + You will receive - {'50 ETH'} */} + {formatBalance(amount ?? 0n).trimmed} ETH @@ -30,9 +31,10 @@ export const FeatureTxInfo = () => { Can't load gas simulation info )} - {/*TODO: replace by real data*/} - {data && {'$99.99'}} - {!data && !isError && !isLoading && -} + {!!data?.result && !isLoading && ( + {data?.result} + )} + {!data?.result && !isError && !isLoading && -} ); diff --git a/features/supply/withdraw/hooks/use-withdraw-with-dashboard.ts b/features/supply/withdraw/hooks/use-withdraw-with-dashboard.ts index 663d8240..51a2f8bd 100644 --- a/features/supply/withdraw/hooks/use-withdraw-with-dashboard.ts +++ b/features/supply/withdraw/hooks/use-withdraw-with-dashboard.ts @@ -1,9 +1,5 @@ import { useCallback } from 'react'; -import { - useSimulateContract, - useWaitForTransactionReceipt, - useWriteContract, -} from 'wagmi'; +import { usePublicClient, useSimulateContract, useWriteContract } from 'wagmi'; import { Address } from 'viem'; import { dashboardAbi } from 'abi/dashboard-abi'; @@ -11,14 +7,20 @@ import { useDappStatus } from 'modules/web3/hooks/use-dapp-status'; import { useVaultInfo } from 'features/overview/contexts'; import invariant from 'tiny-invariant'; import { useVaultPermissions } from 'modules/vaults/hooks/use-vault-permissions'; +import { + SubmitStep, + SubmitStepEnum, +} from 'shared/components/submit-modal/types'; type WithdrawWithDashboardArgs = { recipient: Address; amount: bigint; + setModalState: (submitStep: { step: SubmitStep; tx?: Address }) => void; }; export const useWithdrawWithDashboard = (onMutate = () => {}) => { const { activeVault } = useVaultInfo(); + const publicClient = usePublicClient(); const { data: withdrawTx, writeContractAsync } = useWriteContract({ mutation: { @@ -26,27 +28,38 @@ export const useWithdrawWithDashboard = (onMutate = () => {}) => { }, }); - const { data: withdrawReceipt } = useWaitForTransactionReceipt({ - hash: withdrawTx, - }); - const callWithdraw = useCallback( - async ({ amount, recipient }: WithdrawWithDashboardArgs) => { - invariant(activeVault, 'activeVault is undefined'); - return await writeContractAsync({ + async ({ amount, recipient, setModalState }: WithdrawWithDashboardArgs) => { + invariant( + activeVault, + '[useWithdrawWithDashboard] activeVault is undefined', + ); + invariant( + publicClient, + '[useFundWithDashboard] publicClient is undefined', + ); + + setModalState({ step: SubmitStepEnum.confirming }); + const tx = await writeContractAsync({ abi: dashboardAbi, address: activeVault.owner, functionName: 'withdraw', args: [recipient, amount], }); + + setModalState({ step: SubmitStepEnum.submitting, tx }); + await publicClient.waitForTransactionReceipt({ + hash: tx, + }); + + return tx; }, - [activeVault, writeContractAsync], + [activeVault, writeContractAsync, publicClient], ); return { callWithdraw, withdrawTx, - withdrawReceipt, }; }; diff --git a/features/supply/withdraw/withdraw-form-context/withdraw-form-provider.tsx b/features/supply/withdraw/withdraw-form-context/withdraw-form-provider.tsx index 6122b75a..b9001a95 100644 --- a/features/supply/withdraw/withdraw-form-context/withdraw-form-provider.tsx +++ b/features/supply/withdraw/withdraw-form-context/withdraw-form-provider.tsx @@ -5,24 +5,30 @@ import { useCallback, createContext, useContext, + useState, } from 'react'; import { FormProvider, useForm } from 'react-hook-form'; import { Address, isAddress } from 'viem'; import invariant from 'tiny-invariant'; import { useFormControllerRetry } from 'shared/hook-form/form-controller/use-form-controller-retry-delegate'; +import { + useWithdrawable, + useWithdrawWithDashboard, +} from 'features/supply/withdraw/hooks'; import { FormController, FormControllerContext, FormControllerContextValueType, } from 'shared/hook-form/form-controller'; +import { SubmitModal } from 'shared/components'; import { WithdrawFormSchema } from 'features/supply/withdraw/types'; import { - useWithdrawable, - useWithdrawWithDashboard, -} from 'features/supply/withdraw/hooks'; + SubmitStep, + SubmitStepEnum, +} from 'shared/components/submit-modal/types'; type WithdrawDataContextValue = { withdrawableAmount: bigint | undefined; @@ -49,6 +55,10 @@ export const useWithdrawFormData = () => { export const WithdrawFormProvider: FC<{ children: ReactNode }> = ({ children, }) => { + const [submitStep, setSubmitStep] = useState<{ + step: SubmitStep; + tx?: Address; + }>(() => ({ step: SubmitStepEnum.edit })); const formObject = useForm({ defaultValues: { amount: undefined, @@ -59,6 +69,12 @@ export const WithdrawFormProvider: FC<{ children: ReactNode }> = ({ }); const { callWithdraw } = useWithdrawWithDashboard(); const { retryEvent, retryFire } = useFormControllerRetry(); + const setModalState = useCallback( + (submitStep: { step: SubmitStep; tx?: Address }) => { + setSubmitStep(submitStep); + }, + [], + ); const { data: withdrawableAmount, @@ -86,15 +102,27 @@ export const WithdrawFormProvider: FC<{ children: ReactNode }> = ({ async ({ amount, recipient }: WithdrawFormSchema) => { try { if (amount && recipient && isAddress(recipient)) { - await callWithdraw({ amount, recipient }); + setModalState({ step: SubmitStepEnum.initiate }); + const tx = await callWithdraw({ amount, recipient, setModalState }); + setModalState({ step: SubmitStepEnum.success, tx }); return true; } - } catch (e) { + } catch (err) { + if ( + err instanceof Error && + err.message.includes('User rejected the request') + ) { + setModalState({ step: SubmitStepEnum.reject }); + } else { + setModalState({ step: SubmitStepEnum.error }); + } + return false; } return false; }, + // eslint-disable-next-line react-hooks/exhaustive-deps [callWithdraw], ); @@ -114,6 +142,7 @@ export const WithdrawFormProvider: FC<{ children: ReactNode }> = ({ {children} + diff --git a/modules/vaults/hooks/use-vaults-data-all.ts b/modules/vaults/hooks/use-vaults-data-all.ts index 3d18b028..2b47e750 100644 --- a/modules/vaults/hooks/use-vaults-data-all.ts +++ b/modules/vaults/hooks/use-vaults-data-all.ts @@ -3,7 +3,7 @@ import { useState, useCallback, useMemo } from 'react'; import { useVaultData } from 'modules/vaults/hooks/use-vault-data'; import { useVaultsConnectedBound } from 'modules/vaults/hooks/use-vaults-connected-bound'; -import { VAULTS_PER_PAGE } from '../consts'; +import { VAULTS_PER_PAGE } from 'modules/vaults/consts'; import type { Address } from 'viem'; diff --git a/shared/components/index.ts b/shared/components/index.ts index 0cb66663..80b70f0f 100644 --- a/shared/components/index.ts +++ b/shared/components/index.ts @@ -16,3 +16,4 @@ export { VaultImpactDashboard } from './vault-impact-dashboard'; export { VaultImpactValuation } from './vault-impact-valuation'; export { InputAmount } from './input-amount'; export { Chip } from './chip'; +export { SubmitModal } from './submit-modal'; diff --git a/shared/components/submit-modal/index.ts b/shared/components/submit-modal/index.ts new file mode 100644 index 00000000..09ec63ec --- /dev/null +++ b/shared/components/submit-modal/index.ts @@ -0,0 +1,2 @@ +export { SubmitModal } from './submit-modal'; +export * from './types'; diff --git a/shared/components/submit-modal/styles.ts b/shared/components/submit-modal/styles.ts new file mode 100644 index 00000000..dfca0ac4 --- /dev/null +++ b/shared/components/submit-modal/styles.ts @@ -0,0 +1,8 @@ +import styled from 'styled-components'; + +export const Content = styled.div` + display: flex; + flex-direction: column; + gap: ${({ theme }) => theme.spaceMap.xl}px; + margin-top: ${({ theme }) => theme.spaceMap.sm}px; +`; diff --git a/shared/components/submit-modal/submit-modal.tsx b/shared/components/submit-modal/submit-modal.tsx new file mode 100644 index 00000000..3c201f74 --- /dev/null +++ b/shared/components/submit-modal/submit-modal.tsx @@ -0,0 +1,142 @@ +import { FC, useMemo } from 'react'; +import { useRouter } from 'next/router'; +import { useFormContext } from 'react-hook-form'; + +import { + Loader, + Modal, + Text, + Success, + Error, + type ModalProps, + Button, +} from '@lidofinance/lido-ui'; + +import { useFormControllerContext } from 'shared/hook-form/form-controller'; +import { ButtonLink, TxLinkEtherscan } from 'shared/components'; +import { Content } from './styles'; + +import { + SubmitStepEnum, + SubmitStep, +} from 'shared/components/submit-modal/types'; +import { AppPaths } from 'consts/urls'; +import { useVaultInfo } from 'features/overview/contexts'; +import { Address } from 'viem'; + +const getIconComponent = (step: SubmitStep) => { + switch (step) { + case SubmitStepEnum.success: + return ; + case SubmitStepEnum.reject: + return ; + case SubmitStepEnum.error: + return ; + default: + return ; + } +}; + +const getModalTitle = (step: SubmitStep) => { + switch (step) { + case SubmitStepEnum.success: + return 'Transaction was finished successfully'; + case SubmitStepEnum.reject: + return 'Wallet tx signature'; + case SubmitStepEnum.error: + return 'Transaction error'; + default: + return 'Transaction'; + } +}; + +const getModalSubTitle = (step: SubmitStep) => { + switch (step) { + case SubmitStepEnum.confirming: + return 'Awaiting wallet signature'; + case SubmitStepEnum.submitting: + return 'Awaiting block confirmation'; + case SubmitStepEnum.reject: + return 'User denied transaction signature'; + case SubmitStepEnum.error: + return 'Got error when called contract simulation or transaction'; + default: + return ''; + } +}; + +interface SubmitModalProps extends ModalProps { + submitStep: { + step: SubmitStep; + tx?: Address; + }; + setModalState: ({ step }: { step: SubmitStep }) => void; +} + +export const SubmitModal: FC = ({ + submitStep, + setModalState, +}) => { + const router = useRouter(); + const { + formState: { isSubmitting, isSubmitted }, + } = useFormContext(); + const { retryFire } = useFormControllerContext(); + const { step, tx } = submitStep ?? {}; + const { activeVault } = useVaultInfo(); + + const iconComponent = useMemo(() => getIconComponent(step), [step]); + const title = getModalTitle(step); + const subtitle = getModalSubTitle(step); + + const handleNavigateToVault = () => { + void router.push(`/${activeVault?.address}/${AppPaths.overview}`); + }; + + const handleCloseModal = () => { + setModalState({ step: SubmitStepEnum.edit }); + }; + + return ( + + + {step === SubmitStepEnum.initiate && ( + + Wait for wallet confirmation window + + )} + + {step === SubmitStepEnum.confirming && ( + + Confirm this transaction in your wallet + + )} + + {step === SubmitStepEnum.success && ( + + )} + + {(SubmitStepEnum.success === step || + SubmitStepEnum.submitting === step) && + tx && } + + {step === SubmitStepEnum.reject && ( + retry + )} + + {step === SubmitStepEnum.error && ( + close + )} + + + ); +}; diff --git a/shared/components/submit-modal/types.ts b/shared/components/submit-modal/types.ts new file mode 100644 index 00000000..3fe8c3b3 --- /dev/null +++ b/shared/components/submit-modal/types.ts @@ -0,0 +1,11 @@ +export enum SubmitStepEnum { + edit = 'edit', + initiate = 'initiate', + confirming = 'confirming', + reject = 'reject', + error = 'error', + submitting = 'submitting', + success = 'success', +} + +export type SubmitStep = keyof typeof SubmitStepEnum;