From 0e05b62effc83c2af2461fa05b3549c4f5d549fa Mon Sep 17 00:00:00 2001 From: devinxl Date: Tue, 29 Aug 2023 14:43:26 +0800 Subject: [PATCH] feat(dcellar-web-ui): add pay by Owner Account to gas fee --- .../file/components/ConfirmCancelModal.tsx | 26 +- .../file/components/ConfirmDeleteModal.tsx | 296 ------------------ .../modules/group/components/DeleteGroup.tsx | 9 +- .../object/components/CancelObject.tsx | 27 +- .../object/components/DeleteObject.tsx | 31 +- .../batch-delete/BatchDeleteObject.tsx | 27 +- .../src/modules/upload/SimulateFee.tsx | 11 +- .../dcellar-web-ui/src/store/slices/object.ts | 3 +- 8 files changed, 45 insertions(+), 385 deletions(-) delete mode 100644 apps/dcellar-web-ui/src/modules/file/components/ConfirmDeleteModal.tsx diff --git a/apps/dcellar-web-ui/src/modules/file/components/ConfirmCancelModal.tsx b/apps/dcellar-web-ui/src/modules/file/components/ConfirmCancelModal.tsx index 043824ae..ef03e48b 100644 --- a/apps/dcellar-web-ui/src/modules/file/components/ConfirmCancelModal.tsx +++ b/apps/dcellar-web-ui/src/modules/file/components/ConfirmCancelModal.tsx @@ -24,6 +24,7 @@ import { signTypedDataV4 } from '@/utils/signDataV4'; import { useAppDispatch, useAppSelector } from '@/store'; import { selectBnbPrice, setupTmpAvailableBalance } from '@/store/slices/global'; import { selectBalance } from '@/store/slices/balance'; +import { renderFee } from '@/modules/object/components/CancelObject'; interface modalProps { title?: string; @@ -47,31 +48,6 @@ interface modalProps { setStatusModalErrorText: React.Dispatch>; } -const renderFee = ( - key: string, - bnbValue: string, - exchangeRate: number | string, - keyIcon?: React.ReactNode, -) => { - return ( - - - - {key} - - {keyIcon && ( - - {keyIcon} - - )} - - - {renderFeeValue(bnbValue, exchangeRate)} - - - ); -}; - export const ConfirmCancelModal = (props: modalProps) => { const dispatch = useAppDispatch(); const bnbPrice = useAppSelector(selectBnbPrice); diff --git a/apps/dcellar-web-ui/src/modules/file/components/ConfirmDeleteModal.tsx b/apps/dcellar-web-ui/src/modules/file/components/ConfirmDeleteModal.tsx deleted file mode 100644 index 20bcbf9f..00000000 --- a/apps/dcellar-web-ui/src/modules/file/components/ConfirmDeleteModal.tsx +++ /dev/null @@ -1,296 +0,0 @@ -import { Box, Flex, ModalCloseButton, ModalFooter, ModalHeader, Text, toast } from '@totejs/uikit'; -import { useAccount } from 'wagmi'; -import React, { useEffect, useState } from 'react'; - -import { - renderBalanceNumber, - renderFeeValue, - renderInsufficientBalance, -} from '@/modules/file/utils'; -import { - BUTTON_GOT_IT, - FILE_DELETE_GIF, - FILE_DESCRIPTION_DELETE_ERROR, - FILE_FAILED_URL, - FILE_STATUS_DELETING, - FILE_TITLE_DELETE_FAILED, - FILE_TITLE_DELETING, -} from '@/modules/file/constant'; -import { USER_REJECT_STATUS_NUM } from '@/utils/constant'; -import { DCModal } from '@/components/common/DCModal'; -import { Tips } from '@/components/common/Tips'; -import { DCButton } from '@/components/common/DCButton'; -import { reportEvent } from '@/utils/reportEvent'; -import { getClient } from '@/base/client'; -import { signTypedDataV4 } from '@/utils/signDataV4'; -import { useAppDispatch, useAppSelector } from '@/store'; -import { selectBnbPrice, setupTmpAvailableBalance } from '@/store/slices/global'; -import { selectBalance } from '@/store/slices/balance'; - -interface modalProps { - title?: string; - onClose: () => void; - isOpen: boolean; - buttonText?: string; - buttonOnClick?: () => void; - bucketName: string; - fileInfo?: { name: string; size: number }; - gasFeeLoading?: boolean; - simulateGasFee: string; - setListObjects: React.Dispatch>; - listObjects: Array; - setStatusModalIcon: React.Dispatch>; - setStatusModalTitle: React.Dispatch>; - setStatusModalDescription: React.Dispatch>; - setStatusModalButtonText: React.Dispatch>; - onStatusModalOpen: () => void; - onStatusModalClose: () => void; - outsideLoading: boolean; - lockFee: string; - setStatusModalErrorText: React.Dispatch>; -} - -const renderQuota = (key: string, value: string) => { - return ( - - - {key} - - - {value} - - - ); -}; - -const renderFee = ( - key: string, - bnbValue: string, - exchangeRate: number, - keyIcon?: React.ReactNode, -) => { - return ( - - - - {key} - - {keyIcon && ( - - {keyIcon} - - )} - - - {renderFeeValue(bnbValue, exchangeRate)} - - - ); -}; - -export const ConfirmDeleteModal = (props: modalProps) => { - const dispatch = useAppDispatch(); - const { loginAccount: address } = useAppSelector((root) => root.persist); - const bnbPrice = useAppSelector(selectBnbPrice); - const exchangeRate = Number(bnbPrice); - const [loading, setLoading] = useState(false); - const [buttonDisabled, setButtonDisabled] = useState(false); - const { bankBalance: availableBalance } = useAppSelector((root) => root.accounts); - const { - title = 'Confirm Delete', - onClose, - isOpen, - bucketName, - fileInfo = { name: '', size: 0 }, - lockFee, - outsideLoading, - simulateGasFee, - setListObjects, - listObjects, - setStatusModalIcon, - setStatusModalTitle, - setStatusModalDescription, - onStatusModalOpen, - onStatusModalClose, - setStatusModalButtonText, - setStatusModalErrorText, - } = props; - - useEffect(() => { - if (!isOpen) return; - dispatch(setupTmpAvailableBalance(address)); - }, [isOpen, dispatch, address]); - - const { connector } = useAccount(); - useEffect(() => { - if (!simulateGasFee || Number(simulateGasFee) < 0 || !lockFee || Number(lockFee) < 0) { - setButtonDisabled(false); - return; - } - const currentBalance = Number(availableBalance); - if (currentBalance >= Number(simulateGasFee) + Number(lockFee)) { - setButtonDisabled(false); - return; - } - setButtonDisabled(true); - }, [simulateGasFee, availableBalance, lockFee]); - const { name = '', size = 0 } = fileInfo; - const filePath = name.split('/'); - const showName = filePath[filePath.length - 1]; - const description = `Are you sure you want to delete file "${showName}"?`; - - const setFailedStatusModal = (description: string, error: any) => { - onStatusModalClose(); - setStatusModalIcon(FILE_FAILED_URL); - setStatusModalTitle(FILE_TITLE_DELETE_FAILED); - setStatusModalDescription(description); - setStatusModalButtonText(BUTTON_GOT_IT); - setStatusModalErrorText('Error message: ' + error?.message ?? ''); - onStatusModalOpen(); - }; - - return ( - - {title} - - - {description} - - - {renderFee( - 'Unlocked storage fee', - lockFee, - exchangeRate, - - - We will unlock the storage fee after you delete the file. - - - } - />, - )} - {renderFee('Gas Fee', simulateGasFee, exchangeRate)} - - - - {renderInsufficientBalance(simulateGasFee, lockFee, availableBalance || '0', { - gaShowName: 'dc.file.delete_confirm.depost.show', - gaClickName: 'dc.file.delete_confirm.transferin.click', - })} - - - Available balance: {renderBalanceNumber(availableBalance || '0')} - - - - - - Cancel - - { - try { - setLoading(true); - onClose(); - setStatusModalIcon(FILE_DELETE_GIF); - setStatusModalTitle(FILE_TITLE_DELETING); - setStatusModalDescription(FILE_STATUS_DELETING); - setStatusModalErrorText(''); - setStatusModalButtonText(''); - onStatusModalOpen(); - const client = await getClient(); - const delObjTx = await client.object.deleteObject({ - bucketName, - objectName: name, - operator: address, - }); - const simulateInfo = await delObjTx.simulate({ - denom: 'BNB', - }); - const txRes = await delObjTx.broadcast({ - denom: 'BNB', - gasLimit: Number(simulateInfo?.gasLimit), - gasPrice: simulateInfo?.gasPrice || '5000000000', - payer: address, - granter: '', - signTypedDataCallback: async (addr: string, message: string) => { - const provider = await connector?.getProvider(); - return await signTypedDataV4(provider, addr, message); - }, - }); - - if (txRes.code === 0) { - toast.success({ description: 'File deleted successfully.' }); - reportEvent({ - name: 'dc.toast.file_delete.success.show', - }); - } else { - toast.error({ description: 'Delete file error.' }); - } - const newListObject = listObjects.filter((v, i) => { - return v?.ObjectName !== name; - }); - setListObjects(newListObject); - onStatusModalClose(); - setLoading(false); - } catch (error: any) { - setLoading(false); - const { code = '' } = error; - if (code && parseInt(code) === USER_REJECT_STATUS_NUM) { - onStatusModalClose(); - return; - } - // eslint-disable-next-line no-console - console.error('Delete file error.', error); - - setFailedStatusModal(FILE_DESCRIPTION_DELETE_ERROR, error); - } - }} - colorScheme="danger" - isLoading={loading || outsideLoading} - isDisabled={buttonDisabled} - > - Delete - - - - ); -}; diff --git a/apps/dcellar-web-ui/src/modules/group/components/DeleteGroup.tsx b/apps/dcellar-web-ui/src/modules/group/components/DeleteGroup.tsx index 1d430514..4211cc45 100644 --- a/apps/dcellar-web-ui/src/modules/group/components/DeleteGroup.tsx +++ b/apps/dcellar-web-ui/src/modules/group/components/DeleteGroup.tsx @@ -10,12 +10,13 @@ import { BUTTON_GOT_IT, FILE_DELETE_GIF, FILE_FAILED_URL, + GAS_FEE_DOC, GROUP_DELETE, UNKNOWN_ERROR, WALLET_CONFIRM, } from '@/modules/file/constant'; import { useUnmount } from 'ahooks'; -import { Flex, ModalCloseButton, ModalFooter, ModalHeader, Text, toast } from '@totejs/uikit'; +import { Flex, Link, ModalCloseButton, ModalFooter, ModalHeader, Text, toast } from '@totejs/uikit'; import { DCModal } from '@/components/common/DCModal'; import { renderBalanceNumber, @@ -146,7 +147,11 @@ export const DeleteGroup = memo(function DeleteGroup() { fontWeight={400} color={'readable.tertiary'} > - Gas Fee + Gas Fee ( + + Pay by Owner Account + + ) diff --git a/apps/dcellar-web-ui/src/modules/object/components/CancelObject.tsx b/apps/dcellar-web-ui/src/modules/object/components/CancelObject.tsx index 684072e5..387d46bf 100644 --- a/apps/dcellar-web-ui/src/modules/object/components/CancelObject.tsx +++ b/apps/dcellar-web-ui/src/modules/object/components/CancelObject.tsx @@ -1,4 +1,13 @@ -import { Box, Flex, ModalCloseButton, ModalFooter, ModalHeader, Text, toast } from '@totejs/uikit'; +import { + Box, + Flex, + Link, + ModalCloseButton, + ModalFooter, + ModalHeader, + Text, + toast, +} from '@totejs/uikit'; import { useAccount } from 'wagmi'; import React, { useEffect, useState } from 'react'; import { @@ -13,6 +22,7 @@ import { FILE_STATUS_CANCELING, FILE_TITLE_CANCEL_FAILED, FILE_TITLE_CANCELING, + GAS_FEE_DOC, PENDING_ICON_URL, } from '@/modules/file/constant'; import { USER_REJECT_STATUS_NUM } from '@/utils/constant'; @@ -42,10 +52,10 @@ interface modalProps { refetch: () => void; } -const renderFee = ( +export const renderFee = ( key: string, bnbValue: string, - exchangeRate: number, + exchangeRate: number | string, keyIcon?: React.ReactNode, ) => { return ( @@ -53,6 +63,15 @@ const renderFee = ( {key} + {key.toLowerCase() === 'gas fee' && ( + <> + {' '}( + + Pay by Owner Account + + ) + + )} {keyIcon && ( @@ -75,7 +94,7 @@ export const CancelObject = ({ refetch }: modalProps) => { const { bnb: { price: bnbPrice }, } = useAppSelector((root) => root.global); - const {bankBalance: availableBalance} = useAppSelector((root) => root.accounts); + const { bankBalance: availableBalance } = useAppSelector((root) => root.accounts); const { primarySpInfo } = useAppSelector((root) => root.sp); const { bucketName, editCancel } = useAppSelector((root) => root.object); const primarySp = primarySpInfo[bucketName]; diff --git a/apps/dcellar-web-ui/src/modules/object/components/DeleteObject.tsx b/apps/dcellar-web-ui/src/modules/object/components/DeleteObject.tsx index 27144395..da42b98d 100644 --- a/apps/dcellar-web-ui/src/modules/object/components/DeleteObject.tsx +++ b/apps/dcellar-web-ui/src/modules/object/components/DeleteObject.tsx @@ -49,6 +49,7 @@ import { setupTmpAvailableBalance } from '@/store/slices/global'; import { resolve } from '@/facade/common'; import { getListObjects } from '@/facade/object'; import LoadingIcon from '@/public/images/icons/loading.svg'; +import { renderFee } from './CancelObject'; interface modalProps { refetch: () => void; @@ -67,36 +68,6 @@ const renderQuota = (key: string, value: string) => { ); }; -const renderFee = ( - key: string, - bnbValue: string, - exchangeRate: number, - loading?: boolean, - keyIcon?: React.ReactNode, -) => { - return ( - - - - {key} - - {keyIcon && ( - - {keyIcon} - - )} - - {loading ? ( - - ) : ( - - ~{renderFeeValue(bnbValue, exchangeRate)} - - )} - - ); -}; - export const DeleteObject = ({ refetch }: modalProps) => { const dispatch = useAppDispatch(); const [lockFee, setLockFee] = useState(''); diff --git a/apps/dcellar-web-ui/src/modules/object/components/batch-delete/BatchDeleteObject.tsx b/apps/dcellar-web-ui/src/modules/object/components/batch-delete/BatchDeleteObject.tsx index c8b04069..06b598af 100644 --- a/apps/dcellar-web-ui/src/modules/object/components/batch-delete/BatchDeleteObject.tsx +++ b/apps/dcellar-web-ui/src/modules/object/components/batch-delete/BatchDeleteObject.tsx @@ -3,7 +3,6 @@ import { useAccount } from 'wagmi'; import React, { useEffect, useState } from 'react'; import { renderBalanceNumber, - renderFeeValue, renderInsufficientBalance, } from '@/modules/file/utils'; import { @@ -32,6 +31,7 @@ import { round } from 'lodash-es'; import { ColoredWaitingIcon } from '@totejs/icons'; import { useOffChainAuth } from '@/hooks/useOffChainAuth'; import { cancelCreateObject, deleteObject } from '@/facade/object'; +import { renderFee } from '../CancelObject'; interface modalProps { refetch: () => void; @@ -39,31 +39,6 @@ interface modalProps { cancelFn: () => void; } -const renderFee = ( - key: string, - bnbValue: string, - exchangeRate: number, - keyIcon?: React.ReactNode, -) => { - return ( - - - - {key} - - {keyIcon && ( - - {keyIcon} - - )} - - - {renderFeeValue(bnbValue, exchangeRate)} - - - ); -}; - export const BatchDeleteObject = ({ refetch, isOpen, cancelFn }: modalProps) => { const dispatch = useAppDispatch(); const [lockFee, setLockFee] = useState(''); diff --git a/apps/dcellar-web-ui/src/modules/upload/SimulateFee.tsx b/apps/dcellar-web-ui/src/modules/upload/SimulateFee.tsx index 85d77a9f..017257e3 100644 --- a/apps/dcellar-web-ui/src/modules/upload/SimulateFee.tsx +++ b/apps/dcellar-web-ui/src/modules/upload/SimulateFee.tsx @@ -21,6 +21,7 @@ import { MenuCloseIcon } from '@totejs/icons'; import { selectPayLockFeeAccount, setEditUpload } from '@/store/slices/object'; import BigNumber from 'bignumber.js'; import { DECIMAL_NUMBER } from '../wallet/constants'; +import { GAS_FEE_DOC } from '../file/constant'; export const Fee = () => { const dispatch = useAppDispatch(); @@ -130,7 +131,15 @@ export const Fee = () => { color={'readable.tertiary'} as="p" > - {key} + {key}{key.toLowerCase() === 'gas fee' && ( + <> + {' '}( + + Pay by Owner Account + + ) + + )} {keyIcon && ( diff --git a/apps/dcellar-web-ui/src/store/slices/object.ts b/apps/dcellar-web-ui/src/store/slices/object.ts index e8e308b8..c92c2474 100644 --- a/apps/dcellar-web-ui/src/store/slices/object.ts +++ b/apps/dcellar-web-ui/src/store/slices/object.ts @@ -211,8 +211,9 @@ export const objectSlice = createSlice({ setObjectList(state, { payload }: PayloadAction<{ path: string; list: GfSPListObjectsByBucketNameResponse }>) { const { path, list } = payload; const [bucketName] = path.split('/'); + const CommonPrefixes = Array.isArray(list?.CommonPrefixes) ? list?.CommonPrefixes : [list?.CommonPrefixes] // keep order - const folders = list?.CommonPrefixes.reverse() + const folders = CommonPrefixes.reverse() .map((i, index) => ({ bucketName, objectName: i,