Skip to content

Commit

Permalink
feat(dcellar-web-ui): support sponsor payment account (#386)
Browse files Browse the repository at this point in the history
* feat(dcellar-web-ui): support sponsor payment account

* feat(dcellar-web-ui): update flow rate limit tooltip

---------

Co-authored-by: devinxl <[email protected]>
  • Loading branch information
aiden-cao and devinxl authored May 23, 2024
1 parent 3e29c6f commit 64a220f
Show file tree
Hide file tree
Showing 25 changed files with 465 additions and 142 deletions.
22 changes: 20 additions & 2 deletions apps/dcellar-web-ui/src/components/common/DCSelect/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -52,6 +61,7 @@ export function DCSelect(props: DCSelectProps) {
onSearch,
children,
renderOption,
allowInput = false,
...restProps
} = props;

Expand All @@ -66,6 +76,14 @@ export function DCSelect(props: DCSelectProps) {
}
};

const onBlur = (e: FocusEvent<HTMLInputElement>) => {
const value = e.target.value?.trim();
if (isAddress(value) && allowInput) {
onSelectItem({ value, label: value });
}
onClose();
};

const onSelectItem = (item: MenuOption) => {
onChange?.(item.value);
};
Expand Down Expand Up @@ -130,7 +148,7 @@ export function DCSelect(props: DCSelectProps) {
placeholder={text}
onChangeKeyword={onChangeKeyword}
onEnter={onEnter}
onBlur={onClose}
onBlur={onBlur as any}
/>
</DCMenu>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -62,12 +64,9 @@ export const BucketQuotaManager = memo<ManageQuotaProps>(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);
Expand All @@ -85,6 +84,7 @@ export const BucketQuotaManager = memo<ManageQuotaProps>(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];
Expand All @@ -93,33 +93,32 @@ export const BucketQuotaManager = memo<ManageQuotaProps>(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)
.minus(preStoreRate)
.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}
<Text mx={2}>|</Text>
{!isSponsor && (
<>
{oa ? OWNER_ACCOUNT_NAME : pa!.name}
<Text mx={2}>|</Text>
</>
)}
<Link
target="_blank"
color="#1184EE"
Expand All @@ -137,7 +136,7 @@ export const BucketQuotaManager = memo<ManageQuotaProps>(function ManageQuota({
<CopyText value={link} />
</>
);
}, [ownerAccount, paymentAccountList, bucket]);
}, [pa, oa, isSponsor, bucket]);

const errorHandler = (error: string) => {
switch (error) {
Expand Down Expand Up @@ -317,6 +316,7 @@ interface BucketQuotaDrawerProps {}

export const BucketQuotaDrawer = memo<BucketQuotaDrawerProps>(function BucketQuotaDrawer() {
const dispatch = useAppDispatch();
const { pathname } = useRouter();
const bucketEditQuota = useAppSelector((root) => root.bucket.bucketEditQuota);
const [bucketName] = bucketEditQuota;

Expand All @@ -326,6 +326,10 @@ export const BucketQuotaDrawer = memo<BucketQuotaDrawerProps>(function BucketQuo

useUnmount(onClose);

useUpdateEffect(() => {
onClose();
}, [pathname]);

return (
<DCDrawer isOpen={!!bucketName} onClose={onClose}>
<QDrawerHeader>Manage Quota</QDrawerHeader>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -250,7 +255,7 @@ export const GlobalObjectUploadManager = memo<GlobalTasksProps>(
})
.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',
Expand All @@ -261,6 +266,21 @@ export const GlobalObjectUploadManager = memo<GlobalTasksProps>(
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({
Expand Down
11 changes: 11 additions & 0 deletions apps/dcellar-web-ui/src/facade/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -20,6 +22,15 @@ export const resolve = <R>(r: R): [R, null] => [r, null];

export const resolveSpRequest = <R>(r: SpResponse<R>) => {
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];
Expand Down
31 changes: 31 additions & 0 deletions apps/dcellar-web-ui/src/facade/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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];
};

Expand All @@ -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];
};

Expand Down Expand Up @@ -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];
}
Expand Down
26 changes: 26 additions & 0 deletions apps/dcellar-web-ui/src/hooks/useAccountType.ts
Original file line number Diff line number Diff line change
@@ -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,
};
};
Loading

0 comments on commit 64a220f

Please sign in to comment.