From b942406de42dcfaa6403ed91e7b22c4f930c0223 Mon Sep 17 00:00:00 2001 From: aidencao Date: Wed, 15 May 2024 21:41:03 +0800 Subject: [PATCH 1/2] feat(dcellar-web-ui): support sponsor payment account --- .../src/components/common/DCSelect/index.tsx | 22 ++++- .../GlobalManagements/BucketQuotaManager.tsx | 36 +++++---- .../GlobalObjectUploadManager.tsx | 24 +++++- apps/dcellar-web-ui/src/facade/common.ts | 11 +++ apps/dcellar-web-ui/src/facade/error.ts | 31 +++++++ .../src/hooks/useAccountType.ts | 26 ++++++ .../src/hooks/useChangePaymentAccountFee.ts | 48 ++++++++--- .../bucket/components/BucketNameColumn.tsx | 15 +++- .../bucket/components/BucketOperations.tsx | 10 ++- .../components/ChangePaymentTotalFees.tsx | 27 +++++-- .../components/CreateBucketOperation.tsx | 40 ++++++++-- .../bucket/components/DiscontinueNotice.tsx | 52 +++++++++--- .../components/PaymentAccountOperation.tsx | 72 ++++++++++++----- .../components/PaymentAccountSelector.tsx | 6 +- .../bucket/components/SPSelector/index.tsx | 5 +- .../group/components/GroupOperations.tsx | 8 +- .../components/CreateFolderOperation.tsx | 6 +- .../object/components/CreateObject.tsx | 28 ++++--- .../object/components/ObjectOperations.tsx | 8 +- .../object/components/TotalFees/index.tsx | 80 ++++++++++--------- .../src/modules/object/constant.ts | 3 + .../src/modules/object/index.tsx | 4 + .../src/modules/object/utils/index.tsx | 22 +++-- .../src/modules/upload/UploadObjectsFees.tsx | 16 +++- .../src/utils/payment/index.tsx | 8 +- 25 files changed, 466 insertions(+), 142 deletions(-) create mode 100644 apps/dcellar-web-ui/src/hooks/useAccountType.ts diff --git a/apps/dcellar-web-ui/src/components/common/DCSelect/index.tsx b/apps/dcellar-web-ui/src/components/common/DCSelect/index.tsx index bdffc415..87d4ad80 100644 --- a/apps/dcellar-web-ui/src/components/common/DCSelect/index.tsx +++ b/apps/dcellar-web-ui/src/components/common/DCSelect/index.tsx @@ -16,13 +16,22 @@ import { MenuProps, useDisclosure, } from '@node-real/uikit'; -import React, { ReactNode, useEffect, useRef, useState } from 'react'; +import React, { + FocusEventHandler, + FocusEvent, + ReactNode, + useEffect, + useRef, + useState, +} from 'react'; +import { isAddress } from 'ethers/lib/utils.js'; interface ListItemProps extends MenuItemProps { gaClickName?: string; } export interface DCSelectProps extends MenuProps { + allowInput?: boolean; header?: () => ReactNode; footer?: () => ReactNode; value?: string; @@ -52,6 +61,7 @@ export function DCSelect(props: DCSelectProps) { onSearch, children, renderOption, + allowInput = false, ...restProps } = props; @@ -66,6 +76,14 @@ export function DCSelect(props: DCSelectProps) { } }; + const onBlur = (e: FocusEvent) => { + const value = e.target.value?.trim(); + if (isAddress(value) && allowInput) { + onSelectItem({ value, label: value }); + } + onClose(); + }; + const onSelectItem = (item: MenuOption) => { onChange?.(item.value); }; @@ -130,7 +148,7 @@ export function DCSelect(props: DCSelectProps) { placeholder={text} onChangeKeyword={onChangeKeyword} onEnter={onEnter} - onBlur={onClose} + onBlur={onBlur as any} /> ); diff --git a/apps/dcellar-web-ui/src/components/layout/GlobalManagements/BucketQuotaManager.tsx b/apps/dcellar-web-ui/src/components/layout/GlobalManagements/BucketQuotaManager.tsx index 4e8f2990..93dbb6c6 100644 --- a/apps/dcellar-web-ui/src/components/layout/GlobalManagements/BucketQuotaManager.tsx +++ b/apps/dcellar-web-ui/src/components/layout/GlobalManagements/BucketQuotaManager.tsx @@ -45,11 +45,13 @@ import { Text, toast, } from '@node-real/uikit'; -import { useAsyncEffect, useUnmount } from 'ahooks'; +import { useAsyncEffect, useUnmount, useUpdateEffect } from 'ahooks'; import BigNumber from 'bignumber.js'; import { find, isEmpty } from 'lodash-es'; import { memo, useEffect, useMemo, useState } from 'react'; import { useAccount } from 'wagmi'; +import { useRouter } from 'next/router'; +import { useAccountType } from '@/hooks/useAccountType'; const EMPTY_BUCKET = {} as TBucket; @@ -62,12 +64,9 @@ export const BucketQuotaManager = memo(function ManageQuota({ const bucketEditQuota = useAppSelector((root) => root.bucket.bucketEditQuota); const bucketRecords = useAppSelector((root) => root.bucket.bucketRecords); const bucketQuotaRecords = useAppSelector((root) => root.bucket.bucketQuotaRecords); - const ownerAccount = useAppSelector((root) => root.accounts.ownerAccount); const loginAccount = useAppSelector((root) => root.persist.loginAccount); const bankBalance = useAppSelector((root) => root.accounts.bankOrWalletBalance); const gnfdGasFeesConfig = useAppSelector(selectGnfdGasFeesConfig); - - const paymentAccountList = useAppSelector(selectPaymentAccounts(loginAccount)); const storeFeeParams = useAppSelector(selectStoreFeeParams); const [loading, setLoading] = useState(false); @@ -85,6 +84,7 @@ export const BucketQuotaManager = memo(function ManageQuota({ const [bucketName] = bucketEditQuota; const bucket = bucketRecords[bucketName] || EMPTY_BUCKET; const PaymentAddress = bucket.PaymentAddress; + const { pa, oa, isSponsor } = useAccountType(PaymentAddress); const { settlementFee } = useSettlementFee(PaymentAddress); const accountDetail = useAppSelector(selectAccount(PaymentAddress)); const quota = bucketQuotaRecords[bucketName]; @@ -93,11 +93,11 @@ export const BucketQuotaManager = memo(function ManageQuota({ const valid = balanceEnough && !loading && newChargedQuota * G_BYTES > (currentQuota || 0); const totalFee = useMemo(() => { - if (isEmpty(storeFeeParams) || isEmpty(preStoreFeeParams)) return '-1'; + if (isEmpty(storeFeeParams) || isEmpty(preStoreFeeParams) || isSponsor) return '-1'; const quotaRate = getQuotaNetflowRate(newChargedQuota * G_BYTES, storeFeeParams); - const storeRate = getStoreNetflowRate(chargeSize, storeFeeParams); + const storeRate = getStoreNetflowRate(chargeSize, storeFeeParams, true); const preQuotaRate = getQuotaNetflowRate(currentQuota, storeFeeParams); - const preStoreRate = getStoreNetflowRate(chargeSize, preStoreFeeParams); + const preStoreRate = getStoreNetflowRate(chargeSize, preStoreFeeParams, true); const fund = BN(quotaRate) .plus(storeRate) .minus(preQuotaRate) @@ -105,21 +105,20 @@ export const BucketQuotaManager = memo(function ManageQuota({ .times(storeFeeParams.reserveTime); setRefund(fund.isNegative()); return fund.abs().toString(); - }, [storeFeeParams, newChargedQuota, chargeSize, currentQuota]); + }, [storeFeeParams, newChargedQuota, chargeSize, currentQuota, isSponsor]); const paymentAccount = useMemo(() => { if (!bucket) return '--'; const address = bucket.PaymentAddress; - const pa = find(paymentAccountList, (a) => a.address === address); - const oa = ownerAccount.address === address; - - if (!pa && !oa) return '--'; - const link = `${GREENFIELD_CHAIN_EXPLORER_URL}/account/${address}`; return ( <> - {oa ? OWNER_ACCOUNT_NAME : pa!.name} - | + {!isSponsor && ( + <> + {oa ? OWNER_ACCOUNT_NAME : pa!.name} + | + + )} (function ManageQuota({ ); - }, [ownerAccount, paymentAccountList, bucket]); + }, [pa, oa, isSponsor, bucket]); const errorHandler = (error: string) => { switch (error) { @@ -317,6 +316,7 @@ interface BucketQuotaDrawerProps {} export const BucketQuotaDrawer = memo(function BucketQuotaDrawer() { const dispatch = useAppDispatch(); + const { pathname } = useRouter(); const bucketEditQuota = useAppSelector((root) => root.bucket.bucketEditQuota); const [bucketName] = bucketEditQuota; @@ -326,6 +326,10 @@ export const BucketQuotaDrawer = memo(function BucketQuo useUnmount(onClose); + useUpdateEffect(() => { + onClose(); + }, [pathname]); + return ( Manage Quota diff --git a/apps/dcellar-web-ui/src/components/layout/GlobalManagements/GlobalObjectUploadManager.tsx b/apps/dcellar-web-ui/src/components/layout/GlobalManagements/GlobalObjectUploadManager.tsx index d776f4fb..064d765a 100644 --- a/apps/dcellar-web-ui/src/components/layout/GlobalManagements/GlobalObjectUploadManager.tsx +++ b/apps/dcellar-web-ui/src/components/layout/GlobalManagements/GlobalObjectUploadManager.tsx @@ -20,7 +20,12 @@ import { memo, useEffect, useMemo, useState } from 'react'; import { useOffChainAuth } from '@/context/off-chain-auth/useOffChainAuth'; import { resolve } from '@/facade/common'; -import { broadcastFault, createTxFault, simulateFault } from '@/facade/error'; +import { + broadcastFault, + createTxFault, + E_BUCKET_FLOW_RATE_NOT_SET, + simulateFault, +} from '@/facade/error'; import { delegateCreateFolder, getObjectMeta } from '@/facade/object'; import { getCreateObjectTx } from '@/modules/object/utils/getCreateObjectTx'; import { setupAccountRecords } from '@/store/slices/accounts'; @@ -249,7 +254,7 @@ export const GlobalObjectUploadManager = memo( }) .catch(async (e: Response | any) => { console.error('upload error', e); - const { message } = await parseErrorXml(e); + let { message } = await parseErrorXml(e); const authExpired = [ 'bad signature', 'invalid signature', @@ -260,6 +265,21 @@ export const GlobalObjectUploadManager = memo( setAuthModal(true); dispatch(refreshTaskFolder(task)); } + + // todo refactor + const rateLimitNotSet = message?.includes( + 'the flow rate limit is not set for the bucket', + ); + const rateLimitLow = + message?.includes('is greater than the flow rate limit') || + message?.includes('payment account is not changed but the bucket is limited'); + if (rateLimitNotSet) { + message = E_BUCKET_FLOW_RATE_NOT_SET; + } + if (rateLimitLow) { + message = 'Flow rate exceeds limit.'; + } + setTimeout(() => { dispatch( setupUploadTaskErrorMsg({ diff --git a/apps/dcellar-web-ui/src/facade/common.ts b/apps/dcellar-web-ui/src/facade/common.ts index 9c51af33..cb5eed97 100644 --- a/apps/dcellar-web-ui/src/facade/common.ts +++ b/apps/dcellar-web-ui/src/facade/common.ts @@ -5,6 +5,8 @@ import { broadcastFault, commonFault, simulateFault, + E_BUCKET_FLOW_RATE_NOT_SET, + E_BUCKET_FLOW_RATE_LOW, } from '@/facade/error'; import { getClient } from '@/facade/index'; import { UNKNOWN_ERROR } from '@/modules/object/constant'; @@ -20,6 +22,15 @@ export const resolve = (r: R): [R, null] => [r, null]; export const resolveSpRequest = (r: SpResponse) => { if (r.code !== 0) { + if (r.message?.includes('the flow rate limit is not set for the bucket')) { + return [null, E_BUCKET_FLOW_RATE_NOT_SET]; + } + if ( + r.message?.includes('is greater than the flow rate limit') || + r.message?.includes('payment account is not changed but the bucket is limited') + ) { + return [null, E_BUCKET_FLOW_RATE_LOW]; + } return [null, r.message || UNKNOWN_ERROR]; } return [r.body, null]; diff --git a/apps/dcellar-web-ui/src/facade/error.ts b/apps/dcellar-web-ui/src/facade/error.ts index 78ee4a3f..b0821917 100644 --- a/apps/dcellar-web-ui/src/facade/error.ts +++ b/apps/dcellar-web-ui/src/facade/error.ts @@ -32,6 +32,10 @@ export const E_MAX_FOLDER_DEPTH = 'MAX_FOLDER_DEPTH'; export const E_ACCOUNT_BALANCE_NOT_ENOUGH = 'ACCOUNT_BALANCE_NOT_ENOUGH'; export const E_NO_PERMISSION = 'NO_PERMISSION'; export const E_SP_STORAGE_PRICE_FAILED = 'SP_STORAGE_PRICE_FAILED'; +export const E_BUCKET_FLOW_RATE_NOT_SET = + 'The payment account does not specify a flow rate for this bucket, hence it cannot be created. Please contact the payment account owner first to set the flow rate for your bucket.'; +export const E_BUCKET_FLOW_RATE_LOW = + "The flow rate exceeds the maximum value. Please remove some objects or contact the payment account's owner to increase the flow rate."; export declare class BroadcastTxError extends Error { readonly code: number; @@ -54,6 +58,15 @@ export const simulateFault = (e: any): ErrorResponse => { if (e?.message.includes('No such object')) { return [null, E_OBJECT_NOT_EXISTS]; } + if (e?.message.includes('the flow rate limit is not set for the bucket')) { + return [null, E_BUCKET_FLOW_RATE_NOT_SET]; + } + if ( + e?.message.includes('is greater than the flow rate limit') || + e?.message.includes('payment account is not changed but the bucket is limited') + ) { + return [null, E_BUCKET_FLOW_RATE_LOW]; + } return [null, e?.message || E_UNKNOWN_ERROR]; }; @@ -62,6 +75,15 @@ export const broadcastFault = (e: BroadcastTxError): ErrorResponse => { if (String(code) === E_USER_REJECT_STATUS_NUM) { return [null, ErrorMsgMap[E_USER_REJECT_STATUS_NUM]]; } + if (e?.message.includes('the flow rate limit is not set for the bucket')) { + return [null, 'E_BUCKET_FLOW_RATE_NOT_SET']; + } + if ( + e?.message.includes('is greater than the flow rate limit') || + e?.message.includes('payment account is not changed but the bucket is limited') + ) { + return [null, 'Flow rate exceeds limit']; + } return [null, parseWCMessage(e?.message) || E_UNKNOWN_ERROR]; }; @@ -102,6 +124,15 @@ export const offChainAuthFault = (e: any): ErrorResponse => { }; export const commonFault = (e: any): ErrorResponse => { + if (e?.message.includes('the flow rate limit is not set for the bucket')) { + return [null, E_BUCKET_FLOW_RATE_NOT_SET]; + } + if ( + e?.message.includes('is greater than the flow rate limit') || + e?.message.includes('payment account is not changed but the bucket is limited') + ) { + return [null, E_BUCKET_FLOW_RATE_LOW]; + } if (e?.message) { return [null, e?.message]; } diff --git a/apps/dcellar-web-ui/src/hooks/useAccountType.ts b/apps/dcellar-web-ui/src/hooks/useAccountType.ts new file mode 100644 index 00000000..3c58eb3f --- /dev/null +++ b/apps/dcellar-web-ui/src/hooks/useAccountType.ts @@ -0,0 +1,26 @@ +import { useAppSelector } from '@/store'; +import { AccountEntity, selectPaymentAccounts } from '@/store/slices/accounts'; +import { find } from 'lodash-es'; + +export const useAccountType = (address: string) => { + const loginAccount = useAppSelector((root) => root.persist.loginAccount); + const ownerAccount = useAppSelector((root) => root.accounts.ownerAccount); + const paymentAccountList = useAppSelector(selectPaymentAccounts(loginAccount)); + + if (!address) + return { + pa: {} as AccountEntity, + oa: false, + isSponsor: false, + }; + + const pa = find(paymentAccountList, (a) => a.address.toLowerCase() === address.toLowerCase()); + const oa = ownerAccount.address.toLowerCase() === address.toLowerCase(); + const isSponsor = !pa && !oa; + + return { + pa, + oa, + isSponsor, + }; +}; diff --git a/apps/dcellar-web-ui/src/hooks/useChangePaymentAccountFee.ts b/apps/dcellar-web-ui/src/hooks/useChangePaymentAccountFee.ts index eae5c614..fe331096 100644 --- a/apps/dcellar-web-ui/src/hooks/useChangePaymentAccountFee.ts +++ b/apps/dcellar-web-ui/src/hooks/useChangePaymentAccountFee.ts @@ -3,16 +3,18 @@ import { useAppSelector } from '@/store'; import { selectAccount } from '@/store/slices/accounts'; import { selectGnfdGasFeesConfig, selectStoreFeeParams } from '@/store/slices/global'; import { BN } from '@/utils/math'; -import { getStoreNetflowRate } from '@/utils/payment'; +import { getQuotaNetflowRate, getStoreNetflowRate } from '@/utils/payment'; import { MsgUpdateBucketInfoTypeUrl } from '@bnb-chain/greenfield-js-sdk'; import { useMemo } from 'react'; import { useSettlementFee } from './useSettlementFee'; +import { isEmpty } from 'lodash-es'; type ChangePaymentAccountFee = { loading: boolean; fromSettlementFee: string; toSettlementFee: string; storeFee: string; + quotaFee: string; gasFee: string; }; @@ -20,10 +22,12 @@ export const useChangePaymentAccountFee = ({ from, to, storageSize, + readQuota, }: { from: string; to: string; storageSize: number; + readQuota: number; }): ChangePaymentAccountFee => { const gnfdGasFeesConfig = useAppSelector(selectGnfdGasFeesConfig); @@ -32,7 +36,12 @@ export const useChangePaymentAccountFee = ({ const { settlementFee: toSettlementFee, loading: loading2 } = useSettlementFee(to); const { gasFee } = gnfdGasFeesConfig?.[MsgUpdateBucketInfoTypeUrl] ?? {}; - const storeFee = BN(getStoreNetflowRate(storageSize, storeFeeParams)) + const storeFee = BN(getStoreNetflowRate(storageSize, storeFeeParams, true)) + .times(storeFeeParams.reserveTime) + .dp(CRYPTOCURRENCY_DISPLAY_PRECISION) + .toString(); + + const quotaFee = BN(getQuotaNetflowRate(readQuota, storeFeeParams)) .times(storeFeeParams.reserveTime) .dp(CRYPTOCURRENCY_DISPLAY_PRECISION) .toString(); @@ -42,6 +51,7 @@ export const useChangePaymentAccountFee = ({ fromSettlementFee, toSettlementFee, storeFee, + quotaFee, gasFee: String(gasFee), }; }; @@ -52,10 +62,15 @@ export const useValidateChangePaymentFee = ({ fromSettlementFee, toSettlementFee, storeFee, + quotaFee, gasFee, + toSponsor, + fromSponsor, }: Omit & { from: string; to: string; + toSponsor: boolean; + fromSponsor: boolean; }) => { const loginAccount = useAppSelector((root) => root.persist.loginAccount); const bankBalance = useAppSelector((root) => root.accounts.bankOrWalletBalance); @@ -76,12 +91,20 @@ export const useValidateChangePaymentFee = ({ return gasFeeEnough && storeFeeEnough; } - const storeFeeEnough = BN(fromAccountDetail.staticBalance) - .minus(fromSettlementFee) - .isGreaterThanOrEqualTo(0); + const storeFeeEnough = + fromSponsor || + BN(fromAccountDetail.staticBalance).minus(fromSettlementFee).isGreaterThanOrEqualTo(0); return gasFeeEnough && storeFeeEnough; - }, [bankBalance, from, fromAccountDetail.staticBalance, fromSettlementFee, gasFee, loginAccount]); + }, [ + bankBalance, + from, + fromSponsor, + fromAccountDetail.staticBalance, + fromSettlementFee, + gasFee, + loginAccount, + ]); const validTo = useMemo(() => { const isOwnerAccount = loginAccount === to; @@ -96,17 +119,22 @@ export const useValidateChangePaymentFee = ({ return gasFeeEnough && storeFeeEnough; } - const storeFeeEnough = BN(toAccountDetail.staticBalance) - .minus(toSettlementFee) - .minus(storeFee) - .isGreaterThanOrEqualTo(0); + const storeFeeEnough = + toSponsor || + BN(toAccountDetail.staticBalance) + .minus(toSettlementFee) + .minus(storeFee) + .minus(quotaFee) + .isGreaterThanOrEqualTo(0); return gasFeeEnough && storeFeeEnough; }, [ + toSponsor, bankBalance, gasFee, loginAccount, storeFee, + quotaFee, to, toAccountDetail.staticBalance, toSettlementFee, diff --git a/apps/dcellar-web-ui/src/modules/bucket/components/BucketNameColumn.tsx b/apps/dcellar-web-ui/src/modules/bucket/components/BucketNameColumn.tsx index 96de0d43..e06059cb 100644 --- a/apps/dcellar-web-ui/src/modules/bucket/components/BucketNameColumn.tsx +++ b/apps/dcellar-web-ui/src/modules/bucket/components/BucketNameColumn.tsx @@ -1,5 +1,5 @@ import { IconFont } from '@/components/IconFont'; -import { useAppDispatch } from '@/store'; +import { useAppDispatch, useAppSelector } from '@/store'; import { BucketEntity } from '@/store/slices/bucket'; import { setObjectListPage } from '@/store/slices/object'; import { formatFullTime } from '@/utils/time'; @@ -14,7 +14,8 @@ interface BucketNameColumnProps { export const BucketNameColumn = memo(function BucketNameColumn({ item }) { const dispatch = useAppDispatch(); - const { DeleteAt, BucketStatus, BucketName } = item; + const bucketRecords = useAppSelector((root) => root.bucket.bucketRecords); + const { DeleteAt, BucketStatus, BucketName, OffChainStatus } = bucketRecords[item.BucketName]; const discontinue = BucketStatus === 1; const estimateTime = formatFullTime( +DeleteAt * 1000 + 7 * 24 * 60 * 60 * 1000, @@ -22,6 +23,7 @@ export const BucketNameColumn = memo(function BucketNameC ); const more = 'https://docs.nodereal.io/docs/dcellar-faq#question-what-is-discontinue'; const content = `This item will be deleted by SP with an estimated time of ${estimateTime}. Please backup your data in time.`; + const isFlowRateLimit = ['1', '3'].includes(OffChainStatus); return ( @@ -35,7 +37,14 @@ export const BucketNameColumn = memo(function BucketNameC {item.BucketName} - {discontinue && } + {(discontinue || isFlowRateLimit) && ( + + )} ); }); diff --git a/apps/dcellar-web-ui/src/modules/bucket/components/BucketOperations.tsx b/apps/dcellar-web-ui/src/modules/bucket/components/BucketOperations.tsx index 44e7498a..8b1ffdcb 100644 --- a/apps/dcellar-web-ui/src/modules/bucket/components/BucketOperations.tsx +++ b/apps/dcellar-web-ui/src/modules/bucket/components/BucketOperations.tsx @@ -12,11 +12,12 @@ import { useAppDispatch, useAppSelector } from '@/store'; import { BucketOperationsType, TBucket, setBucketOperation } from '@/store/slices/bucket'; import { ObjectMeta } from '@bnb-chain/greenfield-js-sdk/dist/esm/types/sp/Common'; import { ModalCloseButton } from '@node-real/uikit'; -import { useUnmount } from 'ahooks'; -import { memo, useCallback, useMemo } from 'react'; +import { useUnmount, useUpdateEffect } from 'ahooks'; +import { memo, useCallback, useEffect, useMemo } from 'react'; import { EditBucketTagsOperation } from './EditBucketTagsOperation'; import { PaymentAccountOperation } from './PaymentAccountOperation'; import { UpdateBucketTagsOperation } from './UpdateBucketTagsOperation'; +import { useRouter } from 'next/router'; interface BucketOperationsProps { level?: 0 | 1; @@ -26,6 +27,7 @@ export const BucketOperations = memo(function BucketOpera level = 0, }) { const dispatch = useAppDispatch(); + const { pathname } = useRouter(); const bucketOperation = useAppSelector((root) => root.bucket.bucketOperation); const bucketRecords = useAppSelector((root) => root.bucket.bucketRecords); const primarySpRecords = useAppSelector((root) => root.sp.primarySpRecords); @@ -83,6 +85,10 @@ export const BucketOperations = memo(function BucketOpera useUnmount(onClose); + useUpdateEffect(() => { + onClose(); + }, [pathname]); + return ( <> diff --git a/apps/dcellar-web-ui/src/modules/bucket/components/ChangePaymentTotalFees.tsx b/apps/dcellar-web-ui/src/modules/bucket/components/ChangePaymentTotalFees.tsx index 36fdc739..57e65d1c 100644 --- a/apps/dcellar-web-ui/src/modules/bucket/components/ChangePaymentTotalFees.tsx +++ b/apps/dcellar-web-ui/src/modules/bucket/components/ChangePaymentTotalFees.tsx @@ -21,6 +21,9 @@ export type ChangePaymentTotalFeeProps = { from: TSettlementFee; to: TSettlementFee; storeFee: string; + quotaFee: string; + fromSponsor: boolean; + toSponsor: boolean; }; const TipsLink = @@ -32,6 +35,9 @@ export const ChangePaymentTotalFee = ({ from, to, storeFee, + quotaFee, + fromSponsor, + toSponsor, }: ChangePaymentTotalFeeProps) => { const bankBalance = useAppSelector((root) => root.accounts.bankOrWalletBalance); @@ -42,6 +48,7 @@ export const ChangePaymentTotalFee = ({ .plus(from.amount) .plus(to.amount) .plus(storeFee) + .plus(quotaFee) .dp(CRYPTOCURRENCY_DISPLAY_PRECISION) .toString(); @@ -54,11 +61,21 @@ export const ChangePaymentTotalFee = ({ canExpand={true} Tips={Tips} > - - - - - + {!fromSponsor && ( + <> + + + + )} + + {!toSponsor && ( + <> + + + + + )} + diff --git a/apps/dcellar-web-ui/src/modules/bucket/components/CreateBucketOperation.tsx b/apps/dcellar-web-ui/src/modules/bucket/components/CreateBucketOperation.tsx index 4532c3eb..048e8989 100644 --- a/apps/dcellar-web-ui/src/modules/bucket/components/CreateBucketOperation.tsx +++ b/apps/dcellar-web-ui/src/modules/bucket/components/CreateBucketOperation.tsx @@ -38,7 +38,12 @@ import { E_GET_GAS_FEE_LACK_BALANCE_ERROR, E_OFF_CHAIN_AUTH } from '@/facade/err import { useSettlementFee } from '@/hooks/useSettlementFee'; import { PaymentAccountSelector } from '@/modules/bucket/components/PaymentAccountSelector'; import { TotalFees } from '@/modules/object/components/TotalFees'; -import { BUTTON_GOT_IT, WALLET_CONFIRM } from '@/modules/object/constant'; +import { + BUTTON_GOT_IT, + CONTINUE_STEP, + PAYMASTER_CONTINUE_DESC, + WALLET_CONFIRM, +} from '@/modules/object/constant'; import { PaymentInsufficientBalance } from '@/modules/object/utils'; import { MIN_AMOUNT } from '@/modules/wallet/constants'; import { useAppDispatch, useAppSelector } from '@/store'; @@ -111,8 +116,10 @@ export const CreateBucketOperation = memo(function C const { connector } = useAccount(); const { setOpenAuthModal } = useOffChainAuth(); + const isSponsorAddress = selectedPaRef.current?.name === ''; const PaymentAddress = selectedPaRef.current.address; - const { settlementFee } = useSettlementFee(PaymentAddress); + // no need to query sponsor account settlement fee + const { settlementFee } = useSettlementFee(isSponsorAddress ? '' : PaymentAddress); const accountDetail = useAppSelector(selectAccount(PaymentAddress)); const validTags = getValidTags(bucketEditTagsData); @@ -308,7 +315,7 @@ export const CreateBucketOperation = memo(function C [checkGasFee, setValue, validateName], ); - const onSubmit = async (data: any) => { + const doSubmit = async (data: any) => { dispatch( setSignatureAction({ icon: Animates.object, title: 'Creating Bucket', desc: WALLET_CONFIRM }), ); @@ -326,7 +333,6 @@ export const CreateBucketOperation = memo(function C const txs: TxResponse[] = []; const [bucketTx, error1] = await getCreateBucketTx(msgCreateBucket); if (!bucketTx) return errorHandler(error1); - txs.push(bucketTx); if (validTags.length > 0) { @@ -366,6 +372,23 @@ export const CreateBucketOperation = memo(function C }); }; + const onSubmit = async (data: any) => { + if (selectedPaRef.current?.name) { + return doSubmit(data); + } + dispatch( + setSignatureAction({ + icon: 'error-auth', + title: 'Confirm Payment Account', + desc: PAYMASTER_CONTINUE_DESC, + buttonText: CONTINUE_STEP, + buttonOnClick() { + setTimeout(doSubmit, 300, data); + }, + }), + ); + }; + const disableCreateButton = () => { return ( isSubmitting || @@ -395,7 +418,10 @@ export const CreateBucketOperation = memo(function C const onPaymentAccountChange = useCallback( async (pa: AccountEntity) => { selectedPaRef.current = pa; - await dispatch(setupAccountRecords(pa.address)); + if (pa.name !== '') { + // no need to query sponsor account record. + await dispatch(setupAccountRecords(pa.address)); + } const { value, available } = validateNameAndGas.name; if (available && value) { checkGasFee(value); @@ -521,7 +547,7 @@ export const CreateBucketOperation = memo(function C @@ -529,7 +555,7 @@ export const CreateBucketOperation = memo(function C gasFee={gasFee} storeFee={quotaFee} refundFee="0" - settlementFee={!chargeQuota ? '0' : settlementFee} + settlementFee={!chargeQuota || isSponsorAddress ? '0' : settlementFee} payGasFeeBalance={bankBalance} payStoreFeeBalance={accountDetail.staticBalance} ownerAccount={loginAccount} diff --git a/apps/dcellar-web-ui/src/modules/bucket/components/DiscontinueNotice.tsx b/apps/dcellar-web-ui/src/modules/bucket/components/DiscontinueNotice.tsx index fe055404..1d4b5b5a 100644 --- a/apps/dcellar-web-ui/src/modules/bucket/components/DiscontinueNotice.tsx +++ b/apps/dcellar-web-ui/src/modules/bucket/components/DiscontinueNotice.tsx @@ -1,31 +1,61 @@ import { IconFont } from '@/components/IconFont'; import { DCLink } from '@/components/common/DCLink'; -import { Box, Flex, Menu, MenuButton, MenuList, Text } from '@node-real/uikit'; +import { Box, Divider, Flex, Menu, MenuButton, MenuList, Text } from '@node-real/uikit'; export const DiscontinueNotice = ({ content, learnMore, + flowRateLimit = false, + discontinue = true, }: { content: string; learnMore: string; + flowRateLimit?: boolean; + discontinue?: boolean; }) => { + const account = discontinue && flowRateLimit ? 2 : 0; + // todo + const discontinueReasons = [ + { + title: 'Flow rate exceeds limit', + desc: "This bucket's flow rate has surpassed the payment account's...", + link: 'https://docs.nodereal.io/docs/dcellar-faq#question-what-is-flow-rate', + show: flowRateLimit, + }, + { title: 'Discontinue Notice', desc: content, link: learnMore, show: discontinue }, + ].filter((i) => i.show); + return ( <> - e.stopPropagation()}> + e.stopPropagation()} + whiteSpace={'nowrap'} + color={'#D9304E'} + display={'flex'} + alignItems={'center'} + fontWeight={600} + > + {!!account && <>{account}} e.stopPropagation()}> - - Discontinue Notice - - {content} - - e.stopPropagation()}> - Learn More - - + {discontinueReasons.map(({ title, desc, link }, index) => ( + + + {account > 1 && <>{index + 1}. } + {title} + + {desc} + + e.stopPropagation()}> + Learn More + + + {index !== discontinueReasons.length - 1 && } + + ))} diff --git a/apps/dcellar-web-ui/src/modules/bucket/components/PaymentAccountOperation.tsx b/apps/dcellar-web-ui/src/modules/bucket/components/PaymentAccountOperation.tsx index 621191f1..819ffef1 100644 --- a/apps/dcellar-web-ui/src/modules/bucket/components/PaymentAccountOperation.tsx +++ b/apps/dcellar-web-ui/src/modules/bucket/components/PaymentAccountOperation.tsx @@ -7,16 +7,22 @@ import { CopyText } from '@/components/common/CopyText'; import { DCButton } from '@/components/common/DCButton'; import { OWNER_ACCOUNT_NAME } from '@/constants/wallet'; import { useOffChainAuth } from '@/context/off-chain-auth/useOffChainAuth'; -import { UpdateBucketInfoPayload, updateBucketInfo } from '@/facade/bucket'; +import { updateBucketInfo, UpdateBucketInfoPayload } from '@/facade/bucket'; import { E_OFF_CHAIN_AUTH } from '@/facade/error'; import { useChangePaymentAccountFee, useValidateChangePaymentFee, } from '@/hooks/useChangePaymentAccountFee'; -import { BUTTON_GOT_IT, UNKNOWN_ERROR, WALLET_CONFIRM } from '@/modules/object/constant'; +import { + BUTTON_GOT_IT, + CONTINUE_STEP, + PAYMASTER_CONTINUE_DESC, + UNKNOWN_ERROR, + WALLET_CONFIRM, +} from '@/modules/object/constant'; import { useAppDispatch, useAppSelector } from '@/store'; -import { AccountEntity, selectAccount, selectPaymentAccounts } from '@/store/slices/accounts'; -import { TBucket, setBucketPaymentAccount } from '@/store/slices/bucket'; +import { AccountEntity, selectAccount } from '@/store/slices/accounts'; +import { setBucketPaymentAccount, setupBucket, TBucket } from '@/store/slices/bucket'; import { BN } from '@/utils/math'; import { trimLongStr } from '@/utils/string'; import styled from '@emotion/styled'; @@ -30,12 +36,12 @@ import { Text, toast, } from '@node-real/uikit'; -import { find } from 'lodash-es'; import { memo, useMemo, useState } from 'react'; import { useAccount } from 'wagmi'; import { PaymentAccountSelector } from '../components/PaymentAccountSelector'; import { ChangePaymentTotalFee } from './ChangePaymentTotalFees'; import { setSignatureAction } from '@/store/slices/global'; +import { useAccountType } from '@/hooks/useAccountType'; export const PaymentAccountOperation = memo(function PaymentAccountOperation({ bucket, @@ -47,11 +53,12 @@ export const PaymentAccountOperation = memo(function PaymentAccountOperation({ const dispatch = useAppDispatch(); const loginAccount = useAppSelector((root) => root.persist.loginAccount); const bankBalance = useAppSelector((root) => root.accounts.bankOrWalletBalance); - const ownerAccount = useAppSelector((root) => root.accounts.ownerAccount); - - const paymentAccountList = useAppSelector(selectPaymentAccounts(loginAccount)); const [newPaymentAccount, setNewPaymentAccount] = useState({} as AccountEntity); const newAccountDetail = useAppSelector(selectAccount(newPaymentAccount.address)); + const PaymentAddress = bucket.PaymentAddress; + const { pa, oa, isSponsor } = useAccountType(PaymentAddress); + const { isSponsor: toSponsor } = useAccountType(newPaymentAccount.address); + const fromSponsor = isSponsor; const [loading, setLoading] = useState(false); const { connector } = useAccount(); const { setOpenAuthModal } = useOffChainAuth(); @@ -60,6 +67,7 @@ export const PaymentAccountOperation = memo(function PaymentAccountOperation({ loading: loadingFee, gasFee, storeFee, + quotaFee, fromSettlementFee, toSettlementFee, } = useChangePaymentAccountFee({ @@ -67,15 +75,19 @@ export const PaymentAccountOperation = memo(function PaymentAccountOperation({ to: newPaymentAccount.address, // @ts-expect-error TODO storageSize: bucket.StorageSize, + readQuota: bucket.ChargedReadQuota, }); const { validFrom, validTo } = useValidateChangePaymentFee({ from: bucket.PaymentAddress, to: newPaymentAccount.address, storeFee, + quotaFee, gasFee, fromSettlementFee, toSettlementFee, + toSponsor, + fromSponsor, }); const InsufficientAccounts = []; @@ -100,16 +112,17 @@ export const PaymentAccountOperation = memo(function PaymentAccountOperation({ const paymentAccount = useMemo(() => { if (!bucket) return '--'; const address = bucket.PaymentAddress; - const pa = find(paymentAccountList, (a) => a.address === address); - const oa = ownerAccount.address === address; - - if (!pa && !oa) return '--'; const link = `${GREENFIELD_CHAIN_EXPLORER_URL}/account/${address}`; return ( <> - {oa ? OWNER_ACCOUNT_NAME : pa!.name} - | + {!isSponsor && ( + <> + {oa ? OWNER_ACCOUNT_NAME : pa!.name} + | + + )} + ); - }, [ownerAccount, paymentAccountList, bucket]); + }, [pa, oa, isSponsor, bucket]); const errorHandler = (error: string) => { switch (error) { @@ -147,7 +160,7 @@ export const PaymentAccountOperation = memo(function PaymentAccountOperation({ } }; - const onChangeConfirm = async () => { + const doChangePaymentAccount = async () => { if (loading) return; setLoading(true); dispatch( @@ -165,7 +178,6 @@ export const PaymentAccountOperation = memo(function PaymentAccountOperation({ paymentAddress: newPaymentAccount.address, chargedReadQuota: String(bucket.ChargedReadQuota), }; - const [txRes, txError] = await updateBucketInfo(payload, connector!); setLoading(false); if (!txRes || txRes.code !== 0) return errorHandler(txError || UNKNOWN_ERROR); @@ -178,6 +190,27 @@ export const PaymentAccountOperation = memo(function PaymentAccountOperation({ paymentAddress: newPaymentAccount.address, }), ); + if (toSponsor || fromSponsor) { + dispatch(setupBucket(bucket.BucketName)); + } + }; + + const onChangeConfirm = async () => { + if (toSponsor) { + dispatch( + setSignatureAction({ + icon: 'error-auth', + title: 'Confirm Payment Account', + desc: PAYMASTER_CONTINUE_DESC, + buttonText: CONTINUE_STEP, + buttonOnClick() { + setTimeout(doChangePaymentAccount, 300); + }, + }), + ); + } else { + doChangePaymentAccount(); + } }; return ( @@ -205,12 +238,15 @@ export const PaymentAccountOperation = memo(function PaymentAccountOperation({ setNewPaymentAccount(account)} /> - + {!toSponsor && } diff --git a/apps/dcellar-web-ui/src/modules/bucket/components/PaymentAccountSelector.tsx b/apps/dcellar-web-ui/src/modules/bucket/components/PaymentAccountSelector.tsx index 6a72e23b..43ead8fc 100644 --- a/apps/dcellar-web-ui/src/modules/bucket/components/PaymentAccountSelector.tsx +++ b/apps/dcellar-web-ui/src/modules/bucket/components/PaymentAccountSelector.tsx @@ -70,7 +70,8 @@ export const PaymentAccountSelector = memo( }, [router]); const onPaymentAccountChange = (value: string) => { - setPaymentAccount(keyAccountList[value]); + const account = keyAccountList[value]; + setPaymentAccount(account ?? { name: '', id: '', address: value }); }; const onSearch = (result: MenuOption[]) => { @@ -97,6 +98,7 @@ export const PaymentAccountSelector = memo( return ( ( ); const renderItem = (moniker: string, address: string) => { - return [moniker, trimLongStr(address, 10, 6, 4)].filter(Boolean).join(' | '); + return [moniker, !moniker ? address : trimLongStr(address, 10, 6, 4)].filter(Boolean).join(' | '); }; function OptionItem(props: MenuOption) { diff --git a/apps/dcellar-web-ui/src/modules/bucket/components/SPSelector/index.tsx b/apps/dcellar-web-ui/src/modules/bucket/components/SPSelector/index.tsx index 2b2dcc2f..649e33b0 100644 --- a/apps/dcellar-web-ui/src/modules/bucket/components/SPSelector/index.tsx +++ b/apps/dcellar-web-ui/src/modules/bucket/components/SPSelector/index.tsx @@ -11,6 +11,7 @@ import { sort } from 'radash'; import { memo } from 'react'; import { TH } from './style'; import { OptionItem } from './OptionItem'; +import { isEmpty } from 'lodash-es'; interface SPSelectorProps { onChange: (value: SpEntity) => void; @@ -118,7 +119,9 @@ export const SPSelector = memo(function SPSelector({ onChange } latency[`${origin}:${port || 443}`] = parseInt(String(entry.duration)); } }); - dispatch(setSpLatency(latency)); + if (!isEmpty(latency)) { + dispatch(setSpLatency(latency)); + } }); // reuse offchain auth request nonce call data observer.observe({ type: 'resource', buffered: true }); diff --git a/apps/dcellar-web-ui/src/modules/group/components/GroupOperations.tsx b/apps/dcellar-web-ui/src/modules/group/components/GroupOperations.tsx index 1989f328..3181c02e 100644 --- a/apps/dcellar-web-ui/src/modules/group/components/GroupOperations.tsx +++ b/apps/dcellar-web-ui/src/modules/group/components/GroupOperations.tsx @@ -9,11 +9,12 @@ import { useAppDispatch, useAppSelector } from '@/store'; import { GroupOperationsType, selectGroupList, setGroupOperation } from '@/store/slices/group'; import { GroupInfo } from '@bnb-chain/greenfield-cosmos-types/greenfield/storage/types'; import { ModalCloseButton } from '@node-real/uikit'; -import { useUnmount } from 'ahooks'; +import { useUnmount, useUpdateEffect } from 'ahooks'; import { find } from 'lodash-es'; import { memo, useCallback, useEffect, useMemo } from 'react'; import { EditGroupTagsOperation } from './EditGroupTagsOperation'; import { UpdateGroupTagsOperation } from './UpdateGroupTagsOperation'; +import { useRouter } from 'next/router'; interface GroupOperationsProps { level?: 0 | 1; @@ -21,6 +22,7 @@ interface GroupOperationsProps { export const GroupOperations = memo(function GroupOperations({ level = 0 }) { const dispatch = useAppDispatch(); + const { pathname } = useRouter(); const loginAccount = useAppSelector((root) => root.persist.loginAccount); const groupOperation = useAppSelector((root) => root.group.groupOperation); const groupList = useAppSelector(selectGroupList(loginAccount)); @@ -72,6 +74,10 @@ export const GroupOperations = memo(function GroupOperatio useUnmount(onClose); + useUpdateEffect(() => { + onClose(); + }, [pathname]); + return ( <> diff --git a/apps/dcellar-web-ui/src/modules/object/components/CreateFolderOperation.tsx b/apps/dcellar-web-ui/src/modules/object/components/CreateFolderOperation.tsx index fdf2d8b8..722cb3c5 100644 --- a/apps/dcellar-web-ui/src/modules/object/components/CreateFolderOperation.tsx +++ b/apps/dcellar-web-ui/src/modules/object/components/CreateFolderOperation.tsx @@ -11,7 +11,6 @@ import { E_OFF_CHAIN_AUTH, E_USER_REJECT_STATUS_NUM, ErrorResponse, - commonFault, createTxFault, simulateFault, } from '@/facade/error'; @@ -75,6 +74,7 @@ import { useAccount } from 'wagmi'; import { TotalFees } from './TotalFees'; import { MsgCreateObject } from '@bnb-chain/greenfield-cosmos-types/greenfield/storage/tx'; import { getSpOffChainData } from '@/store/slices/persist'; +import { useAccountType } from '@/hooks/useAccountType'; interface CreateFolderOperationProps { selectBucket: TBucket; @@ -119,6 +119,7 @@ export const CreateFolderOperation = memo(function C const folderList = objectListRecords[completeCommonPrefix]?.filter((item) => item.objectName.endsWith('/')) || []; const isOwnerAccount = accountDetail.address === loginAccount; + const { isSponsor } = useAccountType(accountDetail.address); const validTags = getValidTags(objectEditTagsData); const isSetTags = validTags.length > 0; const { gasFee: createBucketGasFee } = gnfdGasFeesConfig?.[MsgCreateObjectTypeUrl] || {}; @@ -487,7 +488,7 @@ export const CreateFolderOperation = memo(function C }, []); useEffect(() => { - if (isEmpty(storeFeeParams)) { + if (isEmpty(storeFeeParams) || isSponsor) { return; } const nGasFee = BigNumber(gasFee); @@ -504,6 +505,7 @@ export const CreateFolderOperation = memo(function C } } }, [ + isSponsor, gasFee, bankBalance, storeFee, diff --git a/apps/dcellar-web-ui/src/modules/object/components/CreateObject.tsx b/apps/dcellar-web-ui/src/modules/object/components/CreateObject.tsx index 50e41980..5df5ba7f 100644 --- a/apps/dcellar-web-ui/src/modules/object/components/CreateObject.tsx +++ b/apps/dcellar-web-ui/src/modules/object/components/CreateObject.tsx @@ -6,7 +6,7 @@ import { BatchOperations } from '@/modules/object/components/BatchOperations'; import { UploadMenuList } from '@/modules/object/components/UploadMenuList'; import { useAppDispatch, useAppSelector } from '@/store'; import { selectAccount } from '@/store/slices/accounts'; -import { setupBucketQuota } from '@/store/slices/bucket'; +import { setupBucket, setupBucketQuota } from '@/store/slices/bucket'; import { SELECT_OBJECT_NUM_LIMIT, SINGLE_OBJECT_MAX_SIZE, @@ -54,6 +54,7 @@ export const CreateObject = memo(function NewObject({ const primarySpRecords = useAppSelector((root) => root.sp.primarySpRecords); const bucket = bucketRecords[currentBucketName]; const accountDetail = useAppSelector(selectAccount(bucket?.PaymentAddress)); + const isFlowRateLimit = ['1', '3'].includes(bucket?.OffChainStatus); const primarySp = primarySpRecords[currentBucketName]; const onOpenCreateFolder = () => { if (disabled) return; @@ -76,6 +77,7 @@ export const CreateObject = memo(function NewObject({ dispatch(setupBucketQuota(currentBucketName)); dispatch(setObjectListRefreshing(true)); dispatch(setObjectListPageRestored(false)); + dispatch(setupBucket(currentBucketName)); await dispatch(setupListObjects(params)); dispatch(setObjectListRefreshing(false)); }, 150), @@ -102,9 +104,15 @@ export const CreateObject = memo(function NewObject({ const maxFolderDepth = invalidPath || pathSegments.length >= MAX_FOLDER_LEVEL; const loading = !objectListRecords[completeCommonPrefix]; - const disabled = maxFolderDepth || isBucketDiscontinue || loading || accountDetail.clientFrozen; + const disabled = + maxFolderDepth || + isBucketDiscontinue || + isFlowRateLimit || + loading || + accountDetail.clientFrozen; const uploadDisabled = isBucketDiscontinue || + isFlowRateLimit || invalidPath || pathSegments.length > MAX_FOLDER_LEVEL || loading || @@ -185,13 +193,15 @@ export const CreateObject = memo(function NewObject({ content={ isBucketDiscontinue ? 'Bucket in the discontinue status cannot upload objects.' - : accountDetail?.clientFrozen - ? 'The payment account in the frozen status cannot upload objects.' - : uploadDisabled - ? 'Path invalid' - : `Please limit object size to ${formatBytes( - SINGLE_OBJECT_MAX_SIZE, - )} and upload a maximum of ${SELECT_OBJECT_NUM_LIMIT} objects at a time.` + : isFlowRateLimit + ? "The bucket's flow rate exceeds the payment account limit. Contact the account owner or switch accounts to increase it." + : accountDetail?.clientFrozen + ? 'The payment account in the frozen status cannot upload objects.' + : uploadDisabled + ? 'Path invalid' + : `Please limit object size to ${formatBytes( + SINGLE_OBJECT_MAX_SIZE, + )} and upload a maximum of ${SELECT_OBJECT_NUM_LIMIT} objects at a time.` } >
diff --git a/apps/dcellar-web-ui/src/modules/object/components/ObjectOperations.tsx b/apps/dcellar-web-ui/src/modules/object/components/ObjectOperations.tsx index 870bd189..369b0efe 100644 --- a/apps/dcellar-web-ui/src/modules/object/components/ObjectOperations.tsx +++ b/apps/dcellar-web-ui/src/modules/object/components/ObjectOperations.tsx @@ -23,11 +23,12 @@ import { import { getSpOffChainData } from '@/store/slices/persist'; import { ObjectMeta } from '@bnb-chain/greenfield-js-sdk/dist/esm/types/sp/Common'; import { ModalCloseButton } from '@node-real/uikit'; -import { useUnmount } from 'ahooks'; +import { useUnmount, useUpdateEffect } from 'ahooks'; import { get, isEmpty } from 'lodash-es'; import { memo, useCallback, useEffect, useMemo } from 'react'; import { EditObjectTagsOperation } from './EditObjectTagsOperation'; import { UpdateObjectTagsOperation } from './UpdateObjectTagsOperation'; +import { useRouter } from 'next/router'; interface ObjectOperationsProps { level?: 0 | 1; @@ -37,6 +38,7 @@ export const ObjectOperations = memo(function ObjectOpera level = 0, }) { const dispatch = useAppDispatch(); + const { pathname } = useRouter(); const loginAccount = useAppSelector((root) => root.persist.loginAccount); const objectOperation = useAppSelector((root) => root.object.objectOperation); const objectRecords = useAppSelector((root) => root.object.objectRecords); @@ -222,6 +224,10 @@ export const ObjectOperations = memo(function ObjectOpera useUnmount(onClose); + useUpdateEffect(() => { + onClose(); + }, [pathname]); + return ( <> diff --git a/apps/dcellar-web-ui/src/modules/object/components/TotalFees/index.tsx b/apps/dcellar-web-ui/src/modules/object/components/TotalFees/index.tsx index 55767f6f..61472495 100644 --- a/apps/dcellar-web-ui/src/modules/object/components/TotalFees/index.tsx +++ b/apps/dcellar-web-ui/src/modules/object/components/TotalFees/index.tsx @@ -14,6 +14,7 @@ import { find } from 'lodash-es'; import { memo } from 'react'; import { PrePaidTips } from './PrepaidTips'; import { SettlementTips } from './SettlementTips'; +import { useAccountType } from '@/hooks/useAccountType'; interface TotalFeesProps { gasFee: string | number; @@ -42,6 +43,7 @@ export const TotalFees = memo(function TotalFeesItem(props) { const bankBalance = useAppSelector(selectAvailableBalance(loginAccount)); const staticBalance = useAppSelector(selectAvailableBalance(payStoreFeeAddress)); const paymentAccounts = useAppSelector(selectPaymentAccounts(loginAccount)); + const { isSponsor } = useAccountType(payStoreFeeAddress); const paymentAccount = find( paymentAccounts, @@ -75,10 +77,12 @@ export const TotalFees = memo(function TotalFeesItem(props) { justifySelf={'flex-end'} fontWeight={'400'} > - {renderFeeValue( - BigNumber(gasFee).plus(BigNumber(prepaidFee)).plus(settlementFee).toString(), - exchangeRate, - )} + {isSponsor + ? renderFeeValue(BigNumber(gasFee).toString(), exchangeRate) + : renderFeeValue( + BigNumber(gasFee).plus(BigNumber(prepaidFee)).plus(settlementFee).toString(), + exchangeRate, + )} {expandable && ( (function TotalFeesItem(props) { {isOpenFees && } {isOpenFees && ( <> - - - - {refund ? 'Prepaid fee refund' : 'Prepaid fee'} - - - - - {refund && ( - - Refund + {!isSponsor && ( + <> + + + + {refund ? 'Prepaid fee refund' : 'Prepaid fee'} + + + + + {refund && ( + + Refund + + )} + {renderFeeValue(prepaidFee, exchangeRate)} - )} - {renderFeeValue(prepaidFee, exchangeRate)} - - + - - - - Settlement fee - - - - {renderFeeValue(settlementFee, exchangeRate)} - - {paymentAccount && ( - - - - {paymentLabel} {renderBalanceNumber(staticBalance || '0')} ( - {renderUsd(staticBalance || '0', exchangeRate)}) - - + + + + Settlement fee + + + + {renderFeeValue(settlementFee, exchangeRate)} + + {paymentAccount && ( + + + + {paymentLabel} {renderBalanceNumber(staticBalance || '0')} ( + {renderUsd(staticBalance || '0', exchangeRate)}) + + + )} + )} {+gasFee !== 0 && ( diff --git a/apps/dcellar-web-ui/src/modules/object/constant.ts b/apps/dcellar-web-ui/src/modules/object/constant.ts index 71da7677..85a452c2 100644 --- a/apps/dcellar-web-ui/src/modules/object/constant.ts +++ b/apps/dcellar-web-ui/src/modules/object/constant.ts @@ -44,6 +44,9 @@ const DUPLICATE_OBJECT_NAME = 'This name is already taken, try another one.'; const UNKNOWN_ERROR = `Unknown error. Please try again later.`; const AUTH_EXPIRED = 'Authentication Expired'; const WALLET_CONFIRM = 'Please confirm the transaction in your wallet.'; +export const PAYMASTER_CONTINUE_DESC = + 'This payment account does not belong to you. Please ensure that the account owner has set the flow rate for you; otherwise, you will be unable to upload anything to this bucket.'; +export const CONTINUE_STEP = 'Continue'; export const EMPTY_TX_HASH = '0x0000000000000000000000000000000000000000000000000000000000000000'; diff --git a/apps/dcellar-web-ui/src/modules/object/index.tsx b/apps/dcellar-web-ui/src/modules/object/index.tsx index 7247b094..8e05deae 100644 --- a/apps/dcellar-web-ui/src/modules/object/index.tsx +++ b/apps/dcellar-web-ui/src/modules/object/index.tsx @@ -46,6 +46,7 @@ export const ObjectsPage = () => { const title = last(items)!; const [bucketName, ...folders] = items; const bucket = bucketRecords[bucketName]; + const isFlowRateLimit = ['1', '3'].includes(bucket?.OffChainStatus); const selected = objectSelectedKeys.length; @@ -135,6 +136,9 @@ export const ObjectsPage = () => { content="You are browsing a bucket created by someone else. Certain functions may be restricted." /> )} + {isFlowRateLimit && isBucketOwner && ( + + )} {isBucketDiscontinue && isBucketOwner && ( )} diff --git a/apps/dcellar-web-ui/src/modules/object/utils/index.tsx b/apps/dcellar-web-ui/src/modules/object/utils/index.tsx index 756b9bf2..0a17b2cc 100644 --- a/apps/dcellar-web-ui/src/modules/object/utils/index.tsx +++ b/apps/dcellar-web-ui/src/modules/object/utils/index.tsx @@ -11,6 +11,7 @@ import { } from '@/modules/wallet/constants'; import { BN } from '@/utils/math'; import { displayTokenSymbol, getNumInDigits } from '@/utils/wallet'; +import { useAccountType } from '@/hooks/useAccountType'; const renderFeeValue = (bnbValue: string, exchangeRate: number | string) => { if (!bnbValue || Number(bnbValue) < 0 || isNaN(Number(bnbValue))) { @@ -223,6 +224,7 @@ export const PaymentInsufficientBalance = memo( } = props; const [items, setItems] = useState>([]); const isOwnerAccount = ownerAccount === payAccount; + const { isSponsor } = useAccountType(payAccount); useMount(() => { onValidate(true); @@ -238,13 +240,14 @@ export const PaymentInsufficientBalance = memo( return; } const items = []; - if (isOwnerAccount) { + if (isOwnerAccount || isSponsor) { // If is owner account, bankBalance can pay store fee. if ( BN(payGasFeeBalance).lt(BN(gasFee)) || - BN(BN(payGasFeeBalance).plus(payStoreFeeBalance)).lt( - BN(gasFee).plus(storeFee).plus(settlementFee).minus(refundFee), - ) + (isOwnerAccount && + BN(BN(payGasFeeBalance).plus(payStoreFeeBalance)).lt( + BN(gasFee).plus(storeFee).plus(settlementFee).minus(refundFee), + )) ) { items.push({ link: InternalRoutePaths.transfer_in, @@ -309,6 +312,7 @@ const renderPaymentInsufficientBalance = ({ ownerAccount, payAccount, gaOptions, + isSponsor = false, }: { gasFee: string | number; storeFee: string; @@ -318,18 +322,20 @@ const renderPaymentInsufficientBalance = ({ payStoreFeeBalance: string; ownerAccount: string; payAccount: string; + isSponsor?: boolean; gaOptions?: { gaClickName: string; gaShowName: string }; }) => { if (!gasFee || Number(gasFee) < 0) return <>; const items = []; const isOwnerAccount = ownerAccount === payAccount; - if (isOwnerAccount) { + if (isOwnerAccount || isSponsor) { // If is owner account, bankBalance can pay store fee. if ( BN(payGasFeeBalance).lt(BN(gasFee)) || - BN(BN(payGasFeeBalance).plus(payStoreFeeBalance)).lt( - BN(gasFee).plus(storeFee).plus(settlementFee).minus(refundFee), - ) + (isOwnerAccount && + BN(BN(payGasFeeBalance).plus(payStoreFeeBalance)).lt( + BN(gasFee).plus(storeFee).plus(settlementFee).minus(refundFee), + )) ) { items.push({ link: InternalRoutePaths.transfer_in, diff --git a/apps/dcellar-web-ui/src/modules/upload/UploadObjectsFees.tsx b/apps/dcellar-web-ui/src/modules/upload/UploadObjectsFees.tsx index d728772f..3c8dce14 100644 --- a/apps/dcellar-web-ui/src/modules/upload/UploadObjectsFees.tsx +++ b/apps/dcellar-web-ui/src/modules/upload/UploadObjectsFees.tsx @@ -18,6 +18,7 @@ import { selectLocateBucket, setObjectOperation } from '@/store/slices/object'; import { BN } from '@/utils/math'; import { getStoreNetflowRate } from '@/utils/payment'; import { isUploadObjectUpdate } from '@/utils/object'; +import { useAccountType } from '@/hooks/useAccountType'; interface FeesProps { delegateUpload: boolean; @@ -37,6 +38,7 @@ export const UploadObjectsFees = memo(function Fees({ delegateUpload, const payStoreFeeAccount = useAppSelector(selectAccount(bucket.PaymentAddress)); const availableBalance = useAppSelector(selectAvailableBalance(payStoreFeeAccount.address)); const { settlementFee } = useSettlementFee(bucket.PaymentAddress); + const { isSponsor } = useAccountType(payStoreFeeAccount.address); const { gasFee: singleTxGasFee } = gnfdGasFeesConfig?.[MsgCreateObjectTypeUrl] || {}; const isOwnerAccount = payStoreFeeAccount.address === loginAccount; @@ -90,7 +92,8 @@ export const UploadObjectsFees = memo(function Fees({ delegateUpload, }, [delegateUpload, isChecking, objectWaitQueue, singleTxGasFee, createTmpAccountGasFee]); const isBalanceAvailable = useMemo(() => { - if (isOwnerAccount) { + if (isOwnerAccount || isSponsor) { + if (isSponsor) return BN(bankBalance).minus(BN(gasFee)).isPositive(); return ( BN(bankBalance).minus(BN(gasFee)).isPositive() && BN(bankBalance) @@ -107,7 +110,7 @@ export const UploadObjectsFees = memo(function Fees({ delegateUpload, .isPositive() ); } - }, [bankBalance, gasFee, isOwnerAccount, storeFee, payStoreFeeAccount?.staticBalance]); + }, [isSponsor, bankBalance, gasFee, isOwnerAccount, storeFee, payStoreFeeAccount?.staticBalance]); useAsyncEffect(async () => { if (isEmpty(storeFeeParams)) { @@ -125,8 +128,11 @@ export const UploadObjectsFees = memo(function Fees({ delegateUpload, 'upload', { gasFee: BN(gasFee).toString(DECIMAL_NUMBER), - preLockFee: BN(storeFee).toString(DECIMAL_NUMBER), - totalFee: BN(gasFee).plus(BN(storeFee)).plus(settlementFee).toString(DECIMAL_NUMBER), + preLockFee: isSponsor ? '0' : BN(storeFee).toString(DECIMAL_NUMBER), + totalFee: (isSponsor + ? BN(gasFee) + : BN(gasFee).plus(BN(storeFee)).plus(settlementFee) + ).toString(DECIMAL_NUMBER), isBalanceAvailable: isBalanceAvailable, }, ], @@ -134,6 +140,7 @@ export const UploadObjectsFees = memo(function Fees({ delegateUpload, ); } }, [ + isSponsor, availableBalance, dispatch, gasFee, @@ -163,6 +170,7 @@ export const UploadObjectsFees = memo(function Fees({ delegateUpload, payStoreFeeBalance: payStoreFeeAccount.staticBalance, payAccount: payStoreFeeAccount.address, ownerAccount: loginAccount, + isSponsor, })} diff --git a/apps/dcellar-web-ui/src/utils/payment/index.tsx b/apps/dcellar-web-ui/src/utils/payment/index.tsx index ad06080b..520ba93e 100644 --- a/apps/dcellar-web-ui/src/utils/payment/index.tsx +++ b/apps/dcellar-web-ui/src/utils/payment/index.tsx @@ -21,7 +21,11 @@ export const getSettlementFee = async (address: string) => { return [amount, null]; }; -export const getStoreNetflowRate = (size: number, storeFeeParams: StoreFeeParams) => { +export const getStoreNetflowRate = ( + size: number, + storeFeeParams: StoreFeeParams, + isChargeSize = false, +) => { const { primarySpStorePrice, secondarySpStorePrice, @@ -30,7 +34,7 @@ export const getStoreNetflowRate = (size: number, storeFeeParams: StoreFeeParams minChargeSize, validatorTaxRate, } = storeFeeParams; - const chargeSize = size >= minChargeSize ? size : minChargeSize; + const chargeSize = isChargeSize ? size : size >= minChargeSize ? size : minChargeSize; const primarySpRate = BN(primarySpStorePrice).times(BN(chargeSize)); const secondarySpNum = redundantDataChunkNum + redundantParityChunkNum; let secondarySpRate = BN(secondarySpStorePrice).times(BN(chargeSize)); From ddef9e9d008d138a6c538a39750f813b720af126 Mon Sep 17 00:00:00 2001 From: aidencao Date: Thu, 16 May 2024 10:36:24 +0800 Subject: [PATCH 2/2] feat(dcellar-web-ui): update flow rate limit tooltip --- .../src/modules/bucket/components/DiscontinueNotice.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/dcellar-web-ui/src/modules/bucket/components/DiscontinueNotice.tsx b/apps/dcellar-web-ui/src/modules/bucket/components/DiscontinueNotice.tsx index 1d4b5b5a..bade8963 100644 --- a/apps/dcellar-web-ui/src/modules/bucket/components/DiscontinueNotice.tsx +++ b/apps/dcellar-web-ui/src/modules/bucket/components/DiscontinueNotice.tsx @@ -14,12 +14,11 @@ export const DiscontinueNotice = ({ discontinue?: boolean; }) => { const account = discontinue && flowRateLimit ? 2 : 0; - // todo const discontinueReasons = [ { title: 'Flow rate exceeds limit', - desc: "This bucket's flow rate has surpassed the payment account's...", - link: 'https://docs.nodereal.io/docs/dcellar-faq#question-what-is-flow-rate', + desc: "The bucket's flow rate exceeds the payment account limit. Contact the account owner or switch accounts to increase it.", + link: 'https://docs.nodereal.io/docs/dcellar-faq#question-why-is-my-bucket-flow-rate-limited', show: flowRateLimit, }, { title: 'Discontinue Notice', desc: content, link: learnMore, show: discontinue },