diff --git a/packages/neuron-ui/src/components/ApproveMultisigTxDialog/index.tsx b/packages/neuron-ui/src/components/ApproveMultisigTxDialog/index.tsx index 71adb6570c..0aa3544869 100644 --- a/packages/neuron-ui/src/components/ApproveMultisigTxDialog/index.tsx +++ b/packages/neuron-ui/src/components/ApproveMultisigTxDialog/index.tsx @@ -13,28 +13,25 @@ import getMultisigSignStatus from 'utils/getMultisigSignStatus' import { useBroadcast, useExport, useSignAndBroadcast, useSignAndExport, useTabView } from './hooks' import styles from './approveMultisigTx.module.scss' -interface CellProps { - lock: CKBComponents.Script - type?: CKBComponents.Script - data?: string - capacity: string - lockHash: string -} - -const Cell = React.memo(({ cell, isMainnet }: { cell: CellProps; isMainnet: boolean }) => { - const address = useMemo(() => ckbCore.utils.scriptToAddress(cell.lock, isMainnet), [cell, isMainnet]) - return ( -
-
- {address.slice(0, 6)}...{address.slice(-6)} - Type - Data - +const Cell = React.memo( + ({ cell, isMainnet }: { cell: State.DetailedInput | State.DetailedOutput; isMainnet: boolean }) => { + const address = useMemo( + () => (cell.lock ? ckbCore.utils.scriptToAddress(cell.lock, isMainnet) : ''), + [cell, isMainnet] + ) + return ( +
+
+ {address.slice(0, 6)}...{address.slice(-6)} + Type + Data + +
+
{`${shannonToCKBFormatter(cell.capacity ?? '0')} CKB`}
-
{`${shannonToCKBFormatter(cell.capacity)} CKB`}
-
- ) -}) + ) + } +) const ApproveMultisigTxDialog = ({ multisigConfig, closeDialog, @@ -152,12 +149,12 @@ const ApproveMultisigTxDialog = ({

Inputs

- {offlineSignJson.transaction?.inputs?.map((input: CellProps) => ( + {offlineSignJson.transaction?.inputs?.map(input => ( ))}

Outputs

- {offlineSignJson.transaction?.outputs?.map((output: CellProps) => ( + {offlineSignJson.transaction?.outputs?.map(output => ( ))}
diff --git a/packages/neuron-ui/src/components/DepositDialog/depositDialog.module.scss b/packages/neuron-ui/src/components/DepositDialog/depositDialog.module.scss index 46c18e26ac..9ffadbba2d 100644 --- a/packages/neuron-ui/src/components/DepositDialog/depositDialog.module.scss +++ b/packages/neuron-ui/src/components/DepositDialog/depositDialog.module.scss @@ -44,13 +44,19 @@ font-size: 14px; line-height: 20px; color: var(--main-text-color); + + & > div { + &:nth-last-child(1) { + width: 230px; + text-align: right; + } + } } .notice { $lineHeight: 24px; display: flex; box-sizing: border-box; - max-width: 648px; margin-top: 22px; border: 1px solid rgba(252, 136, 0, 0.2); padding: 7px 54px; @@ -65,7 +71,7 @@ & > svg { $size: 14px; flex-shrink: 0; - margin-top: ($lineHeight - $size) / 2; + margin: calc(($lineHeight - $size) / 2) 4px 0 0; width: $size; height: $size; diff --git a/packages/neuron-ui/src/components/DepositDialog/hooks.ts b/packages/neuron-ui/src/components/DepositDialog/hooks.ts new file mode 100644 index 0000000000..27d6b35794 --- /dev/null +++ b/packages/neuron-ui/src/components/DepositDialog/hooks.ts @@ -0,0 +1,270 @@ +import { isErrorWithI18n } from 'exceptions' +import { TFunction } from 'i18next' +import { Dispatch, SetStateAction, useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { + generateDaoDepositAllTx as generateDaoDepositAllTxAPI, + generateDaoDepositTx as generateDaoDepositTxAPI, +} from 'services/remote' +import { AppActions, useDispatch } from 'states' +import { + CKBToShannonFormatter, + ErrorCode, + isSuccessResponse, + padFractionDigitsIfDecimal, + shannonToCKBFormatter, + useClearGeneratedTx, + validateAmount, +} from 'utils' +import { MAX_DECIMAL_DIGITS, MIN_DEPOSIT_AMOUNT, SHANNON_CKB_RATIO } from 'utils/const' + +const PERCENT_100 = 100 + +function checkDepositValue(depositValue: string, t: TFunction): string | undefined { + try { + validateAmount(depositValue) + } catch (err) { + if (isErrorWithI18n(err)) { + return t(`messages.codes.${err.code}`, { + fieldName: 'deposit', + fieldValue: depositValue, + length: MAX_DECIMAL_DIGITS, + }) + } + return undefined + } + if (BigInt(CKBToShannonFormatter(depositValue)) < BigInt(MIN_DEPOSIT_AMOUNT * SHANNON_CKB_RATIO)) { + return t('nervos-dao.minimal-fee-required', { minimal: MIN_DEPOSIT_AMOUNT }) + } + return undefined +} + +function generateDaoDepositTx({ + walletID, + capacity, + suggestFeeRate, + t, +}: { + walletID: string + capacity: string + suggestFeeRate: number + t: TFunction +}): Promise { + return generateDaoDepositTxAPI({ + feeRate: `${suggestFeeRate}`, + capacity, + walletID, + }).then(res => { + if (isSuccessResponse(res)) { + return res.result + } + if (res.status === 0) { + throw new Error(`${typeof res.message === 'string' ? res.message : res.message.content}`) + } else if (res.status === ErrorCode.CapacityNotEnoughForChange) { + throw new Error(t(`messages.codes.106`)) + } else { + throw new Error(t(`messages.codes.${res.status}`)) + } + }) +} + +function generateDaoDepositAllTx({ + suggestFeeRate, + isBalanceReserved, + walletID, +}: { + suggestFeeRate: number + isBalanceReserved: boolean + walletID: string +}): Promise { + return generateDaoDepositAllTxAPI({ + walletID, + feeRate: `${suggestFeeRate}`, + isBalanceReserved, + }).then(res => { + if (isSuccessResponse(res)) { + return res.result + } + throw new Error(`${typeof res.message === 'string' ? res.message : res.message.content}`) + }) +} + +export const useGenerateDaoDepositTx = ({ + walletID, + isBalanceReserved, + depositValue, + suggestFeeRate, + showDepositDialog, + slidePercent, +}: { + walletID: string + isBalanceReserved: boolean + depositValue: string + suggestFeeRate: number + showDepositDialog: boolean + slidePercent: number +}) => { + const timer = useRef>() + const [errorMessage, setErrorMessage] = useState('') + const [maxDepositValue, setMaxDepositValue] = useState() + const [t] = useTranslation() + const dispatch = useDispatch() + const clearGeneratedTx = useClearGeneratedTx() + const isDepositAll = useMemo(() => slidePercent === PERCENT_100, [slidePercent]) + useEffect(() => { + clearTimeout(timer.current) + if (!showDepositDialog) { + return + } + timer.current = setTimeout(() => { + setErrorMessage('') + const errorDepositValue = checkDepositValue(depositValue, t) + if (errorDepositValue) { + clearGeneratedTx() + setErrorMessage(errorDepositValue) + return + } + + const generateDaoDepositResult: Promise = isDepositAll + ? generateDaoDepositAllTx({ walletID, isBalanceReserved, suggestFeeRate }) + : generateDaoDepositTx({ walletID, capacity: CKBToShannonFormatter(depositValue), suggestFeeRate, t }) + generateDaoDepositResult + .then(res => { + dispatch({ + type: AppActions.UpdateGeneratedTx, + payload: res, + }) + if (isDepositAll) { + setMaxDepositValue(shannonToCKBFormatter(res?.outputs[0]?.capacity ?? '0', false, '')) + if (!isBalanceReserved) { + setErrorMessage(t('messages.remain-ckb-for-withdraw')) + } + } + }) + .catch((err: unknown) => { + clearGeneratedTx() + setErrorMessage(err instanceof Error ? err.message : '') + }) + }) + }, [ + clearGeneratedTx, + dispatch, + walletID, + t, + setErrorMessage, + isBalanceReserved, + depositValue, + suggestFeeRate, + showDepositDialog, + isDepositAll, + ]) + return { + errorMessage, + maxDepositValue: isDepositAll ? maxDepositValue ?? depositValue : null, + } +} + +function calculatePercent(amount: string, total: string) { + if (!total || total === '0') return 0 + return +((BigInt(PERCENT_100) * BigInt(amount)) / BigInt(total)).toString() +} + +export const useDepositValue = (balance: string) => { + const [depositValue, setDepositValue] = useState(`${MIN_DEPOSIT_AMOUNT}`) + const [slidePercent, setSlidePercent] = useState( + calculatePercent(CKBToShannonFormatter(`${MIN_DEPOSIT_AMOUNT}`), balance) + ) + const onSliderChange = useCallback( + (percent: number) => { + setSlidePercent(percent) + const amount = shannonToCKBFormatter( + ((BigInt(percent) * BigInt(balance)) / BigInt(PERCENT_100)).toString(), + false, + '' + ) + setDepositValue(padFractionDigitsIfDecimal(amount, 8)) + }, + [balance] + ) + const onChangeDepositValue = useCallback( + (e: React.SyntheticEvent) => { + const { value } = e.currentTarget + const amount = value.replace(/,/g, '') + if (Number.isNaN(+amount) || /[^\d.]/.test(amount) || +amount < 0) { + return + } + setDepositValue(amount) + try { + validateAmount(amount) + const percent = calculatePercent(CKBToShannonFormatter(amount), balance) + setSlidePercent(percent >= PERCENT_100 ? 100 : percent) + } catch (error) { + // here we can ignore the error, it used to verify amount and set slide percent + } + }, + [setDepositValue, balance] + ) + const resetDepositValue = useCallback(() => { + setDepositValue(`${MIN_DEPOSIT_AMOUNT}`) + setSlidePercent(calculatePercent(CKBToShannonFormatter(`${MIN_DEPOSIT_AMOUNT}`), balance)) + }, [balance]) + return { + onChangeDepositValue, + setDepositValue, + depositValue, + slidePercent, + onSliderChange, + resetDepositValue, + } +} + +export const useBalanceReserved = () => { + const [isBalanceReserved, setIsBalanceReserved] = useState(true) + const onIsBalanceReservedChange = (e: React.SyntheticEvent) => { + setIsBalanceReserved(!e.currentTarget.checked) + } + return { + isBalanceReserved, + onIsBalanceReservedChange, + setIsBalanceReserved, + } +} + +export const useOnDepositDialogSubmit = ({ + onCloseDepositDialog, + walletID, +}: { + onCloseDepositDialog: () => void + walletID: string +}) => { + const dispatch = useDispatch() + return useCallback(() => { + dispatch({ + type: AppActions.RequestPassword, + payload: { + walletID, + actionType: 'send', + }, + }) + onCloseDepositDialog() + }, [dispatch, walletID, onCloseDepositDialog]) +} + +export const useOnDepositDialogCancel = ({ + onCloseDepositDialog, + resetDepositValue, + setIsBalanceReserved, +}: { + onCloseDepositDialog: () => void + resetDepositValue: () => void + setIsBalanceReserved: Dispatch> +}) => { + const dispatch = useDispatch() + const clearGeneratedTx = useClearGeneratedTx() + return useCallback(() => { + onCloseDepositDialog() + resetDepositValue() + setIsBalanceReserved(true) + clearGeneratedTx() + }, [dispatch, onCloseDepositDialog, resetDepositValue, clearGeneratedTx]) +} diff --git a/packages/neuron-ui/src/components/DepositDialog/index.tsx b/packages/neuron-ui/src/components/DepositDialog/index.tsx index 849555ff4d..6737a2f42c 100644 --- a/packages/neuron-ui/src/components/DepositDialog/index.tsx +++ b/packages/neuron-ui/src/components/DepositDialog/index.tsx @@ -1,84 +1,80 @@ -import React, { useEffect, useMemo } from 'react' +import React from 'react' import { Slider } from 'office-ui-fabric-react' -import { useTranslation, Trans } from 'react-i18next' +import { Trans, useTranslation } from 'react-i18next' import TextField from 'widgets/TextField' import Spinner, { SpinnerSize } from 'widgets/Spinner' import { openExternal } from 'services/remote' -import { CONSTANTS, localNumberFormatter, shannonToCKBFormatter } from 'utils' +import { localNumberFormatter, shannonToCKBFormatter } from 'utils' import { Attention, Success } from 'widgets/Icons/icon' import Dialog from 'widgets/Dialog' import styles from './depositDialog.module.scss' - -const { SHANNON_CKB_RATIO } = CONSTANTS +import { + useBalanceReserved, + useDepositValue, + useGenerateDaoDepositTx, + useOnDepositDialogCancel, + useOnDepositDialogSubmit, +} from './hooks' const NERVOS_DAO_RFC_URL = 'https://www.github.com/nervosnetwork/rfcs/blob/master/rfcs/0023-dao-deposit-withdraw/0023-dao-deposit-withdraw.md' interface DepositDialogProps { + balance: string show: boolean - value: any fee: string - onOpen: () => void - onDismiss: () => void - onChange: (e: React.SyntheticEvent) => void - onSubmit: () => void - onSlide: (value: number) => void - maxDepositAmount: bigint + onCloseDepositDialog: () => void isDepositing: boolean - errorMessage: string isTxGenerated: boolean - isBalanceReserved: boolean - onIsBalanceReservedChange: (e: React.SyntheticEvent) => void + suggestFeeRate: number + walletID: string } +const RfcLink = React.memo(() => ( +
diff --git a/packages/neuron-ui/src/components/HardwareSign/index.tsx b/packages/neuron-ui/src/components/HardwareSign/index.tsx index d4351dc020..92c2ec715d 100644 --- a/packages/neuron-ui/src/components/HardwareSign/index.tsx +++ b/packages/neuron-ui/src/components/HardwareSign/index.tsx @@ -460,7 +460,7 @@ const HardwareSign = ({ - {wallet.isHD ? : null} + {wallet.isHD && generatedTx ? : null}