From 7249042cf1a5f884e1f065cc419e2fba0746c334 Mon Sep 17 00:00:00 2001 From: devchenyan Date: Tue, 14 Jan 2025 17:12:38 +0800 Subject: [PATCH] update --- .../src/components/MultisigAddress/hooks.ts | 24 +- .../src/components/MultisigAddress/index.tsx | 74 +++- .../MultisigAddressNervosDAODialog/hooks.ts | 339 ++++++++++++++++++ .../MultisigAddressNervosDAODialog/index.tsx | 213 +++++++++++ ...multisigAddressNervosDAODialog.module.scss | 118 ++++++ .../src/components/NervosDAO/index.tsx | 1 + .../src/components/WithdrawDialog/index.tsx | 4 +- packages/neuron-ui/src/locales/ar.json | 3 +- packages/neuron-ui/src/locales/en.json | 3 +- packages/neuron-ui/src/locales/es.json | 3 +- packages/neuron-ui/src/locales/fr.json | 3 +- packages/neuron-ui/src/locales/zh-tw.json | 3 +- packages/neuron-ui/src/locales/zh.json | 3 +- 13 files changed, 758 insertions(+), 33 deletions(-) create mode 100644 packages/neuron-ui/src/components/MultisigAddressNervosDAODialog/hooks.ts create mode 100644 packages/neuron-ui/src/components/MultisigAddressNervosDAODialog/index.tsx create mode 100644 packages/neuron-ui/src/components/MultisigAddressNervosDAODialog/multisigAddressNervosDAODialog.module.scss diff --git a/packages/neuron-ui/src/components/MultisigAddress/hooks.ts b/packages/neuron-ui/src/components/MultisigAddress/hooks.ts index c234662dc8..55dcb2b2d6 100644 --- a/packages/neuron-ui/src/components/MultisigAddress/hooks.ts +++ b/packages/neuron-ui/src/components/MultisigAddress/hooks.ts @@ -300,44 +300,44 @@ const useApproveAction = () => { const useDaoDepositAction = () => { const [isDialogOpen, setIsDialogOpen] = useState(false) - const [sendFromMultisig, setSendFromMultisig] = useState() - const onOpenSendDialog = useCallback( + const [depositFromMultisig, setDepositFromMultisig] = useState() + const onOpenDialog = useCallback( (option: MultisigConfig) => { setIsDialogOpen(true) - setSendFromMultisig(option) + setDepositFromMultisig(option) }, - [setIsDialogOpen, setSendFromMultisig] + [setIsDialogOpen, setDepositFromMultisig] ) const closeDialog = useCallback(() => { setIsDialogOpen(false) }, [setIsDialogOpen]) return { - action: onOpenSendDialog, + action: onOpenDialog, closeDialog, - sendFromMultisig, + depositFromMultisig, isDialogOpen, } } const useDaoWithdrawAction = () => { const [isDialogOpen, setIsDialogOpen] = useState(false) - const [sendFromMultisig, setSendFromMultisig] = useState() - const onOpenSendDialog = useCallback( + const [withdrawFromMultisig, setWithdrawFromMultisig] = useState() + const onOpenDialog = useCallback( (option: MultisigConfig) => { setIsDialogOpen(true) - setSendFromMultisig(option) + setWithdrawFromMultisig(option) }, - [setIsDialogOpen, setSendFromMultisig] + [setIsDialogOpen, setWithdrawFromMultisig] ) const closeDialog = useCallback(() => { setIsDialogOpen(false) }, [setIsDialogOpen]) return { - action: onOpenSendDialog, + action: onOpenDialog, closeDialog, - sendFromMultisig, + withdrawFromMultisig, isDialogOpen, } } diff --git a/packages/neuron-ui/src/components/MultisigAddress/index.tsx b/packages/neuron-ui/src/components/MultisigAddress/index.tsx index 9182414df8..45b7524236 100644 --- a/packages/neuron-ui/src/components/MultisigAddress/index.tsx +++ b/packages/neuron-ui/src/components/MultisigAddress/index.tsx @@ -6,13 +6,18 @@ import { useExitOnWalletChange, useGoBack, useOnWindowResize, + calculateFee, + useClearGeneratedTx, } from 'utils' -import { useState as useGlobalState } from 'states' +import appState from 'states/init/app' +import { useState as useGlobalState, useDispatch, showPageNotice } from 'states' import MultisigAddressCreateDialog from 'components/MultisigAddressCreateDialog' import MultisigAddressInfo from 'components/MultisigAddressInfo' import SendFromMultisigDialog from 'components/SendFromMultisigDialog' import { MultisigConfig, changeMultisigSyncStatus, openExternal } from 'services/remote' import ApproveMultisigTxDialog from 'components/ApproveMultisigTxDialog' +import DepositDialog from 'components/DepositDialog' +import MultisigAddressNervosDAODialog from 'components/MultisigAddressNervosDAODialog' import Dialog from 'widgets/Dialog' import Table from 'widgets/Table' import Tooltip from 'widgets/Tooltip' @@ -37,9 +42,11 @@ import AttentionCloseDialog from 'widgets/Icons/Attention.png' import { HIDE_BALANCE, NetworkType } from 'utils/const' import { onEnter } from 'utils/inputDevice' import getMultisigSignStatus from 'utils/getMultisigSignStatus' +import useGetCountDownAndFeeRateStats from 'utils/hooks/useGetCountDownAndFeeRateStats' import Button from 'widgets/Button' import SetStartBlockNumberDialog from 'components/SetStartBlockNumberDialog' import { type TFunction } from 'i18next' +import hooks from 'components/NervosDAO/hooks' import { useSearch, useConfigManage, @@ -97,14 +104,19 @@ const MultisigAddress = () => { const [t] = useTranslation() useExitOnWalletChange() const { - wallet: { id: walletId, addresses }, + app: { + send = appState.send, + loadings: { sending = false }, + }, + wallet, chain: { - syncState: { bestKnownBlockNumber }, + syncState: { bestKnownBlockNumber, bestKnownBlockTimestamp }, networkID, connectionStatus, }, settings: { networks = [] }, } = useGlobalState() + const { id: walletId, addresses, balance } = wallet const isMainnet = isMainnetUtil(networks, networkID) const isLightClient = useMemo( () => networks.find(n => n.id === networkID)?.type === NetworkType.Light, @@ -134,6 +146,11 @@ const MultisigAddress = () => { deleteConfigById, }) const [showDeleteDialog, setShowDeleteDialog] = useState(false) + const { suggestFeeRate } = useGetCountDownAndFeeRateStats() + const clearGeneratedTx = useClearGeneratedTx() + const dispatch = useDispatch() + const [globalAPC, setGlobalAPC] = useState(0) + const [genesisBlockTimestamp, setGenesisBlockTimestamp] = useState(undefined) const onClickItem = useCallback( (multisigConfig: MultisigConfig) => (e: React.SyntheticEvent) => { @@ -237,6 +254,42 @@ const MultisigAddress = () => { }, [updateTipPosition]) useOnWindowResize(updateTipPosition) + const genesisBlockHash = useMemo(() => networks.find(v => v.id === networkID)?.genesisHash, [networkID, networks]) + hooks.useInitData({ + clearGeneratedTx, + dispatch, + wallet, + setGenesisBlockTimestamp, + genesisBlockHash, + }) + hooks.useUpdateGlobalAPC({ bestKnownBlockTimestamp, genesisBlockTimestamp, setGlobalAPC }) + + const fee = `${shannonToCKBFormatter( + send.generatedTx ? send.generatedTx.fee || calculateFee(send.generatedTx) : '0' + )} CKB` + + const onDepositSuccess = useCallback(() => { + daoDepositAction.closeDialog() + showPageNotice('nervos-dao.deposit-submitted')(dispatch) + }, [dispatch, daoDepositAction.closeDialog]) + + const MemoizedDepositDialog = useMemo(() => { + return ( + + ) + }, [balance, walletId, fee, sending, send.generatedTx, suggestFeeRate, globalAPC, daoDepositAction]) + return (
{ /> ) : null} - {daoDepositAction.sendFromMultisig && daoDepositAction.isDialogOpen ? ( - - ) : null} + {MemoizedDepositDialog} - {daoWithdrawAction.sendFromMultisig && daoWithdrawAction.isDialogOpen ? ( - ) : null}
diff --git a/packages/neuron-ui/src/components/MultisigAddressNervosDAODialog/hooks.ts b/packages/neuron-ui/src/components/MultisigAddressNervosDAODialog/hooks.ts new file mode 100644 index 0000000000..95134739f7 --- /dev/null +++ b/packages/neuron-ui/src/components/MultisigAddressNervosDAODialog/hooks.ts @@ -0,0 +1,339 @@ +import { useEffect, useCallback, useMemo } from 'react' +import { AppActions, StateAction } from 'states/stateProvider/reducer' +import { updateNervosDaoData, clearNervosDaoData, showGlobalAlertDialog } from 'states/stateProvider/actionCreators' + +import { NavigateFunction } from 'react-router-dom' +import { type CKBComponents } from '@ckb-lumos/lumos/rpc' +import { CONSTANTS, isSuccessResponse, RoutePath } from 'utils' + +import { rpc, getHeader } from 'services/chain' +import { generateDaoWithdrawTx, generateDaoClaimTx, MultisigConfig } from 'services/remote' +import { calculateMaximumWithdrawCompatible } from '@ckb-lumos/lumos/common-scripts/dao' + +const { MEDIUM_FEE_RATE } = CONSTANTS + +const getRecordKey = ({ depositOutPoint, outPoint }: State.NervosDAORecord) => { + return depositOutPoint ? `${depositOutPoint.txHash}-${depositOutPoint.index}` : `${outPoint.txHash}-${outPoint.index}` +} + +export const useInitData = ({ + clearGeneratedTx, + dispatch, + wallet, + setGenesisBlockTimestamp, + genesisBlockHash, +}: { + clearGeneratedTx: () => void + dispatch: React.Dispatch + wallet: State.Wallet + setGenesisBlockTimestamp: React.Dispatch> + genesisBlockHash?: string +}) => + useEffect(() => { + updateNervosDaoData({ walletID: wallet.id })(dispatch) + const intervalId = setInterval(() => { + updateNervosDaoData({ walletID: wallet.id })(dispatch) + }, 10000) + if (genesisBlockHash) { + getHeader(genesisBlockHash) + .then(header => setGenesisBlockTimestamp(+header.timestamp)) + .catch(err => console.error(err)) + } + return () => { + clearInterval(intervalId) + clearNervosDaoData()(dispatch) + clearGeneratedTx() + } + // eslint-disable-next-line + }, []) + +export const useOnWithdrawDialogDismiss = (setActiveRecord: React.Dispatch) => + useCallback(() => { + setActiveRecord(null) + }, [setActiveRecord]) + +export const useOnWithdrawDialogSubmit = ({ + activeRecord, + setActiveRecord, + clearGeneratedTx, + walletID, + dispatch, + suggestFeeRate, +}: { + activeRecord: State.NervosDAORecord | null + setActiveRecord: React.Dispatch + clearGeneratedTx: () => void + walletID: string + dispatch: React.Dispatch + suggestFeeRate: number | string +}) => + useCallback(() => { + if (activeRecord) { + generateDaoWithdrawTx({ + walletID, + outPoint: activeRecord.outPoint, + feeRate: `${suggestFeeRate}`, + }) + .then(res => { + if (isSuccessResponse(res)) { + dispatch({ + type: AppActions.UpdateGeneratedTx, + payload: res.result, + }) + dispatch({ + type: AppActions.RequestPassword, + payload: { + walletID, + actionType: 'send', + onSuccess: () => {}, + }, + }) + } else { + clearGeneratedTx() + throw new Error(`${typeof res.message === 'string' ? res.message : res.message.content}`) + } + }) + .catch((err: Error) => { + showGlobalAlertDialog({ + type: 'failed', + message: err.message, + action: 'ok', + })(dispatch) + }) + } + setActiveRecord(null) + }, [activeRecord, setActiveRecord, clearGeneratedTx, walletID, dispatch, suggestFeeRate]) + +export const useOnActionClick = ({ + records, + clearGeneratedTx, + dispatch, + walletID, + setActiveRecord, + navigate, +}: { + records: Readonly + clearGeneratedTx: () => void + dispatch: React.Dispatch + walletID: string + setActiveRecord: React.Dispatch + navigate: NavigateFunction +}) => + useCallback( + (e: any) => { + const { dataset } = e.target + const outPoint = { + txHash: dataset.txHash, + index: dataset.index, + } + const record = records.find(r => r.outPoint.txHash === outPoint.txHash && r.outPoint.index === outPoint.index) + if (record) { + if (record.status === 'sent') { + navigate(`${RoutePath.History}/${record.depositInfo?.txHash}`) + } else if (record.depositOutPoint) { + generateDaoClaimTx({ + walletID, + withdrawingOutPoint: record.outPoint, + depositOutPoint: record.depositOutPoint, + feeRate: `${MEDIUM_FEE_RATE}`, + }) + .then(res => { + if (isSuccessResponse(res)) { + dispatch({ + type: AppActions.UpdateGeneratedTx, + payload: res.result, + }) + dispatch({ + type: AppActions.RequestPassword, + payload: { + walletID, + actionType: 'send', + onSuccess: () => {}, + }, + }) + } else { + clearGeneratedTx() + throw new Error(`${typeof res.message === 'string' ? res.message : res.message.content}`) + } + }) + .catch((err: Error) => { + showGlobalAlertDialog({ + type: 'failed', + message: err.message, + action: 'ok', + })(dispatch) + }) + } else { + setActiveRecord(record) + } + } + }, + [records, clearGeneratedTx, dispatch, walletID, setActiveRecord] + ) + +export const useUpdateWithdrawList = ({ + records, + tipDao, + setWithdrawList, +}: { + records: Readonly + tipDao?: string + setWithdrawList: React.Dispatch>> +}) => + useEffect(() => { + if (!tipDao) { + setWithdrawList(new Map()) + return + } + const depositOutPointHashes = records.map(v => v.depositOutPoint?.txHash ?? v.outPoint.txHash) + rpc + .createBatchRequest<'getTransaction', string[], CKBComponents.TransactionWithStatus[]>( + depositOutPointHashes.map(v => ['getTransaction', v]) + ) + .exec() + .then((txs: CKBComponents.TransactionWithStatus[]) => { + const committedTx = txs.filter(v => v.txStatus.status === 'committed') + const blockHashes = [ + ...(committedTx.map(v => v.txStatus.blockHash).filter(v => !!v) as string[]), + ...(records.map(v => (v.depositOutPoint ? v.blockHash : null)).filter(v => !!v) as string[]), + ] + return rpc + .createBatchRequest<'getHeader', string[], CKBComponents.BlockHeader[]>( + blockHashes.map(v => ['getHeader', v]) + ) + .exec() + .then((blockHeaders: CKBComponents.BlockHeader[]) => { + const hashHeaderMap = new Map() + blockHeaders.forEach((header, idx) => { + hashHeaderMap.set(blockHashes[idx], header.dao) + }) + const txMap = new Map() + txs.forEach((tx, idx) => { + if (tx.txStatus.status === 'committed') { + txMap.set(depositOutPointHashes[idx], tx) + } + }) + const withdrawList = new Map() + records.forEach(record => { + const key = getRecordKey(record) + const withdrawBlockHash = record.depositOutPoint ? record.blockHash : undefined + const formattedDepositOutPoint = record.depositOutPoint + ? { + txHash: record.depositOutPoint.txHash, + index: `0x${BigInt(record.depositOutPoint.index).toString(16)}`, + } + : { + txHash: record.outPoint.txHash, + index: `0x${BigInt(record.outPoint.index).toString(16)}`, + } + const tx = txMap.get(formattedDepositOutPoint.txHash) + if (!tx) { + return + } + const depositDAO = hashHeaderMap.get(tx.txStatus.blockHash!) + const withdrawDAO = withdrawBlockHash ? hashHeaderMap.get(withdrawBlockHash) : tipDao + if (!depositDAO || !withdrawDAO) { + return + } + withdrawList.set( + key, + calculateMaximumWithdrawCompatible( + { + cellOutput: tx.transaction.outputs[+formattedDepositOutPoint.index], + data: tx.transaction.outputsData[+formattedDepositOutPoint.index], + }, + depositDAO, + withdrawDAO + ).toHexString() + ) + }) + setWithdrawList(withdrawList) + }) + }) + .catch(() => { + setWithdrawList(new Map()) + }) + }, [records, tipDao, setWithdrawList]) + +const getBlockHashes = (txHashes: string[]) => { + const batchParams: ['getTransaction', string][] = txHashes.map(v => ['getTransaction', v]) + return rpc + .createBatchRequest<'getTransaction', [string], CKBComponents.TransactionWithStatus[]>(batchParams) + .exec() + .then((res: CKBComponents.TransactionWithStatus[]) => { + return res.map((v, idx) => ({ + txHash: txHashes[idx], + blockHash: v.txStatus.blockHash, + })) + }) + .catch(() => { + return [] + }) +} + +export const useUpdateDepositEpochList = ({ + records, + setDepositEpochList, + connectionStatus, +}: { + records: Readonly + setDepositEpochList: React.Dispatch>> + connectionStatus: State.ConnectionStatus +}) => + useEffect(() => { + if (connectionStatus === 'online') { + getBlockHashes(records.map(v => v.depositOutPoint?.txHash).filter(v => !!v) as string[]).then( + (depositBlockHashes: { txHash: string; blockHash: string | undefined }[]) => { + const recordKeyIdx: string[] = [] + const batchParams: ['getHeader', string][] = [] + records.forEach(record => { + if (!record.depositOutPoint && record.blockHash) { + batchParams.push(['getHeader', record.blockHash]) + recordKeyIdx.push(record.outPoint.txHash) + } + }) + depositBlockHashes.forEach(v => { + if (v.blockHash) { + batchParams.push(['getHeader', v.blockHash]) + recordKeyIdx.push(v.txHash) + } + }) + rpc + .createBatchRequest<'getHeader', any, CKBComponents.BlockHeader[]>(batchParams) + .exec() + .then((res: CKBComponents.BlockHeader[]) => { + const epochList = new Map() + records.forEach(record => { + const key = record.depositOutPoint ? record.depositOutPoint.txHash : record.outPoint.txHash + epochList.set(key, res[recordKeyIdx.indexOf(key)]?.epoch) + }) + setDepositEpochList(epochList) + }) + .catch(() => { + setDepositEpochList(new Map()) + }) + } + ) + } + }, [records, setDepositEpochList, connectionStatus]) + +export const useCanSign = ({ + addresses, + multisigConfig, +}: { + addresses: State.Address[] + multisigConfig: MultisigConfig +}) => { + const addressList = useMemo(() => addresses.map(v => v.address), [addresses]) + return useMemo(() => multisigConfig.addresses.some(v => addressList.includes(v)), [multisigConfig, addressList]) +} + +export default { + useInitData, + useOnWithdrawDialogDismiss, + useOnWithdrawDialogSubmit, + useOnActionClick, + useUpdateWithdrawList, + useUpdateDepositEpochList, + useCanSign, +} diff --git a/packages/neuron-ui/src/components/MultisigAddressNervosDAODialog/index.tsx b/packages/neuron-ui/src/components/MultisigAddressNervosDAODialog/index.tsx new file mode 100644 index 0000000000..1815d26a54 --- /dev/null +++ b/packages/neuron-ui/src/components/MultisigAddressNervosDAODialog/index.tsx @@ -0,0 +1,213 @@ +import React, { useState, useMemo } from 'react' +import { useState as useGlobalState, useDispatch } from 'states' +import { useTranslation } from 'react-i18next' +import { useNavigate } from 'react-router-dom' +import { MultisigConfig } from 'services/remote' +import { clsx, useClearGeneratedTx } from 'utils' +import Dialog from 'widgets/Dialog' +import WithdrawDialog from 'components/WithdrawDialog' +import DAORecord, { DAORecordProps } from 'components/NervosDAORecord' +import TableNoData from 'widgets/Icons/TableNoData.png' + +import useGetCountDownAndFeeRateStats from 'utils/hooks/useGetCountDownAndFeeRateStats' +import hooks from './hooks' +import styles from './multisigAddressNervosDAODialog.module.scss' + +const MultisigAddressNervosDAODialog = ({ + multisigConfig, + closeDialog, +}: { + multisigConfig: MultisigConfig + closeDialog: () => void +}) => { + const [tabIdx, setTabIdx] = useState('0') + const { + app: { tipDao, tipBlockTimestamp, epoch }, + wallet, + nervosDAO: { records }, + chain: { connectionStatus, networkID }, + settings: { networks }, + } = useGlobalState() + const dispatch = useDispatch() + const navigate = useNavigate() + const [t] = useTranslation() + const { suggestFeeRate } = useGetCountDownAndFeeRateStats() + const [activeRecord, setActiveRecord] = useState(null) + const [withdrawList, setWithdrawList] = useState>(new Map()) + const [genesisBlockTimestamp, setGenesisBlockTimestamp] = useState(undefined) + const [depositEpochList, setDepositEpochList] = useState>(new Map()) + const clearGeneratedTx = useClearGeneratedTx() + + const canSign = hooks.useCanSign({ addresses: wallet.addresses, multisigConfig }) + + const onWithdrawDialogDismiss = hooks.useOnWithdrawDialogDismiss(setActiveRecord) + + const genesisBlockHash = useMemo(() => networks.find(v => v.id === networkID)?.genesisHash, [networkID, networks]) + hooks.useInitData({ + clearGeneratedTx, + dispatch, + wallet, + setGenesisBlockTimestamp, + genesisBlockHash, + }) + const onWithdrawDialogSubmit = hooks.useOnWithdrawDialogSubmit({ + activeRecord, + setActiveRecord, + clearGeneratedTx, + walletID: wallet.id, + dispatch, + suggestFeeRate, + }) + + const onActionClick = hooks.useOnActionClick({ + records, + clearGeneratedTx, + dispatch, + walletID: wallet.id, + setActiveRecord, + navigate, + }) + + hooks.useUpdateDepositEpochList({ records, setDepositEpochList, connectionStatus }) + + hooks.useUpdateWithdrawList({ + records, + tipDao, + setWithdrawList, + }) + + const MemoizedRecords = useMemo(() => { + const onTabClick = (e: React.SyntheticEvent) => { + const { + dataset: { idx }, + } = e.target as HTMLDivElement + if (idx) { + setTabIdx(idx) + } + } + const filteredRecord = records.filter(record => { + if (record.status === 'failed') { + return false + } + + if (tabIdx === '0') { + return record.status !== 'dead' + } + return record.status === 'dead' + }) + + if (tabIdx === '0') { + filteredRecord.sort((r1, r2) => +r2.depositInfo!.timestamp! - +r1.depositInfo!.timestamp!) + } else if (tabIdx === '1') { + filteredRecord.sort((r1, r2) => +r2.unlockInfo!.timestamp! - +r1.unlockInfo!.timestamp!) + } + + return ( + <> +
+
+
+ + +
+
+ {filteredRecord.length ? ( +
+ {filteredRecord.map(record => { + const key = record.depositOutPoint + ? `${record.depositOutPoint.txHash}-${record.depositOutPoint.index}` + : `${record.outPoint.txHash}-${record.outPoint.index}` + const txHash = record.depositOutPoint ? record.depositOutPoint.txHash : record.outPoint.txHash + + const props: DAORecordProps = { + ...record, + tipBlockTimestamp, + withdrawCapacity: withdrawList.get(key) || null, + onClick: onActionClick, + depositEpoch: depositEpochList.get(txHash) || '', + currentEpoch: epoch, + genesisBlockTimestamp, + connectionStatus, + hasCkbBalance: +wallet.balance > 0, + } + return ( +
+ +
+ ) + })} +
+ ) : ( +
+ No Data + {t(`nervos-dao.deposit-record.no-${tabIdx === '0' ? 'deposit' : 'completed'}`)} +
+ )} + + ) + }, [ + records, + withdrawList, + t, + onActionClick, + epoch, + connectionStatus, + genesisBlockTimestamp, + tipBlockTimestamp, + depositEpochList, + tabIdx, + setTabIdx, + ]) + + const MemoizedWithdrawDialog = useMemo(() => { + return activeRecord ? ( + + ) : null + }, [activeRecord, onWithdrawDialogDismiss, onWithdrawDialogSubmit, tipDao, epoch, canSign]) + + return ( + +
+ {MemoizedRecords} + + {MemoizedWithdrawDialog} +
+
+ ) +} + +MultisigAddressNervosDAODialog.displayName = 'MultisigAddressNervosDAODialog' + +export default MultisigAddressNervosDAODialog diff --git a/packages/neuron-ui/src/components/MultisigAddressNervosDAODialog/multisigAddressNervosDAODialog.module.scss b/packages/neuron-ui/src/components/MultisigAddressNervosDAODialog/multisigAddressNervosDAODialog.module.scss new file mode 100644 index 0000000000..de81f40ca9 --- /dev/null +++ b/packages/neuron-ui/src/components/MultisigAddressNervosDAODialog/multisigAddressNervosDAODialog.module.scss @@ -0,0 +1,118 @@ +@import '../../styles/mixin.scss'; + +.container { + width: 900px; +} + +.tabContainer { + display: flex; + justify-content: space-between; + align-items: center; + .sortBtn { + display: flex; + justify-content: center; + align-items: center; + background: var(--secondary-background-color); + width: 36px; + height: 36px; + border-radius: 100%; + min-width: 0; + cursor: pointer; + &[data-desc='false'] { + svg { + transform: rotateX(-180deg); + } + } + &:hover { + svg { + g, + path { + stroke: var(--primary-color); + } + } + } + } +} + +.recordTab { + // To achieve animation on switching the selected target with pure CSS, a layout with fixed width is required. + $itemWidth: 96px; + $itemOverlapping: 8px; + $padding: 4px; + + --selected-tab: 0; + + position: relative; + width: max-content; + margin: 0; + padding: $padding; + background: var(--fourth-background-color); + border-radius: 40px; + + button { + @include bold-text; + position: relative; + appearance: none; + width: $itemWidth; + height: 40px; + font-weight: 500; + font-size: 14px; + background-color: transparent; + color: var(--primary-color); + padding: 0; + border: none; + margin-left: -$itemOverlapping; + cursor: pointer; + + &:first-of-type { + margin-left: 0; + } + + &.tab { + color: var(--secondary-text-color); + } + + &.active { + cursor: default; + color: var(--primary-color); + } + } + + .underline { + display: block; + position: absolute; + top: $padding; + left: $padding; + height: 40px; + width: $itemWidth; + background: var(--third-background-color); + border-radius: 40px; + transition: transform 0.1s ease-in-out; + transform: translateX(calc(var(--selected-tab) * ($itemWidth - $itemOverlapping))); + } +} + +.records { + display: flex; + flex-direction: column; + gap: 16px; + margin-top: 12px; + + .recordWrap { + border: 1px solid var(--divide-line-color); + border-radius: 16px; + } +} + +.noRecords { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 8px; + margin-top: 12px; + padding: 22px 0 44px 0; + background: var(--secondary-background-color); + border-radius: 16px; + font-size: 14px; +} diff --git a/packages/neuron-ui/src/components/NervosDAO/index.tsx b/packages/neuron-ui/src/components/NervosDAO/index.tsx index c80a7b2a5e..93d0a76200 100644 --- a/packages/neuron-ui/src/components/NervosDAO/index.tsx +++ b/packages/neuron-ui/src/components/NervosDAO/index.tsx @@ -287,6 +287,7 @@ const NervosDAO = () => { onSubmit={onWithdrawDialogSubmit} tipDao={tipDao} currentEpoch={epoch} + canSign /> ) : null }, [activeRecord, onWithdrawDialogDismiss, onWithdrawDialogSubmit, tipDao, epoch]) diff --git a/packages/neuron-ui/src/components/WithdrawDialog/index.tsx b/packages/neuron-ui/src/components/WithdrawDialog/index.tsx index 02a0756012..db6c6035cd 100644 --- a/packages/neuron-ui/src/components/WithdrawDialog/index.tsx +++ b/packages/neuron-ui/src/components/WithdrawDialog/index.tsx @@ -16,12 +16,14 @@ const WithdrawDialog = ({ record, tipDao, currentEpoch, + canSign = true, }: { onDismiss: () => void onSubmit: () => void record: State.NervosDAORecord tipDao?: string currentEpoch: string + canSign?: boolean }) => { const [t] = useTranslation() const [depositEpoch, setDepositEpoch] = useState('') @@ -104,7 +106,7 @@ const WithdrawDialog = ({ onCancel={onDismiss} onConfirm={onSubmit} cancelText={t('nervos-dao-detail.cancel')} - confirmText={t('nervos-dao-detail.next')} + confirmText={canSign ? t('nervos-dao-detail.next') : t('nervos-dao-detail.export')} > <>
diff --git a/packages/neuron-ui/src/locales/ar.json b/packages/neuron-ui/src/locales/ar.json index 5ccbd9fce9..8d1cad3725 100644 --- a/packages/neuron-ui/src/locales/ar.json +++ b/packages/neuron-ui/src/locales/ar.json @@ -810,7 +810,8 @@ "amount": "المبلغ", "cell-from-cellbase": "من خلية الأساس", "cancel": "إلغاء", - "next": "التالي" + "next": "التالي", + "export": "تصدير المعاملة" }, "deposit-rules": { "minimum-deposit": "الحد الأدنى للإيداع", diff --git a/packages/neuron-ui/src/locales/en.json b/packages/neuron-ui/src/locales/en.json index b9aea41979..0f3c9cabc5 100644 --- a/packages/neuron-ui/src/locales/en.json +++ b/packages/neuron-ui/src/locales/en.json @@ -810,7 +810,8 @@ "amount": "Amount", "cell-from-cellbase": "From cellbase", "cancel": "Cancel", - "next": "Next" + "next": "Next", + "export": "Export Tx" }, "deposit-rules": { "minimum-deposit": "Minimum Deposit", diff --git a/packages/neuron-ui/src/locales/es.json b/packages/neuron-ui/src/locales/es.json index 5ac87dd864..e359d72608 100644 --- a/packages/neuron-ui/src/locales/es.json +++ b/packages/neuron-ui/src/locales/es.json @@ -793,7 +793,8 @@ "amount": "Cantidad", "cell-from-cellbase": "Desde cellbase", "cancel": "Cancelar", - "next": "Siguiente" + "next": "Siguiente", + "export": "Exportar Tx" }, "deposit-rules": { "minimum-deposit": "Depósito mínimo", diff --git a/packages/neuron-ui/src/locales/fr.json b/packages/neuron-ui/src/locales/fr.json index 8db344661e..2968d4560d 100644 --- a/packages/neuron-ui/src/locales/fr.json +++ b/packages/neuron-ui/src/locales/fr.json @@ -800,7 +800,8 @@ "amount": "Montant", "cell-from-cellbase": "De cellbase", "cancel": "Annuler", - "next": "Suivant" + "next": "Suivant", + "export": "Exporter Tx" }, "deposit-rules": { "minimum-deposit": "Dépôt minimum", diff --git a/packages/neuron-ui/src/locales/zh-tw.json b/packages/neuron-ui/src/locales/zh-tw.json index 72f4eb29b4..f0aeb803fe 100644 --- a/packages/neuron-ui/src/locales/zh-tw.json +++ b/packages/neuron-ui/src/locales/zh-tw.json @@ -804,7 +804,8 @@ "amount": "數量", "cell-from-cellbase": "來自 Cellbase", "cancel": "取消", - "next": "下一步" + "next": "下一步", + "export": "導出交易" }, "deposit-rules": { "minimum-deposit": "最低存入額", diff --git a/packages/neuron-ui/src/locales/zh.json b/packages/neuron-ui/src/locales/zh.json index 1fe8278e5f..096b90d78b 100644 --- a/packages/neuron-ui/src/locales/zh.json +++ b/packages/neuron-ui/src/locales/zh.json @@ -803,7 +803,8 @@ "amount": "数量", "cell-from-cellbase": "来自 Cellbase", "cancel": "取消", - "next": "下一步" + "next": "下一步", + "export": "导出交易" }, "deposit-rules": { "minimum-deposit": "最低存入额",