Skip to content

Commit

Permalink
feat(dcellar-web-ui): support reactive the frozen account (#398)
Browse files Browse the repository at this point in the history
* feat(dcellar-web-ui): support reactive the frozen account

* chore(dcellar-web-ui): remove redundant codes

* docs(dcellar-web-ui): update changelog
  • Loading branch information
devinxl authored Oct 10, 2024
1 parent 9507eb3 commit 648afa0
Show file tree
Hide file tree
Showing 30 changed files with 492 additions and 117 deletions.
1 change: 1 addition & 0 deletions apps/dcellar-web-ui/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ module.exports = {
rules: {
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-unused-vars': 'off',
'react-hooks/exhaustive-deps': 1,
},
settings: {
react: {
Expand Down
12 changes: 12 additions & 0 deletions apps/dcellar-web-ui/CHANGELOG.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
{
"name": "dcellar-web-ui",
"entries": [
{
"version": "1.8.0",
"tag": "dcellar-web-ui_v1.8.0",
"date": "Thu, 10 Oct 2024 09:34:45 GMT",
"comments": {
"minor": [
{
"comment": "Support reactive the frozen account"
}
]
}
},
{
"version": "1.7.5",
"tag": "dcellar-web-ui_v1.7.5",
Expand Down
11 changes: 9 additions & 2 deletions apps/dcellar-web-ui/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
# Change Log - dcellar-web-ui

This log was last generated on Thu, 19 Sep 2024 08:01:12 GMT and should not be manually modified.
This log was last generated on Thu, 10 Oct 2024 09:34:45 GMT and should not be manually modified.

## 1.8.0
Thu, 10 Oct 2024 09:34:45 GMT

### Minor changes

- Support reactive the frozen account

## 1.7.5
Thu, 19 Sep 2024 08:01:12 GMT

### Patches

- Compatible with metamask eip-712 signature
- Compatiable with metamask eip-712 signature

## 1.7.4
Thu, 19 Sep 2024 07:13:05 GMT
Expand Down
2 changes: 1 addition & 1 deletion apps/dcellar-web-ui/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "dcellar-web-ui",
"version": "1.7.5",
"version": "1.8.0",
"private": false,
"scripts": {
"dev": "node ./scripts/dev.js -p 3200",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { Box, Flex, ModalBody, ModalCloseButton, ModalFooter, Text } from '@node-real/uikit';
import { DCButton } from '../common/DCButton';
import { DCModal } from '../common/DCModal';
import { Step } from '@/components/RenewalNotification/Step';
import { useRouter } from 'next/router';
import { InternalRoutePaths } from '@/constants/paths';

const STEP_DATA = [
{
num: 1,
description: 'Transfer in enough BNB to your Owner Account.',
},
{
num: 2,
description:
'Deposit BNB from your Owner Account to your Payment Account which shares the same address with your Payment Account.',
},
];

export type RenewalGuideModalProps = {
isOpen: boolean;
onClose: () => void;
};
export const RenewalGuideModal = ({ isOpen, onClose }: RenewalGuideModalProps) => {
const router = useRouter();
const onNavigate = (path: string) => {
router.push(path);
};
return (
<DCModal isOpen={isOpen} onClose={onClose} gaShowName="dc.renewal.modal.0.show">
<ModalCloseButton />
<ModalBody textAlign={'center'} mt={0}>
<Text fontSize={'24px'} fontWeight={600} lineHeight="150%" marginBottom={'8px'}>
DCellar Renewal Guide
</Text>
<Text color="#76808F" fontSize={'16px'} fontWeight="400">
Your Owner Account has been frozen due to insufficient funds. The Payment Account
associated with the same address has also had its bucket restricted in service. Follow the
following steps to unfreeze your account and restore your data service.
</Text>
<Flex flexDir={'column'} marginTop={32}>
{STEP_DATA.map((step, index) => {
return (
<>
<Step {...step} />
{index !== STEP_DATA.length - 1 && (
<Box
height={'32px'}
w={'1px'}
borderLeft={'1px dotted #AEB4BC'}
marginLeft={'9px'}
/>
)}
</>
);
})}
</Flex>
</ModalBody>
<ModalFooter flexDirection={'column'} mt={32} gap={16}>
<DCButton
size="md"
gaClickName="dc.wrongnet.gf_modal.swithtogf.click"
width={'100%'}
onClick={() => {
onNavigate(InternalRoutePaths.transfer_in);
}}
>
Transfer In
</DCButton>
</ModalFooter>
</DCModal>
);
};
27 changes: 27 additions & 0 deletions apps/dcellar-web-ui/src/components/RenewalNotification/Step.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Box, Flex, Text } from '@node-real/uikit';

export type StepProps = {
num: number;
description: string;
};
export const Step = ({ num, description }: StepProps) => {
return (
<Flex gap={12} position={'relative'} textAlign={'left'}>
<Flex
width={'18px'}
height={'18px'}
bg={'opacity1'}
borderRadius={'9px'}
justifyContent={'center'}
alignItems={'center'}
flexShrink={0}
>
<Box width={8} height={8} borderRadius={4} bg={'brand.brand6'} />
</Flex>
<Text fontWeight={500} flexShrink={0}>
Step {num}
</Text>
<Text>{description}</Text>
</Flex>
);
};
223 changes: 223 additions & 0 deletions apps/dcellar-web-ui/src/components/RenewalNotification/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
import { GREENFIELD_CHAIN_ID } from '@/base/env';
import { IconFont } from '@/components/IconFont';
import { RenewalGuideModal } from '@/components/RenewalNotification/RenewalGuideModal';
import { InternalRoutePaths } from '@/constants/paths';
import { MIN_AMOUNT } from '@/modules/wallet/constants';
import { useAppDispatch, useAppSelector } from '@/store';
import { EStreamRecordStatus, selectPaymentAccounts } from '@/store/slices/accounts';
import { setCloseRenewalAddresses } from '@/store/slices/session-persist';
import { displayTokenSymbol } from '@/utils/wallet';
import { Box, Button, Flex, useDisclosure } from '@node-real/uikit';
import { fetchBalance } from '@wagmi/core';
import { useAsyncEffect } from 'ahooks';
import BigNumber from 'bignumber.js';
import dayjs from 'dayjs';
import { isEmpty } from 'lodash-es';
import { useRouter } from 'next/router';
import { ReactNode, useMemo, useState } from 'react';

export type RenewalNotificationProps = {
address?: string;
};

export const RenewalNotification = ({ address }: RenewalNotificationProps) => {
const router = useRouter();
const dispatch = useAppDispatch();
const { isOpen, onClose, onOpen } = useDisclosure();
const loginAccount = useAppSelector((root) => root.persist.loginAccount);
const _bankBalance = useAppSelector((root) => root.accounts.bankOrWalletBalance);
const [bankBalance, setBankBalance] = useState<null | string>(null);
const accountInfos = useAppSelector((root) => root.accounts.accountInfos);
const { reserveTime } = useAppSelector((root) => root.global.storeFeeParams);
const paymentAccountList = useAppSelector(selectPaymentAccounts(loginAccount));
const closeRenewalAddresses = useAppSelector((root) => root.sessionPersist.closeRenewalAddresses);

useAsyncEffect(async () => {
if (!loginAccount) return;
const data = await fetchBalance({
address: loginAccount as `0x${string}`,
chainId: GREENFIELD_CHAIN_ID,
});
setBankBalance(data.formatted);
}, [loginAccount, _bankBalance]);

const notifications = useMemo(() => {
const nodes: { type: 'danger' | 'warning'; node: ReactNode }[] = [];
const onNavDeposit = (address: string) => {
const isOwner = address.toLowerCase() === loginAccount.toLowerCase();
if (isOwner) {
return onOpen();
}

return router.push(
`${InternalRoutePaths.wallet}?type=send&from=${loginAccount}&to=${address}`,
);
};
const onCloseNotification = (address: string) => {
dispatch(setCloseRenewalAddresses([...closeRenewalAddresses, address]));
};

const ownAccounts = [loginAccount, ...paymentAccountList.map((item) => item.address)];
if ((address && !accountInfos[address]) || bankBalance === null || isEmpty(accountInfos)) {
return nodes;
}
const accounts = (address ? [address] : ownAccounts).filter((item) => !!item) || [];

for (const _account of accounts) {
const item = accountInfos[_account];

if (!item || closeRenewalAddresses.includes(item.address)) {
continue;
}

if (item.status === EStreamRecordStatus.FROZEN) {
const renewalStoreFee = BigNumber(item.frozenNetflowRate).times(reserveTime).abs();
const node = (
<Flex justifyContent={'space-between'} w={'100%'} alignItems={'center'}>
<Flex gap={'4px'}>
<Flex alignItems={'center'} h={20}>
<IconFont type={'colored-error2'} w={'16'} />
</Flex>
<Box fontSize={'14px'} lineHeight={'20px'}>
Your{' '}
<Box as="span" fontWeight={600}>
{item.name}
</Box>{' '}
is frozen, associated storage services are currently limited. To avoid data loss,
please deposit at least{' '}
<Box fontWeight={600} as="span">
{renewalStoreFee.isLessThan(MIN_AMOUNT)
? MIN_AMOUNT
: renewalStoreFee.toFixed(8, 0)}{' '}
{displayTokenSymbol()}
</Box>{' '}
to reactive.{' '}
<Button
variant="link"
onClick={() => onNavDeposit(item.address)}
fontWeight={400}
fontSize={'14px'}
textDecoration={'underline'}
>
Deposit Now
</Button>
</Box>
</Flex>
<IconFont
cursor={'pointer'}
onClick={() => {
onCloseNotification(item.address);
}}
color="readable.secondary"
type="close"
w={'16'}
/>
</Flex>
);
nodes.push({ type: 'danger', node });
continue;
}

// The purpose of settle is to lock in the costs for the next 6 months. At the time of settleTime, the user's costs from the last settlement (crudtimeStamp) to the current time will be deducted, primarily using the payment's buffer balance. If the storage price changes during the storage period, causing the buffer balance to be insufficient to cover the deduction, the remaining amount will be paid from the static balance. The future 6 months' costs also need to be locked in by transferring from the static balance/bank balance to the buffer balance.
const nextStoreFee = BigNumber(item.netflowRate).times(reserveTime).abs();
const curExtraFee = BigNumber(item.bufferBalance)
.minus(
BigNumber(item.netflowRate)
.abs()
.times(dayjs().unix() - item.crudTimestamp),
)
.isPositive()
? 0
: BigNumber(item.bufferBalance)
.minus(
BigNumber(item.netflowRate)
.abs()
.times(dayjs().unix() - item.crudTimestamp),
)
.abs();
const fee = nextStoreFee.plus(curExtraFee);
const isOwnerAccount = item.address.toLowerCase() === loginAccount.toLowerCase();
const lessThan7Days =
item.settleTimestamp !== 0 ? item.settleTimestamp - dayjs().unix() < 1 * 60 : false;
const notPayNextFee = isOwnerAccount
? BigNumber(item.staticBalance).plus(bankBalance).isLessThan(fee)
: BigNumber(item.staticBalance).isLessThan(fee);

if (lessThan7Days && notPayNextFee) {
const node = (
<Flex justifyContent={'space-between'} w={'100%'} alignItems={'center'}>
<Flex gap={'4px'}>
<Flex alignItems={'center'} h={20}>
<IconFont type={'warning'} w={'16'} />
</Flex>
<Box fontSize={'14px'}>
Your{' '}
<Box as="span" fontWeight={600}>
{item.name}
</Box>{' '}
is estimated to settle on {dayjs(item.settleTimestamp * 1000).format('MMM-DD-YYYY')}
. To avoid account freezing and potential data loss, please deposit at least{' '}
<Box fontWeight={600} as="span">
{fee.isLessThan(MIN_AMOUNT) ? MIN_AMOUNT : fee.toFixed(8, 0)}{' '}
{displayTokenSymbol()}
</Box>{' '}
into your payment account or associated owner account.{' '}
<Button
variant="link"
onClick={() => onNavDeposit(item.address)}
fontWeight={400}
fontSize={'14px'}
textDecoration={'underline'}
>
Deposit Now
</Button>
</Box>
</Flex>
<IconFont
cursor={'pointer'}
onClick={() => {
onCloseNotification(item.address);
}}
color="readable.secondary"
type="close"
w={'16'}
/>
</Flex>
);
nodes.push({ type: 'warning', node });
}
}
return nodes;
}, [
loginAccount,
paymentAccountList,
address,
accountInfos,
bankBalance,
router,
onOpen,
dispatch,
closeRenewalAddresses,
reserveTime,
]);

return (
<>
<Flex flexDirection={'column'} gap={16} mb={16}>
{notifications.map((item, index) => (
<Flex
key={index}
color={item.type === 'danger' ? '#CA300E' : 'readable.label-normal'}
bgColor={item.type === 'danger' ? 'rgba(238, 57, 17, 0.1)' : 'opacity3'}
borderRadius={4}
padding={'8px 12px'}
gap={12}
>
{item.node}
</Flex>
))}
</Flex>
<RenewalGuideModal isOpen={isOpen} onClose={onClose} />
</>
);
};
Loading

0 comments on commit 648afa0

Please sign in to comment.