diff --git a/apps/dcellar-web-ui/CHANGELOG.json b/apps/dcellar-web-ui/CHANGELOG.json index 6600f228..df99654a 100644 --- a/apps/dcellar-web-ui/CHANGELOG.json +++ b/apps/dcellar-web-ui/CHANGELOG.json @@ -1,6 +1,21 @@ { "name": "dcellar-web-ui", "entries": [ + { + "version": "1.5.0", + "tag": "dcellar-web-ui_v1.5.0", + "date": "Thu, 23 May 2024 03:42:29 GMT", + "comments": { + "minor": [ + { + "comment": "Support sponsor payment account" + }, + { + "comment": "Introduce the migrate bucket feature" + } + ] + } + }, { "version": "1.4.0", "tag": "dcellar-web-ui_v1.4.0", diff --git a/apps/dcellar-web-ui/CHANGELOG.md b/apps/dcellar-web-ui/CHANGELOG.md index 1c0b8d7a..878b0642 100644 --- a/apps/dcellar-web-ui/CHANGELOG.md +++ b/apps/dcellar-web-ui/CHANGELOG.md @@ -1,6 +1,14 @@ # Change Log - dcellar-web-ui -This log was last generated on Thu, 16 May 2024 09:14:19 GMT and should not be manually modified. +This log was last generated on Thu, 23 May 2024 03:42:29 GMT and should not be manually modified. + +## 1.5.0 +Thu, 23 May 2024 03:42:29 GMT + +### Minor changes + +- Support sponsor payment account +- Introduce the migrate bucket feature ## 1.4.0 Thu, 16 May 2024 09:14:19 GMT diff --git a/apps/dcellar-web-ui/package.json b/apps/dcellar-web-ui/package.json index 94210fef..1ef8bfae 100644 --- a/apps/dcellar-web-ui/package.json +++ b/apps/dcellar-web-ui/package.json @@ -1,6 +1,6 @@ { "name": "dcellar-web-ui", - "version": "1.4.0", + "version": "1.5.0", "private": false, "scripts": { "dev": "node ./scripts/dev.js -p 3200", @@ -19,7 +19,7 @@ "antd": "5.11.0", "ahooks": "3.7.7", "hash-wasm": "4.10.0", - "@bnb-chain/greenfield-js-sdk": "2.0.0-alpha.7", + "@bnb-chain/greenfield-js-sdk": "2.1.0-alpha.0", "@bnb-chain/greenfield-cosmos-types": "0.4.0-alpha.32", "@emotion/react": "^11.10.5", "@emotion/styled": "^11.10.5", diff --git a/apps/dcellar-web-ui/src/components/Fee/InsufficientBalance.tsx b/apps/dcellar-web-ui/src/components/Fee/InsufficientBalances.tsx similarity index 82% rename from apps/dcellar-web-ui/src/components/Fee/InsufficientBalance.tsx rename to apps/dcellar-web-ui/src/components/Fee/InsufficientBalances.tsx index 4bf99e97..066b366d 100644 --- a/apps/dcellar-web-ui/src/components/Fee/InsufficientBalance.tsx +++ b/apps/dcellar-web-ui/src/components/Fee/InsufficientBalances.tsx @@ -3,24 +3,24 @@ import { Flex, Text } from '@node-real/uikit'; import NextLink from 'next/link'; import { useEffect, useState } from 'react'; -export type InsufficientBalanceProps = { +export type InsufficientBalancesProps = { loginAccount: string; - accounts: { address: string }[]; + accounts: string[]; }; -export const InsufficientBalance = ({ loginAccount, accounts }: InsufficientBalanceProps) => { +export const InsufficientBalances = ({ loginAccount, accounts }: InsufficientBalancesProps) => { const [activeWays, setActiveWays] = useState<{ link: string; text: string }[]>([]); useEffect(() => { const ways = accounts.map((account) => { - const isOwnerAccount = account.address === loginAccount; + const isOwnerAccount = account === loginAccount; return isOwnerAccount ? { link: InternalRoutePaths.transfer_in, text: 'Transfer in', } : { - link: `${InternalRoutePaths.send}&from=${loginAccount}&to=${account.address}`, + link: `${InternalRoutePaths.send}&from=${loginAccount}&to=${account}`, text: 'Send', }; }); diff --git a/apps/dcellar-web-ui/src/components/layout/index.tsx b/apps/dcellar-web-ui/src/components/layout/index.tsx index 4996a008..99261e2e 100644 --- a/apps/dcellar-web-ui/src/components/layout/index.tsx +++ b/apps/dcellar-web-ui/src/components/layout/index.tsx @@ -19,6 +19,7 @@ interface LayoutProps extends PropsWithChildren {} export const Layout = memo(function Layout({ children }) { const dispatch = useAppDispatch(); const isBucketDiscontinue = useAppSelector((root) => root.bucket.isBucketDiscontinue); + const isBucketMigrating = useAppSelector((root) => root.bucket.isBucketMigrating); const isBucketOwner = useAppSelector((root) => root.bucket.isBucketOwner); const bucketRecords = useAppSelector((root) => root.bucket.bucketRecords); const currentBucketName = useAppSelector((root) => root.object.currentBucketName); @@ -49,7 +50,13 @@ export const Layout = memo(function Layout({ children }) { }, hover() { if (pathname !== '/buckets/[...path]') return; - if (isBucketDiscontinue || !isBucketOwner || accountDetail.clientFrozen || !folderExist) + if ( + isBucketDiscontinue || + isBucketMigrating || + !isBucketOwner || + accountDetail.clientFrozen || + !folderExist + ) return; dispatch(setObjectOperation({ operation: ['', 'upload'] })); }, diff --git a/apps/dcellar-web-ui/src/facade/bucket.ts b/apps/dcellar-web-ui/src/facade/bucket.ts index 1c52fb7d..d2133496 100644 --- a/apps/dcellar-web-ui/src/facade/bucket.ts +++ b/apps/dcellar-web-ui/src/facade/bucket.ts @@ -1,4 +1,4 @@ -import { resolve } from '@/facade/common'; +import { DeliverTxResponse, broadcastTx, resolve } from '@/facade/common'; import { ErrorResponse, broadcastFault, @@ -18,6 +18,7 @@ import { QueryQuoteUpdateTimeResponse, } from '@bnb-chain/greenfield-cosmos-types/greenfield/storage/query'; import { + MsgCancelMigrateBucket, MsgCreateBucket, MsgUpdateBucketInfo, } from '@bnb-chain/greenfield-cosmos-types/greenfield/storage/tx'; @@ -30,6 +31,7 @@ import { IQuotaProps, ISimulateGasFee, Long, + MigrateBucketApprovalRequest, ReadQuotaRequest, SpResponse, TxResponse, @@ -456,3 +458,28 @@ export const getBucketActivities = async (id: string): Promise => { return result.data.result || []; }; + +export const migrateBucket = async ( + params: MigrateBucketApprovalRequest, + authType: AuthType, + connector: Connector, +): Promise => { + const client = await getClient(); + const [tx, error1] = await client.bucket + .migrateBucket(params, authType) + .then(resolve, createTxFault); + if (!tx) return [null, error1]; + + return broadcastTx({ tx: tx, address: params.operator, connector }); +}; + +export const cancelMigrateBucket = async ( + params: MsgCancelMigrateBucket, + connector: Connector, +): Promise => { + const client = await getClient(); + const [tx, error1] = await client.bucket.cancelMigrateBucket(params).then(resolve, createTxFault); + if (!tx) return [null, error1]; + + return broadcastTx({ tx: tx, address: params.operator, connector }); +}; diff --git a/apps/dcellar-web-ui/src/facade/object.ts b/apps/dcellar-web-ui/src/facade/object.ts index cfcd6fcc..9b3a016e 100644 --- a/apps/dcellar-web-ui/src/facade/object.ts +++ b/apps/dcellar-web-ui/src/facade/object.ts @@ -1,6 +1,6 @@ import { GROUP_ID } from '@/constants/legacy'; import { quotaRemains } from '@/facade/bucket'; -import { getObjectInfoAndBucketQuota, resolve, resolveSpRequest } from '@/facade/common'; +import { getObjectInfoAndBucketQuota, resolve } from '@/facade/common'; import { E_NOT_FOUND, E_NO_QUOTA, @@ -41,6 +41,7 @@ import { } from '@bnb-chain/greenfield-cosmos-types/greenfield/storage/types'; import { AuthType, + DelegateCreateFolderRepsonse, DelegatedCreateFolderRequest, GRNToString, IQuotaProps, @@ -713,8 +714,14 @@ export const updateObjectTags = async (params: UpdateObjectTagsParams, connector export const delegateCreateFolder = async ( request: DelegatedCreateFolderRequest, auth: AuthType, -) => { +): Promise => { const client = await getClient(); + const [result, error] = await client.object + .delegateCreateFolder(request, auth) + .then(resolve, commonFault); + + if (!result || error) return [null, error]; + if (result.code !== 0 || !result.body) return [null, result.message ?? '']; - return client.object.delegateCreateFolder(request, auth).then(resolveSpRequest, commonFault); + return [result.body, null]; }; 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 e06059cb..21a5f067 100644 --- a/apps/dcellar-web-ui/src/modules/bucket/components/BucketNameColumn.tsx +++ b/apps/dcellar-web-ui/src/modules/bucket/components/BucketNameColumn.tsx @@ -5,8 +5,9 @@ import { setObjectListPage } from '@/store/slices/object'; import { formatFullTime } from '@/utils/time'; import styled from '@emotion/styled'; import Link from 'next/link'; -import { memo } from 'react'; -import { DiscontinueNotice } from './DiscontinueNotice'; +import { memo, useMemo } from 'react'; +import { BucketStatus as BucketStatusEnum } from '@bnb-chain/greenfield-js-sdk'; +import { BucketStatusNotice } from './BucketStatusNotice'; interface BucketNameColumnProps { item: BucketEntity; @@ -16,14 +17,33 @@ export const BucketNameColumn = memo(function BucketNameC const dispatch = useAppDispatch(); 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, - 'YYYY-MM-DD HH:mm:ss', - ); - 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); + const bucketStatusReason = useMemo(() => { + switch (BucketStatus) { + case BucketStatusEnum.BUCKET_STATUS_DISCONTINUED: { + const estimateTime = formatFullTime( + +DeleteAt * 1000 + 7 * 24 * 60 * 60 * 1000, + 'YYYY-MM-DD HH:mm:ss', + ); + return { + icon: 'colored-error2', + title: 'Discontinue Notice', + link: 'https://docs.nodereal.io/docs/dcellar-faq#question-what-is-discontinue', + desc: `This item will be deleted by SP with an estimated time of ${estimateTime}. Please backup your data in time.`, + show: true, + }; + } + case BucketStatusEnum.BUCKET_STATUS_MIGRATING: + return { + icon: 'migrate', + title: 'Data Migrating', + desc: 'This bucket, in the process of data migration to another provider, supports only downloads, quota modifications, deletions, and sharing. It does not support uploads.', + show: true, + }; + default: + return null; + } + }, [BucketStatus, DeleteAt]); return ( @@ -37,12 +57,10 @@ export const BucketNameColumn = memo(function BucketNameC {item.BucketName} - {(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 8b1ffdcb..8d178490 100644 --- a/apps/dcellar-web-ui/src/modules/bucket/components/BucketOperations.tsx +++ b/apps/dcellar-web-ui/src/modules/bucket/components/BucketOperations.tsx @@ -18,6 +18,7 @@ import { EditBucketTagsOperation } from './EditBucketTagsOperation'; import { PaymentAccountOperation } from './PaymentAccountOperation'; import { UpdateBucketTagsOperation } from './UpdateBucketTagsOperation'; import { useRouter } from 'next/router'; +import { MigrateBucketOperation } from './MigrateBucketOperation'; interface BucketOperationsProps { level?: 0 | 1; @@ -41,6 +42,7 @@ export const BucketOperations = memo(function BucketOpera 'tags', 'edit_tags', 'update_tags', + 'migrate', ].includes(operation); const isModal = ['delete'].includes(operation); const _operation = useModalValues(operation); @@ -78,6 +80,8 @@ export const BucketOperations = memo(function BucketOpera return ; case 'update_tags': return ; + case 'migrate': + return ; default: return null; } diff --git a/apps/dcellar-web-ui/src/modules/bucket/components/DiscontinueNotice.tsx b/apps/dcellar-web-ui/src/modules/bucket/components/BucketStatusNotice.tsx similarity index 63% rename from apps/dcellar-web-ui/src/modules/bucket/components/DiscontinueNotice.tsx rename to apps/dcellar-web-ui/src/modules/bucket/components/BucketStatusNotice.tsx index bade8963..84905855 100644 --- a/apps/dcellar-web-ui/src/modules/bucket/components/DiscontinueNotice.tsx +++ b/apps/dcellar-web-ui/src/modules/bucket/components/BucketStatusNotice.tsx @@ -2,26 +2,36 @@ import { IconFont } from '@/components/IconFont'; import { DCLink } from '@/components/common/DCLink'; import { Box, Divider, Flex, Menu, MenuButton, MenuList, Text } from '@node-real/uikit'; -export const DiscontinueNotice = ({ - content, - learnMore, +type InvalidStatusReason = { + title: string; + desc: string; + link?: string; + icon: string; + show: boolean; +}; +export const BucketStatusNotice = ({ flowRateLimit = false, - discontinue = true, + bucketStatusReason, }: { - content: string; - learnMore: string; flowRateLimit?: boolean; - discontinue?: boolean; + bucketStatusReason: { + title: string; + desc: string; + link?: string; + icon: string; + show: boolean; + } | null; }) => { - const account = discontinue && flowRateLimit ? 2 : 0; - const discontinueReasons = [ + const account = bucketStatusReason && flowRateLimit ? 2 : 0; + const discontinueReasons: InvalidStatusReason[] = [ { + icon: 'colored-error2', title: 'Flow rate exceeds limit', 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 }, + bucketStatusReason || { title: '', desc: '', icon: '', show: false }, ].filter((i) => i.show); return ( @@ -35,7 +45,14 @@ export const DiscontinueNotice = ({ alignItems={'center'} fontWeight={600} > - + {!!account && <>{account}} @@ -48,9 +65,11 @@ export const DiscontinueNotice = ({ {desc} - e.stopPropagation()}> - Learn More - + {link && ( + e.stopPropagation()}> + Learn More + + )} {index !== discontinueReasons.length - 1 && } diff --git a/apps/dcellar-web-ui/src/modules/bucket/components/DetailBucketOperation.tsx b/apps/dcellar-web-ui/src/modules/bucket/components/DetailBucketOperation.tsx index b19ec3ba..63cf8687 100644 --- a/apps/dcellar-web-ui/src/modules/bucket/components/DetailBucketOperation.tsx +++ b/apps/dcellar-web-ui/src/modules/bucket/components/DetailBucketOperation.tsx @@ -12,6 +12,7 @@ import { setupBucketQuota, TBucket, setupBucketActivity, + BucketOperationsType, } from '@/store/slices/bucket'; import { selectBucketSp } from '@/store/slices/sp'; import { convertObjectKey } from '@/utils/common'; @@ -35,12 +36,14 @@ import { Tabs, Text, Tooltip, + toast, } from '@node-real/uikit'; import dayjs from 'dayjs'; import { memo, PropsWithChildren, useEffect, useState } from 'react'; import { useMount, useUnmount } from 'ahooks'; import { DEFAULT_TAG } from '@/components/common/ManageTags'; import { Activities } from '@/components/Activities'; +import { BucketStatus } from '@bnb-chain/greenfield-js-sdk'; export const Label = ({ children }: PropsWithChildren) => ( @@ -141,15 +144,6 @@ export const DetailBucketOperation = memo(function D dispatch(setBucketEditQuota([selectedBucketInfo.BucketName, 'drawer'])); }; - const onManagePaymentAccount = () => { - dispatch( - setBucketOperation({ - level: 1, - operation: [selectedBucketInfo.BucketName, 'payment_account'], - }), - ); - }; - const getContent = () => { const CreateAt = getMillisecond(selectedBucketInfo.CreateAt); const spName = primarySp.moniker; @@ -157,7 +151,6 @@ export const DetailBucketOperation = memo(function D const infos = [ { canCopy: false, - edit: false, label: 'Date created', value: formatFullTime(CreateAt), display: formatFullTime(CreateAt), @@ -166,7 +159,13 @@ export const DetailBucketOperation = memo(function D { canCopy: true, label: 'Primary SP address', + edit: 'migrate', + editDisabled: [ + BucketStatus.BUCKET_STATUS_MIGRATING, + BucketStatus.BUCKET_STATUS_DISCONTINUED, + ].includes(selectedBucketInfo.BucketStatus), name: spName, + operation: 'payment_account', value: primarySp.operatorAddress || '--', display: primarySp.operatorAddress ? trimAddress(primarySp.operatorAddress) : '--', copyGaClickName: 'dc.bucket.b_detail_pop.copy_spadd.click', @@ -175,7 +174,11 @@ export const DetailBucketOperation = memo(function D }, { canCopy: true, - edit: true, + edit: 'payment_account', + editDisabled: [ + BucketStatus.BUCKET_STATUS_MIGRATING, + BucketStatus.BUCKET_STATUS_DISCONTINUED, + ].includes(selectedBucketInfo.BucketStatus), label: 'Payment address', name: payAccountName, value: selectedBucketInfo.PaymentAddress, @@ -186,7 +189,6 @@ export const DetailBucketOperation = memo(function D }, { canCopy: true, - edit: false, label: 'Bucket ID', value: formatId(Number(selectedBucketInfo.Id)), display: formatAddress(formatId(Number(selectedBucketInfo.Id))), @@ -196,7 +198,6 @@ export const DetailBucketOperation = memo(function D }, { canCopy: true, - edit: false, label: 'Create transaction hash', value: selectedBucketInfo.CreateTxHash, display: formatAddress(selectedBucketInfo.CreateTxHash), @@ -233,9 +234,11 @@ export const DetailBucketOperation = memo(function D + !item.editDisabled && onEditClick(item.edit as BucketOperationsType) + } w={16} h={16} > @@ -377,6 +380,30 @@ export const DetailBucketOperation = memo(function D ); }; + const onEditClick = (operation?: BucketOperationsType) => { + switch (operation) { + case 'payment_account': + dispatch( + setBucketOperation({ + level: 1, + operation: [selectedBucketInfo.BucketName, 'payment_account'], + }), + ); + break; + case 'migrate': + if (selectedBucketInfo.BucketStatus === BucketStatus.BUCKET_STATUS_MIGRATING) { + toast.error({ description: 'The bucket is migrating, please wait.' }); + } else { + dispatch( + setBucketOperation({ + level: 1, + operation: [selectedBucketInfo.BucketName, 'migrate'], + }), + ); + } + break; + } + }; useEffect(() => { dispatch(setupBucketQuota(selectedBucketInfo.BucketName)); }, [selectedBucketInfo.BucketName, dispatch]); diff --git a/apps/dcellar-web-ui/src/modules/bucket/components/MigrateBucketFees.tsx b/apps/dcellar-web-ui/src/modules/bucket/components/MigrateBucketFees.tsx new file mode 100644 index 00000000..ec417abf --- /dev/null +++ b/apps/dcellar-web-ui/src/modules/bucket/components/MigrateBucketFees.tsx @@ -0,0 +1,50 @@ +import { BankBalance } from '@/components/Fee/BankBalance'; +import { FullBalance } from '@/components/Fee/FullBalance'; +import { GasFee } from '@/components/Fee/GasFee'; +import { SettlementFee } from '@/components/Fee/SettlementFee'; +import { TotalFeeBox } from '@/components/Fee/TotalFeeBox'; +import { LearnMoreTips } from '@/components/common/Tips'; +import { CRYPTOCURRENCY_DISPLAY_PRECISION } from '@/modules/wallet/constants'; +import { useAppSelector } from '@/store'; +import { selectBnbUsdtExchangeRate } from '@/store/slices/global'; +import { BN } from '@/utils/math'; +import { useDisclosure } from '@node-real/uikit'; + +export type MigrateBucketFeesProps = { + gasFee: number; + settlementFee: string; + paymentAddress: string; +}; + +const TipsLink = + 'https://docs.nodereal.io/docs/dcellar-faq#question-how-much-to-pay-for-changing-payment-account'; +const Tips = ; + +export const MigrateBucketFees = ({ + gasFee, + settlementFee, + paymentAddress, +}: MigrateBucketFeesProps) => { + const { isOpen, onToggle } = useDisclosure(); + const ownerAccount = useAppSelector((root) => root.persist.loginAccount); + const bankBalance = useAppSelector((root) => root.accounts.bankOrWalletBalance); + const exchangeRate = useAppSelector(selectBnbUsdtExchangeRate); + + const amount = BN(gasFee).plus(settlementFee).dp(CRYPTOCURRENCY_DISPLAY_PRECISION).toString(); + + return ( + + + + + + + ); +}; diff --git a/apps/dcellar-web-ui/src/modules/bucket/components/MigrateBucketOperation.tsx b/apps/dcellar-web-ui/src/modules/bucket/components/MigrateBucketOperation.tsx new file mode 100644 index 00000000..fff5440e --- /dev/null +++ b/apps/dcellar-web-ui/src/modules/bucket/components/MigrateBucketOperation.tsx @@ -0,0 +1,225 @@ +import { Animates } from '@/components/AnimatePng'; +import { InsufficientBalances } from '@/components/Fee/InsufficientBalances'; +import { IconFont } from '@/components/IconFont'; +import { DCButton } from '@/components/common/DCButton'; +import { useOffChainAuth } from '@/context/off-chain-auth/useOffChainAuth'; +import { TBucket, setBucketEditQuota, setupBucketList } from '@/store/slices/bucket'; +import { migrateBucket, pollingGetBucket } from '@/facade/bucket'; +import { E_OFF_CHAIN_AUTH } from '@/facade/error'; +import { BUTTON_GOT_IT, UNKNOWN_ERROR, WALLET_CONFIRM } from '@/modules/object/constant'; +import { useAppDispatch, useAppSelector } from '@/store'; +import { formatQuota } from '@/utils/string'; +import { + Divider, + Flex, + FormControl, + FormLabel, + QDrawerBody, + QDrawerFooter, + QDrawerHeader, + Text, + toast, +} from '@node-real/uikit'; +import { memo, useCallback, useMemo, useRef, useState } from 'react'; +import { useAccount } from 'wagmi'; +import { selectGnfdGasFeesConfig, setSignatureAction } from '@/store/slices/global'; +import { Field, Label, Value } from './style'; +import { SPSelector } from './SPSelector'; +import { SpEntity } from '@/store/slices/sp'; +import { selectBucketSp } from '@/store/slices/sp'; +import { + AuthType, + BucketStatus, + MigrateBucketApprovalRequest, + MsgMigrateBucketTypeUrl, +} from '@bnb-chain/greenfield-js-sdk'; +import { getSpOffChainData } from '@/store/slices/persist'; +import { MigrateBucketFees } from './MigrateBucketFees'; +import { formatBytes } from '@/utils/formatter'; +import { useSettlementFee } from '@/hooks/useSettlementFee'; +import { BN } from '@/utils/math'; + +export const MigrateBucketOperation = memo(function MigrateBucketOperation({ + bucket, + onClose, +}: { + bucket: TBucket; + onClose: () => void; +}) { + const dispatch = useAppDispatch(); + const [insufficientBalanceAccounts, setInsufficientBalanceAccounts] = useState([]); + + /* @ts-expect-error StorageSize exist */ + const bucketStorageSize = bucket?.StorageSize; + const primarySp = useAppSelector(selectBucketSp(bucket))!; + const selectedSpRef = useRef(primarySp); + + const bucketQuotaRecords = useAppSelector((root) => root.bucket.bucketQuotaRecords); + const bucketQuota = bucketQuotaRecords[bucket.BucketName]; + const formattedQuota = formatQuota(bucketQuota); + + const loginAccount = useAppSelector((root) => root.persist.loginAccount); + + const gnfdGasFeesConfig = useAppSelector(selectGnfdGasFeesConfig); + const { gasFee } = gnfdGasFeesConfig?.[MsgMigrateBucketTypeUrl] ?? {}; + const bankBalance = useAppSelector((root) => root.accounts.bankOrWalletBalance); + + const [loading, setLoading] = useState(false); + const { connector } = useAccount(); + const { setOpenAuthModal } = useOffChainAuth(); + const { settlementFee, loading: loading1 } = useSettlementFee(bucket.PaymentAddress); + const accountInfos = useAppSelector((root) => root.accounts.accountInfos); + + const isPayQuota = BN(formattedQuota.remain).gt(bucketStorageSize); + const isPayFee = useMemo(() => { + const insufficientBalanceAccounts = []; + const isPayGasFee = BN(bankBalance).gte(gasFee); + const isPaySettlementFee = + bucket.PaymentAddress.toLowerCase() === loginAccount.toLowerCase() + ? BN(bankBalance).plus(accountInfos[bucket.PaymentAddress].staticBalance).gte(settlementFee) + : BN(accountInfos[bucket.PaymentAddress].staticBalance).gte(settlementFee); + + !isPayGasFee && insufficientBalanceAccounts.push(loginAccount); + !isPaySettlementFee && insufficientBalanceAccounts.push(bucket.PaymentAddress); + setInsufficientBalanceAccounts(Array.from(new Set(insufficientBalanceAccounts))); + + return isPayGasFee && isPaySettlementFee; + }, [accountInfos, bankBalance, bucket.PaymentAddress, gasFee, loginAccount, settlementFee]); + + const valid = !loading && isPayFee && isPayQuota; + bucket; + + const errorHandler = (error: string) => { + switch (error) { + case E_OFF_CHAIN_AUTH: + setOpenAuthModal(); + return; + default: + dispatch( + setSignatureAction({ + title: 'Update Failed', + icon: 'status-failed', + desc: 'Sorry, there’s something wrong when signing with the wallet.', + buttonText: BUTTON_GOT_IT, + errorText: 'Error message: ' + error, + }), + ); + } + }; + + const onChangeConfirm = async () => { + if (loading) return; + setLoading(true); + dispatch( + setSignatureAction({ + icon: Animates.object, + title: 'Changing Primary Storage Provider', + desc: WALLET_CONFIRM, + }), + ); + const { seedString } = await dispatch( + getSpOffChainData(loginAccount, primarySp.operatorAddress), + ); + const payload: MigrateBucketApprovalRequest = { + operator: loginAccount, + bucketName: bucket.BucketName, + endpoint: selectedSpRef.current.endpoint, + dstPrimarySpId: selectedSpRef.current.id, + }; + const authType: AuthType = { + type: 'EDDSA', + address: loginAccount, + domain: window.location.origin, + seed: seedString, + }; + const [txRes, txError] = await migrateBucket(payload, authType, connector!); + setLoading(false); + if (!txRes || txRes.code !== 0) return errorHandler(txError || UNKNOWN_ERROR); + dispatch(setSignatureAction({})); + toast.success({ description: 'Primary Storage Provider updated!' }); + onClose(); + dispatch(setupBucketList(loginAccount)); + }; + + const onSpChange = useCallback((sp: SpEntity) => { + selectedSpRef.current = sp; + }, []); + + const onManageQuota = () => { + dispatch(setBucketEditQuota([bucket.BucketName, 'drawer'])); + }; + + return ( + <> + + + + Change Primary Storage Provider + + + Migrate your storage to another primary storage provider. This operation consumes a quota + of the same size as the bucket size. + + + + + Bucket Info + + + + + {bucket?.BucketName} + + + + + {formatBytes(bucketStorageSize)} + + + + + + {formattedQuota.totalText}  + + ({formattedQuota.remainText} remains) + + + + {!isPayQuota && ( + + + No enough download quota to change primary storage provider. + + + manage quota + + + )} + + + + + Primary Storage Provider + + + + + + + + + Confirm + + + + ); +}); 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 819ffef1..b75aa154 100644 --- a/apps/dcellar-web-ui/src/modules/bucket/components/PaymentAccountOperation.tsx +++ b/apps/dcellar-web-ui/src/modules/bucket/components/PaymentAccountOperation.tsx @@ -1,7 +1,7 @@ import { GREENFIELD_CHAIN_EXPLORER_URL } from '@/base/env'; import { Animates } from '@/components/AnimatePng'; import { BalanceOn } from '@/components/Fee/BalanceOn'; -import { InsufficientBalance } from '@/components/Fee/InsufficientBalance'; +import { InsufficientBalances } from '@/components/Fee/InsufficientBalances'; import { IconFont } from '@/components/IconFont'; import { CopyText } from '@/components/common/CopyText'; import { DCButton } from '@/components/common/DCButton'; @@ -42,6 +42,7 @@ import { PaymentAccountSelector } from '../components/PaymentAccountSelector'; import { ChangePaymentTotalFee } from './ChangePaymentTotalFees'; import { setSignatureAction } from '@/store/slices/global'; import { useAccountType } from '@/hooks/useAccountType'; +import { Field, Label, Value } from './style'; export const PaymentAccountOperation = memo(function PaymentAccountOperation({ bucket, @@ -90,9 +91,9 @@ export const PaymentAccountOperation = memo(function PaymentAccountOperation({ fromSponsor, }); - const InsufficientAccounts = []; - !loadingFee && !validFrom && InsufficientAccounts.push({ address: bucket.PaymentAddress }); - !loadingFee && !validTo && InsufficientAccounts.push({ address: newPaymentAccount.address }); + const insufficientBalanceAccounts = []; + !loadingFee && !validFrom && insufficientBalanceAccounts.push(bucket.PaymentAddress); + !loadingFee && !validTo && insufficientBalanceAccounts.push(newPaymentAccount.address); const valid = !loading && !loadingFee && @@ -250,7 +251,7 @@ export const PaymentAccountOperation = memo(function PaymentAccountOperation({ from={{ address: bucket.PaymentAddress, amount: fromSettlementFee }} to={{ address: newPaymentAccount.address, amount: toSettlementFee }} /> - + Confirm @@ -258,28 +259,3 @@ export const PaymentAccountOperation = memo(function PaymentAccountOperation({ ); }); - -const Field = styled(Flex)` - align-items: center; - justify-content: space-between; - margin: 8px 0; - padding: 2px 0; -`; - -const Label = styled.div` - font-weight: 500; - line-height: normal; - color: #76808f; - flex-shrink: 0; - width: 178px; -`; - -const Value = styled(Flex)` - font-weight: 500; - line-height: normal; - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - align-items: center; -`; 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 649e33b0..1f35fb5a 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 @@ -15,9 +15,10 @@ import { isEmpty } from 'lodash-es'; interface SPSelectorProps { onChange: (value: SpEntity) => void; + selectedSp?: string; } -export const SPSelector = memo(function SPSelector({ onChange }) { +export const SPSelector = memo(function SPSelector({ onChange, selectedSp }) { const dispatch = useAppDispatch(); const loginAccount = useAppSelector((root) => root.persist.loginAccount); const unAvailableSps = useAppSelector((root) => root.persist.unAvailableSps); @@ -90,7 +91,7 @@ export const SPSelector = memo(function SPSelector({ onChange } useMount(() => { if (!len) return; setTotal(allSpList.length); - setSP(spRecords[specifiedSp]); + setSP(spRecords[selectedSp || specifiedSp]); }); useEffect(() => { diff --git a/apps/dcellar-web-ui/src/modules/bucket/components/style.tsx b/apps/dcellar-web-ui/src/modules/bucket/components/style.tsx new file mode 100644 index 00000000..942edc05 --- /dev/null +++ b/apps/dcellar-web-ui/src/modules/bucket/components/style.tsx @@ -0,0 +1,40 @@ +import styled from '@emotion/styled'; +import { Box, BoxProps, Flex, Text } from '@node-real/uikit'; + +export const Field = styled(Flex)` + align-items: center; + justify-content: space-between; + margin: 8px 0; + padding: 2px 0; +`; + +// export const Label = styled.div` +// font-weight: 500; +// line-height: normal; +// color: #76808f; +// flex-shrink: 0; +// width: 178px; +// `; +export const Label = ({ children, ...restProps }: BoxProps) => { + return ( + + {children} + + ); +}; + +export const Value = styled(Flex)` + font-weight: 500; + line-height: normal; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + align-items: center; +`; + +export const ErrorText = styled(Text)` + color: readable.danger; + font-size: 12px; + font-weight: 500; +`; diff --git a/apps/dcellar-web-ui/src/modules/bucket/index.tsx b/apps/dcellar-web-ui/src/modules/bucket/index.tsx index c8fd3f0b..59d4dd99 100644 --- a/apps/dcellar-web-ui/src/modules/bucket/index.tsx +++ b/apps/dcellar-web-ui/src/modules/bucket/index.tsx @@ -1,7 +1,7 @@ import { BucketList } from '@/modules/bucket/components/BucketList'; import { CreateBucket } from '@/modules/bucket/components/CreateBucket'; import { useAppDispatch, useAppSelector } from '@/store'; -import { selectHasDiscontinue, setupBucketList } from '@/store/slices/bucket'; +import { selectHasDiscontinueBucket, setupBucketList } from '@/store/slices/bucket'; import { useAsyncEffect, useDocumentVisibility, useUpdateEffect } from 'ahooks'; import { PageTitle } from '@/components/layout/PageTitle'; import { DiscontinueBanner } from '@/components/common/DiscontinueBanner'; @@ -11,7 +11,7 @@ import { GAContextProvider } from '@/context/GAContext'; export const BucketPage = () => { const dispatch = useAppDispatch(); const loginAccount = useAppSelector((root) => root.persist.loginAccount); - const discontinue = useAppSelector(selectHasDiscontinue(loginAccount)); + const hasDiscontinueBucket = useAppSelector(selectHasDiscontinueBucket(loginAccount)); const documentVisibility = useDocumentVisibility(); @@ -32,7 +32,7 @@ export const BucketPage = () => { - {discontinue && ( + {hasDiscontinueBucket && ( (function NewObject({ const dispatch = useAppDispatch(); const { handleFolderTree } = useHandleFolderTree(); const isBucketDiscontinue = useAppSelector((root) => root.bucket.isBucketDiscontinue); + const isBucketMigrating = useAppSelector((root) => root.bucket.isBucketMigrating); const isBucketOwner = useAppSelector((root) => root.bucket.isBucketOwner); const pathSegments = useAppSelector((root) => root.object.pathSegments); const objectCommonPrefix = useAppSelector((root) => root.object.objectCommonPrefix); @@ -108,11 +109,13 @@ export const CreateObject = memo(function NewObject({ maxFolderDepth || isBucketDiscontinue || isFlowRateLimit || + isBucketMigrating || loading || accountDetail.clientFrozen; const uploadDisabled = isBucketDiscontinue || isFlowRateLimit || + isBucketMigrating || invalidPath || pathSegments.length > MAX_FOLDER_LEVEL || loading || @@ -194,14 +197,16 @@ export const CreateObject = memo(function NewObject({ isBucketDiscontinue ? 'Bucket in the discontinue status cannot upload objects.' : 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.` + ? "The bucket's flow rate exceeds the payment account limit. Contact the account owner or switch accounts to increase it." + : isBucketMigrating + ? 'Bucket in the migrating 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.` } >
diff --git a/apps/dcellar-web-ui/src/modules/object/components/MigratingBucketNoticeBanner.tsx b/apps/dcellar-web-ui/src/modules/object/components/MigratingBucketNoticeBanner.tsx new file mode 100644 index 00000000..f1b17d84 --- /dev/null +++ b/apps/dcellar-web-ui/src/modules/object/components/MigratingBucketNoticeBanner.tsx @@ -0,0 +1,80 @@ +import { Animates } from '@/components/AnimatePng'; +import { IconFont } from '@/components/IconFont'; +import { useAppDispatch, useAppSelector } from '@/store'; +import { setSignatureAction } from '@/store/slices/global'; +import { Flex, Text, toast } from '@node-real/uikit'; +import { BUTTON_GOT_IT, WALLET_CONFIRM } from '../constant'; +import { cancelMigrateBucket, headBucket } from '@/facade/bucket'; +import { MsgCancelMigrateBucket } from '@bnb-chain/greenfield-cosmos-types/greenfield/storage/tx'; +import { useAccount } from 'wagmi'; +import { BucketStatus } from '@bnb-chain/greenfield-js-sdk'; +import { setupBucket } from '@/store/slices/bucket'; + +export const MigratingBucketNoticeBanner = ({ bucketName }: { bucketName: string }) => { + const dispatch = useAppDispatch(); + const { connector } = useAccount(); + const loginAccount = useAppSelector((root) => root.persist.loginAccount); + const onCancelMigration = async () => { + dispatch( + setSignatureAction({ + icon: Animates.object, + title: 'Cancelling Migrate Bucket', + desc: WALLET_CONFIRM, + }), + ); + const params: MsgCancelMigrateBucket = { + operator: loginAccount, + bucketName, + }; + const bucketInfo = await headBucket(bucketName); + if (bucketInfo?.bucketStatus === BucketStatus.BUCKET_STATUS_CREATED) { + await dispatch(setupBucket(bucketName)); + dispatch(setSignatureAction({})); + toast.success({ description: 'This bucket has been migrated!' }); + return; + } + + const [txRes, error] = await cancelMigrateBucket(params, connector!); + + if (!txRes || txRes.code !== 0) { + return dispatch( + setSignatureAction({ + title: 'Migrate Failed', + icon: 'status-failed', + desc: 'Sorry, there’s something wrong when signing with the wallet.', + buttonText: BUTTON_GOT_IT, + errorText: 'Error message: ' + error, + }), + ); + } + await dispatch(setupBucket(bucketName)); + dispatch(setSignatureAction({})); + toast.success({ description: 'Cancel Migrate Bucket successfully!' }); + }; + + return ( + + + + This bucket, in the process of data migration to another provider, supports only downloads, + quota modifications, deletions, and sharing. + + + Cancel Migration + + + ); +}; diff --git a/apps/dcellar-web-ui/src/modules/object/components/ObjectBreadcrumb.tsx b/apps/dcellar-web-ui/src/modules/object/components/ObjectBreadcrumb.tsx index 1742aaa2..0d95fdf8 100644 --- a/apps/dcellar-web-ui/src/modules/object/components/ObjectBreadcrumb.tsx +++ b/apps/dcellar-web-ui/src/modules/object/components/ObjectBreadcrumb.tsx @@ -16,6 +16,7 @@ export const ObjectBreadcrumb = memo(function ObjectBread }) { const dispatch = useAppDispatch(); const isBucketDiscontinue = useAppSelector((root) => root.bucket.isBucketDiscontinue); + const isBucketMigrating = useAppSelector((root) => root.bucket.isBucketMigrating); const currentBucketName = useAppSelector((root) => root.object.currentBucketName); const pathSegments = useAppSelector((root) => root.object.pathSegments); @@ -33,7 +34,12 @@ export const ObjectBreadcrumb = memo(function ObjectBread flex={1} minW={0} > - {isBucketDiscontinue && first && } + {first && + (isBucketDiscontinue ? ( + + ) : isBucketMigrating ? ( + + ) : null)} {text} @@ -43,7 +49,12 @@ export const ObjectBreadcrumb = memo(function ObjectBread - {isBucketDiscontinue && first && } + {first && + (isBucketDiscontinue ? ( + + ) : isBucketMigrating ? ( + + ) : null)} (function ObjectList({ shareMode const objectTypeFilter = useAppSelector((root) => root.object.objectTypeFilter); const objectListTruncated = useAppSelector((root) => root.object.objectListTruncated); const isBucketDiscontinue = useAppSelector((root) => root.bucket.isBucketDiscontinue); + const isBucketMigrating = useAppSelector((root) => root.bucket.isBucketMigrating); const isBucketOwner = useAppSelector((root) => root.bucket.isBucketOwner); const bucketRecords = useAppSelector((root) => root.bucket.bucketRecords); const primarySpRecords = useAppSelector((root) => root.sp.primarySpRecords); @@ -204,6 +205,7 @@ export const ObjectList = memo(function ObjectList({ shareMode const title = (() => { if (filtered || shareMode) return 'No Results'; if (isBucketDiscontinue) return 'Discontinue Notice'; + if (isBucketMigrating) return 'Migrating Notice'; if (!currentPathExist && isBucketOwner) return 'No Objects Under This Path'; return 'Upload Objects and Start Your Work Now'; })(); @@ -212,6 +214,8 @@ export const ObjectList = memo(function ObjectList({ shareMode if (filtered || shareMode) return 'No results found. Please try different conditions.'; if (isBucketDiscontinue) return 'This bucket were marked as discontinued and will be deleted by SP soon. '; + if (isBucketMigrating) + return 'This bucket were marked as migrating and will be migrated by SP soon.'; if (!currentPathExist && isBucketOwner) return ( @@ -231,6 +235,7 @@ export const ObjectList = memo(function ObjectList({ shareMode ); }, [ isBucketDiscontinue, + isBucketMigrating, isBucketOwner, empty, filtered, diff --git a/apps/dcellar-web-ui/src/modules/object/index.tsx b/apps/dcellar-web-ui/src/modules/object/index.tsx index 8e05deae..1600c63c 100644 --- a/apps/dcellar-web-ui/src/modules/object/index.tsx +++ b/apps/dcellar-web-ui/src/modules/object/index.tsx @@ -8,7 +8,6 @@ import { useEffect } from 'react'; import { InsufficientBalance } from './components/InsufficientBalance'; import { IconFont } from '@/components/IconFont'; -import { DiscontinueBanner } from '@/components/common/DiscontinueBanner'; import { CreateObject } from '@/modules/object/components/CreateObject'; import { ObjectBreadcrumb } from '@/modules/object/components/ObjectBreadcrumb'; import { ObjectFilterItems } from '@/modules/object/components/ObjectFilterItems'; @@ -30,6 +29,9 @@ import { setPathSegments } from '@/store/slices/object'; import { setPrimarySpInfo, SpEntity } from '@/store/slices/sp'; import { GAContextProvider } from '@/context/GAContext'; import { ObjectOperations } from '@/modules/object/components/ObjectOperations'; +import { BucketStatus as BucketStatusEnum } from '@bnb-chain/greenfield-js-sdk'; +import { DiscontinueBanner } from '@/components/common/DiscontinueBanner'; +import { MigratingBucketNoticeBanner } from './components/MigratingBucketNoticeBanner'; export const ObjectsPage = () => { const dispatch = useAppDispatch(); @@ -39,6 +41,7 @@ export const ObjectsPage = () => { const loginAccount = useAppSelector((root) => root.persist.loginAccount); const objectSelectedKeys = useAppSelector((root) => root.object.objectSelectedKeys); const isBucketDiscontinue = useAppSelector((root) => root.bucket.isBucketDiscontinue); + const isBucketMigrating = useAppSelector((root) => root.bucket.isBucketMigrating); const allSpList = useAppSelector((root) => root.sp.allSpList); const { path } = router.query; @@ -74,8 +77,9 @@ export const ObjectsPage = () => { if (bucket) { const Owner = bucket.Owner; const payload = { - discontinue: bucket.BucketStatus === 1, - owner: Owner === loginAccount, + isBucketDiscontinue: bucket.BucketStatus === BucketStatusEnum.BUCKET_STATUS_DISCONTINUED, + isBucketMigrating: bucket.BucketStatus === BucketStatusEnum.BUCKET_STATUS_MIGRATING, + isBucketOwner: Owner === loginAccount, }; dispatch(setBucketStatus(payload)); return; @@ -142,7 +146,11 @@ export const ObjectsPage = () => { {isBucketDiscontinue && isBucketOwner && ( )} + {isBucketMigrating && isBucketOwner && ( + + )} + diff --git a/apps/dcellar-web-ui/src/modules/upload/NameItem.tsx b/apps/dcellar-web-ui/src/modules/upload/NameItem.tsx index 5e806725..7e4369dc 100644 --- a/apps/dcellar-web-ui/src/modules/upload/NameItem.tsx +++ b/apps/dcellar-web-ui/src/modules/upload/NameItem.tsx @@ -1,4 +1,4 @@ -import { Box, Flex, Text } from '@node-real/uikit'; +import { Box, Flex } from '@node-real/uikit'; import { useRouter } from 'next/router'; import { EllipsisText } from '@/components/common/EllipsisText'; diff --git a/apps/dcellar-web-ui/src/pages/api/storage/[[...slug]].ts b/apps/dcellar-web-ui/src/pages/api/storage/[[...slug]].ts new file mode 100644 index 00000000..df715169 --- /dev/null +++ b/apps/dcellar-web-ui/src/pages/api/storage/[[...slug]].ts @@ -0,0 +1,18 @@ +import { EXPLORER_API_URL } from '@/base/env'; +import axios from 'axios'; +import { NextApiRequest, NextApiResponse } from 'next'; + +const handler = async (req: NextApiRequest, res: NextApiResponse) => { + const { slug } = req.query; + const slugs = slug as string[]; + const url = `${EXPLORER_API_URL}/greenfield/storage/${slugs.join('/')}?page=1&per_page=1000`; + try { + const { data } = await axios.get(url); + res.json(data); + } catch (e) { + console.error('explorer chart error', e); + res.json({}); + } +}; + +export default handler; diff --git a/apps/dcellar-web-ui/src/store/slices/bucket.ts b/apps/dcellar-web-ui/src/store/slices/bucket.ts index a565d4f5..6a7f66ff 100644 --- a/apps/dcellar-web-ui/src/store/slices/bucket.ts +++ b/apps/dcellar-web-ui/src/store/slices/bucket.ts @@ -18,6 +18,7 @@ import { getSpOffChainData } from '@/store/slices/persist'; import { convertObjectKey } from '@/utils/common'; import { Activity } from './object'; import { numberToHex } from 'viem'; +import { BucketStatus as BucketStatusEnum } from '@bnb-chain/greenfield-js-sdk'; export type BucketOperationsType = | 'detail' @@ -28,6 +29,7 @@ export type BucketOperationsType = | 'payment_account' | 'edit_tags' | 'update_tags' + | 'migrate' | ''; export type BucketProps = BucketMetaWithVGF; @@ -49,6 +51,7 @@ export interface BucketState { // current visit bucket; isBucketDiscontinue: boolean; isBucketOwner: boolean; + isBucketMigrating: boolean; bucketEditQuota: string[]; bucketOperation: Record<0 | 1, [string, BucketOperationsType]>; bucketEditTagsData: ResourceTags_Tag[]; @@ -64,6 +67,7 @@ const initialState: BucketState = { bucketListPage: 0, isBucketDiscontinue: false, isBucketOwner: true, + isBucketMigrating: false, bucketEditQuota: ['', ''], bucketOperation: { 0: ['', ''], 1: ['', ''] }, bucketEditTagsData: [DEFAULT_TAG], @@ -87,10 +91,20 @@ export const bucketSlice = createSlice({ setBucketEditQuota(state, { payload }: PayloadAction) { state.bucketEditQuota = payload; }, - setBucketStatus(state, { payload }: PayloadAction<{ discontinue: boolean; owner: boolean }>) { - const { discontinue, owner } = payload; - state.isBucketDiscontinue = discontinue; - state.isBucketOwner = owner; + setBucketStatus( + state, + { + payload, + }: PayloadAction<{ + isBucketDiscontinue: boolean; + isBucketOwner: boolean; + isBucketMigrating: boolean; + }>, + ) { + const { isBucketDiscontinue, isBucketOwner, isBucketMigrating } = payload; + state.isBucketDiscontinue = isBucketDiscontinue; + state.isBucketOwner = isBucketOwner; + state.isBucketMigrating = isBucketMigrating; }, setBucketListPage(state, { payload }: PayloadAction) { state.bucketListPage = payload; @@ -109,7 +123,9 @@ export const bucketSlice = createSlice({ const newInfo = { ...info, ...bucket }; state.bucketRecords[bucketName] = newInfo; state.isBucketOwner = address === newInfo.Owner; - state.isBucketDiscontinue = newInfo.BucketStatus === 1; + state.isBucketDiscontinue = + newInfo.BucketStatus === BucketStatusEnum.BUCKET_STATUS_DISCONTINUED; + state.isBucketMigrating = newInfo.BucketStatus === BucketStatusEnum.BUCKET_STATUS_MIGRATING; }, setBucketList(state, { payload }: PayloadAction<{ address: string; buckets: BucketProps[] }>) { const { address, buckets } = payload; @@ -184,8 +200,10 @@ export const selectBucketListSpinning = (address: string) => (root: AppState) => return !(address in bucketListRecords) || bucketListLoading; }; -export const selectHasDiscontinue = (address: string) => (root: AppState) => - (root.bucket.bucketListRecords[address] || defaultBucketList).some((i) => i.BucketStatus === 1); +export const selectHasDiscontinueBucket = (address: string) => (root: AppState) => + (root.bucket.bucketListRecords[address] || defaultBucketList).some( + (i) => i.BucketStatus === BucketStatusEnum.BUCKET_STATUS_DISCONTINUED, + ); export const setupBucket = (bucketName: string, address?: string) => async (dispatch: AppDispatch, getState: GetState) => { diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index b5bf5825..373c7230 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -8,7 +8,7 @@ importers: ../../apps/dcellar-web-ui: specifiers: '@bnb-chain/greenfield-cosmos-types': 0.4.0-alpha.32 - '@bnb-chain/greenfield-js-sdk': 2.0.0-alpha.7 + '@bnb-chain/greenfield-js-sdk': 2.1.0-alpha.0 '@commitlint/cli': ^17.4.3 '@commitlint/config-conventional': ^17.4.3 '@emotion/react': ^11.10.5 @@ -75,7 +75,7 @@ importers: wagmi: ~1.4.10 dependencies: '@bnb-chain/greenfield-cosmos-types': 0.4.0-alpha.32 - '@bnb-chain/greenfield-js-sdk': 2.0.0-alpha.7 + '@bnb-chain/greenfield-js-sdk': 2.1.0-alpha.0 '@emotion/react': 11.11.3_mj3jo2baq3jslihcop7oivercy '@emotion/styled': 11.11.0_44o7ug6fvmx5wru7ifqtcwoy2i '@next/bundle-analyzer': 13.5.6 @@ -1497,8 +1497,8 @@ packages: protobufjs: 6.11.4 dev: false - /@bnb-chain/greenfield-js-sdk/2.0.0-alpha.7: - resolution: {integrity: sha512-/AJt4OvX0eH3VU7AOKmMaByhkX6PEBY+nve2LpNM/Mku9IlmF/WCSdaWiImG2EV27Mv/CKgopFKFqmmf/bUEPw==} + /@bnb-chain/greenfield-js-sdk/2.1.0-alpha.0: + resolution: {integrity: sha512-QEXoofh2VAo6oCCSiyirCf41txbHc9TuuA5ETQMImqdlQQTWTSBfQQEmiVLScKYoNMd7boTDQTTIq0z0yK9RPQ==} dependencies: '@bnb-chain/greenfield-cosmos-types': 0.4.0-alpha.32 '@cosmjs/proto-signing': 0.32.2 @@ -1521,12 +1521,15 @@ packages: lodash.set: 4.3.2 lodash.sortby: 4.7.0 long: 5.2.3 + mime-types: 2.1.35 reflect-metadata: 0.1.14 + superagent: 8.1.2 tsyringe: 4.8.0 transitivePeerDependencies: - bufferutil - debug - encoding + - supports-color - utf-8-validate dev: false @@ -4896,6 +4899,10 @@ packages: engines: {node: '>=0.10.0'} dev: true + /asap/2.0.6: + resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} + dev: false + /ast-types-flow/0.0.8: resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} dev: true @@ -5334,6 +5341,10 @@ packages: dot-prop: 5.3.0 dev: true + /component-emitter/1.3.1: + resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==} + dev: false + /compute-scroll-into-view/3.1.0: resolution: {integrity: sha512-rj8l8pD4bJ1nx+dAkMhV1xB5RuZEyVysfxJqB1pRchh1KVvwOv9b7CGB8ZfjTImVv2oF+sYMUkMZq6Na5Ftmbg==} dev: false @@ -5384,6 +5395,10 @@ packages: resolution: {integrity: sha512-mWYvfOLrfEc996hlKcdABeIiPHUPC6DM2QYZdGGOvhOTbA3tjm2eBwqlJpoFdjC89NI4Qt6h0Pu06Mp+1Pj5OQ==} dev: false + /cookiejar/2.1.4: + resolution: {integrity: sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==} + dev: false + /copy-to-clipboard/3.3.3: resolution: {integrity: sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==} dependencies: @@ -5626,6 +5641,13 @@ packages: hasBin: true dev: false + /dezalgo/1.0.4: + resolution: {integrity: sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==} + dependencies: + asap: 2.0.6 + wrappy: 1.0.2 + dev: false + /diff/4.0.2: resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} engines: {node: '>=0.3.1'} @@ -6483,6 +6505,15 @@ packages: mime-types: 2.1.35 dev: false + /formidable/2.1.2: + resolution: {integrity: sha512-CM3GuJ57US06mlpQ47YcunuUZ9jpm8Vx+P2CGt2j7HpgkKZO/DJYQ0Bobim8G6PFQmK5lOqOOdUXboU+h73A4g==} + dependencies: + dezalgo: 1.0.4 + hexoid: 1.0.0 + once: 1.4.0 + qs: 6.11.2 + dev: false + /fs-extra/11.2.0: resolution: {integrity: sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==} engines: {node: '>=14.14'} @@ -6761,6 +6792,11 @@ packages: dependencies: function-bind: 1.1.2 + /hexoid/1.0.0: + resolution: {integrity: sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==} + engines: {node: '>=8'} + dev: false + /hey-listen/1.0.8: resolution: {integrity: sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==} dev: false @@ -7707,6 +7743,11 @@ packages: engines: {node: '>= 8'} dev: true + /methods/1.1.2: + resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} + engines: {node: '>= 0.6'} + dev: false + /micro-ftch/0.3.1: resolution: {integrity: sha512-/0LLxhzP0tfiR5hcQebtudP56gUurs2CLkGarnCiB/OqEyUFQ6U3paQi/tgLv0hBJYt2rnr9MNpxz4fiiugstg==} dev: false @@ -7730,6 +7771,12 @@ packages: mime-db: 1.52.0 dev: false + /mime/2.6.0: + resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==} + engines: {node: '>=4.0.0'} + hasBin: true + dev: false + /mime/3.0.0: resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} engines: {node: '>=10.0.0'} @@ -9802,6 +9849,25 @@ packages: resolution: {integrity: sha512-EQepAV+wMsIaGVGX1RECzgrcqRRU/0sYOHkeLsZ3fzHaHXZy4DaOOX0vOlGQdlsjkh3mFHAIlVimpwAs4dslyQ==} dev: false + /superagent/8.1.2: + resolution: {integrity: sha512-6WTxW1EB6yCxV5VFOIPQruWGHqc3yI7hEmZK6h+pyk69Lk/Ut7rLUY6W/ONF2MjBuGjvmMiIpsrVJ2vjrHlslA==} + engines: {node: '>=6.4.0 <13 || >=14'} + deprecated: Please upgrade to v9.0.0+ as we have fixed a public vulnerability with formidable dependency. Note that v9.0.0+ requires Node.js v14.18.0+. See https://github.com/ladjs/superagent/pull/1800 for insight. This project is supported and maintained by the team at Forward Email @ https://forwardemail.net + dependencies: + component-emitter: 1.3.1 + cookiejar: 2.1.4 + debug: 4.3.4 + fast-safe-stringify: 2.1.1 + form-data: 4.0.0 + formidable: 2.1.2 + methods: 1.1.2 + mime: 2.6.0 + qs: 6.11.2 + semver: 7.6.0 + transitivePeerDependencies: + - supports-color + dev: false + /superstruct/0.14.2: resolution: {integrity: sha512-nPewA6m9mR3d6k7WkZ8N8zpTWfenFH3q9pA2PkuiZxINr9DKB2+40wEQf0ixn8VaGuJ78AB6iWOtStI+/4FKZQ==} dev: false