From 4d4d0eed565b86c867fe9922e96e8b7cc12e4cb2 Mon Sep 17 00:00:00 2001 From: Steven Date: Wed, 8 Jan 2025 17:31:20 +0100 Subject: [PATCH] feat(rwa): add a message when a investor is frozen (#2779) --- .../apps/rwa-demo/src/app/(app)/layout.tsx | 2 + .../BadgeFeezeForm/BadgeFeezeForm.tsx | 67 ---------- .../BadgeFreezeForm/BadgeFreezeForm.tsx | 117 ++++++++++++++++++ .../src/components/DemoBanner/DemoBanner.tsx | 2 +- .../FreezeInvestor/FreezeInvestor.tsx | 110 ++++++++++++---- .../FrozenInvestorBanner.tsx | 9 ++ .../InvestorFrozenMessage.tsx | 27 +++- .../components/InvestorList/InvestorList.tsx | 2 +- .../src/hooks/batchFreezeInvestors.ts | 4 + packages/apps/rwa-demo/src/hooks/freeze.ts | 9 +- .../apps/rwa-demo/src/hooks/freezeInvestor.ts | 4 + .../src/services/batchSetAddressFrozen.ts | 1 + .../rwa-demo/src/services/setAddressFrozen.ts | 1 + .../apps/rwa-demo/src/utils/store/index.ts | 53 +++++++- 14 files changed, 305 insertions(+), 103 deletions(-) delete mode 100644 packages/apps/rwa-demo/src/components/BadgeFeezeForm/BadgeFeezeForm.tsx create mode 100644 packages/apps/rwa-demo/src/components/BadgeFreezeForm/BadgeFreezeForm.tsx create mode 100644 packages/apps/rwa-demo/src/components/FrozenInvestorBanner/FrozenInvestorBanner.tsx diff --git a/packages/apps/rwa-demo/src/app/(app)/layout.tsx b/packages/apps/rwa-demo/src/app/(app)/layout.tsx index 3ab1e7a11a..9953c1c48b 100644 --- a/packages/apps/rwa-demo/src/app/(app)/layout.tsx +++ b/packages/apps/rwa-demo/src/app/(app)/layout.tsx @@ -11,6 +11,7 @@ import { import { ActiveTransactionsList } from '@/components/ActiveTransactionsList/ActiveTransactionsList'; import { AssetInfo } from '@/components/AssetInfo/AssetInfo'; import { DemoBanner } from '@/components/DemoBanner/DemoBanner'; +import { FrozenInvestorBanner } from '@/components/FrozenInvestorBanner/FrozenInvestorBanner'; import { GraphOnlineBanner } from '@/components/GraphOnlineBanner/GraphOnlineBanner'; import { TransactionPendingIcon } from '@/components/TransactionPendingIcon/TransactionPendingIcon'; import { useAccount } from '@/hooks/account'; @@ -92,6 +93,7 @@ const RootLayout = ({ + } logo={ diff --git a/packages/apps/rwa-demo/src/components/BadgeFeezeForm/BadgeFeezeForm.tsx b/packages/apps/rwa-demo/src/components/BadgeFeezeForm/BadgeFeezeForm.tsx deleted file mode 100644 index 36b541589a..0000000000 --- a/packages/apps/rwa-demo/src/components/BadgeFeezeForm/BadgeFeezeForm.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import { useBatchFreezeInvestors } from '@/hooks/batchFreezeInvestors'; -import type { IBatchSetAddressFrozenProps } from '@/services/batchSetAddressFrozen'; -import { MonoPause, MonoPlayArrow } from '@kadena/kode-icons'; -import { Button } from '@kadena/kode-ui'; - -import { useAccount } from '@/hooks/account'; -import type { FC } from 'react'; -import type { UseFormHandleSubmit, UseFormReset } from 'react-hook-form'; -import { TransactionTypeSpinner } from '../TransactionTypeSpinner/TransactionTypeSpinner'; -import { TXTYPES } from '../TransactionsProvider/TransactionsProvider'; - -interface IProps { - pause: boolean; - isDisabled: boolean; - handleReset: UseFormReset<{ - select: []; - }>; - handleSubmit: UseFormHandleSubmit< - { - select: []; - }, - undefined - >; -} - -export const BadgeFreezeForm: FC = ({ - handleSubmit, - handleReset, - isDisabled, - pause, -}) => { - const { account } = useAccount(); - const { submit, isAllowed } = useBatchFreezeInvestors(); - - const onSubmit = async ({ select }: { select: string[] }) => { - const data: IBatchSetAddressFrozenProps = { - investorAccounts: select, - pause, - }; - const tx = await submit(data); - tx?.listener.subscribe( - () => {}, - () => {}, - () => { - handleReset({ select: [] }); - }, - ); - }; - - return ( - - ); -}; diff --git a/packages/apps/rwa-demo/src/components/BadgeFreezeForm/BadgeFreezeForm.tsx b/packages/apps/rwa-demo/src/components/BadgeFreezeForm/BadgeFreezeForm.tsx new file mode 100644 index 0000000000..5bc47ef807 --- /dev/null +++ b/packages/apps/rwa-demo/src/components/BadgeFreezeForm/BadgeFreezeForm.tsx @@ -0,0 +1,117 @@ +import { useBatchFreezeInvestors } from '@/hooks/batchFreezeInvestors'; +import type { IBatchSetAddressFrozenProps } from '@/services/batchSetAddressFrozen'; +import { MonoPause, MonoPlayArrow } from '@kadena/kode-icons'; +import { + Button, + Dialog, + DialogContent, + DialogFooter, + DialogHeader, + TextareaField, +} from '@kadena/kode-ui'; + +import { useAccount } from '@/hooks/account'; +import type { ChangeEvent, FC } from 'react'; +import { useState } from 'react'; +import type { UseFormHandleSubmit, UseFormReset } from 'react-hook-form'; +import { TransactionTypeSpinner } from '../TransactionTypeSpinner/TransactionTypeSpinner'; +import { TXTYPES } from '../TransactionsProvider/TransactionsProvider'; + +interface IProps { + pause: boolean; + isDisabled: boolean; + handleReset: UseFormReset<{ + select: []; + }>; + handleSubmit: UseFormHandleSubmit< + { + select: []; + }, + undefined + >; +} + +export const BadgeFreezeForm: FC = ({ + handleSubmit, + handleReset, + isDisabled, + pause, +}) => { + const [isModalOpen, setIsModalOpen] = useState(false); + const [message, setMessage] = useState(''); + const { account } = useAccount(); + const { submit, isAllowed } = useBatchFreezeInvestors(); + + const onSubmit = async ({ select }: { select: string[] }) => { + const data: IBatchSetAddressFrozenProps = { + investorAccounts: select, + pause, + message, + }; + const tx = await submit(data); + setIsModalOpen(false); + tx?.listener.subscribe( + () => {}, + () => {}, + () => { + handleReset({ select: [] }); + }, + ); + }; + + const handleMessageChange = (e: ChangeEvent) => { + setMessage((e.target as HTMLTextAreaElement).value); + }; + + const handleStart = async () => { + if (pause) { + setIsModalOpen(true); + return; + } + }; + + return ( + <> + {pause && ( + setIsModalOpen(false)}> + Freeze selected accounts + + + + + + + + + )} + + + ); +}; diff --git a/packages/apps/rwa-demo/src/components/DemoBanner/DemoBanner.tsx b/packages/apps/rwa-demo/src/components/DemoBanner/DemoBanner.tsx index 762a0d820c..0ccafc786c 100644 --- a/packages/apps/rwa-demo/src/components/DemoBanner/DemoBanner.tsx +++ b/packages/apps/rwa-demo/src/components/DemoBanner/DemoBanner.tsx @@ -3,7 +3,7 @@ import type { FC } from 'react'; export const DemoBanner: FC = () => { return ( - + This is a demo app. And is not running on Mainnet diff --git a/packages/apps/rwa-demo/src/components/FreezeInvestor/FreezeInvestor.tsx b/packages/apps/rwa-demo/src/components/FreezeInvestor/FreezeInvestor.tsx index d33741ec72..b1cc3892ec 100644 --- a/packages/apps/rwa-demo/src/components/FreezeInvestor/FreezeInvestor.tsx +++ b/packages/apps/rwa-demo/src/components/FreezeInvestor/FreezeInvestor.tsx @@ -2,9 +2,20 @@ import { useFreeze } from '@/hooks/freeze'; import { useFreezeInvestor } from '@/hooks/freezeInvestor'; import { MonoPause, MonoPlayArrow } from '@kadena/kode-icons'; import type { IButtonProps } from '@kadena/kode-ui'; -import { Button } from '@kadena/kode-ui'; +import { + Button, + Dialog, + DialogContent, + DialogFooter, + DialogHeader, + DialogHeaderSubtitle, + maskValue, + Text, + TextareaField, +} from '@kadena/kode-ui'; import type { FC } from 'react'; import React, { useEffect, useState } from 'react'; +import { useForm } from 'react-hook-form'; import { SendTransactionAnimation } from '../SendTransactionAnimation/SendTransactionAnimation'; import { TransactionPendingIcon } from '../TransactionPendingIcon/TransactionPendingIcon'; import { TXTYPES } from '../TransactionsProvider/TransactionsProvider'; @@ -30,25 +41,48 @@ export const FreezeInvestor: FC = ({ isCompact, variant, }) => { + const [isModalOpen, setIsModalOpen] = useState(false); const { frozen } = useFreeze({ investorAccount }); const [isLoading, setIsLoading] = useState(true); const { submit, isAllowed } = useFreezeInvestor(); - const handleFreeze = async () => { + const { + register, + handleSubmit, + formState: { isValid }, + } = useForm<{ message: string }>({ + values: { + message: '', + }, + }); + + const handleFreeze = async (data?: { message: string }) => { if (frozen === undefined) return; - const data = { + const newData = { investorAccount: investorAccount, pause: !frozen, + message: data?.message, }; try { setIsLoading(true); - return await submit(data); + return await submit(newData); } catch (e: any) { setIsLoading(false); + } finally { + setIsModalOpen(false); } }; + const handleStart = async () => { + if (frozen === undefined || frozen === false) { + setIsModalOpen(true); + return; + } + + await handleFreeze(); + }; + useEffect(() => { setIsLoading(false); }, [frozen]); @@ -56,24 +90,56 @@ export const FreezeInvestor: FC = ({ const label = iconOnly ? '' : frozen ? 'Unfreeze account' : 'Freeze account'; return ( - + setIsModalOpen(false)}> +
+ Freeze the account + + {maskValue(investorAccount)} + + + - } - isDisabled={!isAllowed} - isCompact={isCompact} - variant={variant} - > - {label} - - } - > + + + + + +
+
+ + } + isDisabled={!isAllowed} + isCompact={isCompact} + variant={variant} + > + {label} + + } + > + ); }; diff --git a/packages/apps/rwa-demo/src/components/FrozenInvestorBanner/FrozenInvestorBanner.tsx b/packages/apps/rwa-demo/src/components/FrozenInvestorBanner/FrozenInvestorBanner.tsx new file mode 100644 index 0000000000..353787cb63 --- /dev/null +++ b/packages/apps/rwa-demo/src/components/FrozenInvestorBanner/FrozenInvestorBanner.tsx @@ -0,0 +1,9 @@ +import { useAccount } from '@/hooks/account'; +import type { FC } from 'react'; +import { InvestorFrozenMessage } from '../InvestorFrozenMessage/InvestorFrozenMessage'; + +export const FrozenInvestorBanner: FC = () => { + const { account } = useAccount(); + if (!account) return; + return ; +}; diff --git a/packages/apps/rwa-demo/src/components/InvestorFrozenMessage/InvestorFrozenMessage.tsx b/packages/apps/rwa-demo/src/components/InvestorFrozenMessage/InvestorFrozenMessage.tsx index 0ae8b6eb17..2226267d06 100644 --- a/packages/apps/rwa-demo/src/components/InvestorFrozenMessage/InvestorFrozenMessage.tsx +++ b/packages/apps/rwa-demo/src/components/InvestorFrozenMessage/InvestorFrozenMessage.tsx @@ -1,18 +1,37 @@ +import { useAccount } from '@/hooks/account'; import { useFreeze } from '@/hooks/freeze'; -import { Notification } from '@kadena/kode-ui'; +import { store } from '@/utils/store'; +import { Notification, NotificationHeading } from '@kadena/kode-ui'; import type { FC } from 'react'; +import { useEffect, useState } from 'react'; interface IProps { investorAccount: string; } export const InvestorFrozenMessage: FC = ({ investorAccount }) => { + const { isInvestor, account } = useAccount(); const { frozen } = useFreeze({ investorAccount }); + const [message, setMessage] = useState(); - if (!frozen) return; + const init = async () => { + if (!account) return; + const result = await store.getFrozenMessage(account.address); + setMessage(result); + }; + + useEffect(() => { + if (!isInvestor || !account) return; + + // eslint-disable-next-line @typescript-eslint/no-floating-promises + init(); + }, [frozen, isInvestor, account]); + + if (!frozen || !isInvestor || !account) return; return ( - - The investor account is frozen + + The investor account is frozen + {message} ); }; diff --git a/packages/apps/rwa-demo/src/components/InvestorList/InvestorList.tsx b/packages/apps/rwa-demo/src/components/InvestorList/InvestorList.tsx index edf8a7e405..ce8cfb3f24 100644 --- a/packages/apps/rwa-demo/src/components/InvestorList/InvestorList.tsx +++ b/packages/apps/rwa-demo/src/components/InvestorList/InvestorList.tsx @@ -15,7 +15,7 @@ import { useRouter } from 'next/navigation'; import type { FC } from 'react'; import { useRef } from 'react'; import { useForm } from 'react-hook-form'; -import { BadgeFreezeForm } from '../BadgeFeezeForm/BadgeFeezeForm'; +import { BadgeFreezeForm } from '../BadgeFreezeForm/BadgeFreezeForm'; import { InvestorBatchForm } from '../InvestorBatchForm/InvestorBatchForm'; import { InvestorForm } from '../InvestorForm/InvestorForm'; import { FormatCheckbox } from '../TableFormatters/FormatCheckbox'; diff --git a/packages/apps/rwa-demo/src/hooks/batchFreezeInvestors.ts b/packages/apps/rwa-demo/src/hooks/batchFreezeInvestors.ts index 65a3b027d8..ef5bd30f51 100644 --- a/packages/apps/rwa-demo/src/hooks/batchFreezeInvestors.ts +++ b/packages/apps/rwa-demo/src/hooks/batchFreezeInvestors.ts @@ -6,6 +6,7 @@ import { import type { IBatchSetAddressFrozenProps } from '@/services/batchSetAddressFrozen'; import { batchSetAddressFrozen } from '@/services/batchSetAddressFrozen'; import { getClient } from '@/utils/client'; +import { store } from '@/utils/store'; import { useNotifications } from '@kadena/kode-ui/patterns'; import { useEffect, useState } from 'react'; import { useAccount } from './account'; @@ -24,7 +25,10 @@ export const useBatchFreezeInvestors = () => { ): Promise => { try { const tx = await batchSetAddressFrozen(data, account!); + // eslint-disable-next-line @typescript-eslint/no-floating-promises + await store.setFrozenMessages(data); const signedTransaction = await sign(tx); + if (!signedTransaction) return; const client = getClient(); diff --git a/packages/apps/rwa-demo/src/hooks/freeze.ts b/packages/apps/rwa-demo/src/hooks/freeze.ts index e09d262fea..cb81f6f488 100644 --- a/packages/apps/rwa-demo/src/hooks/freeze.ts +++ b/packages/apps/rwa-demo/src/hooks/freeze.ts @@ -55,10 +55,13 @@ export const useFreeze = ({ useEffect(() => { if (!data?.events?.length) return; - const params = JSON.parse(data?.events[0].parameters ?? '[]'); - if (params.length < 2 || params[0] !== investorAccount) return; - setFrozen(params[1]); + data?.events?.map((evt) => { + const params = JSON.parse(evt.parameters ?? '[]'); + if (params.length < 2 || params[0] !== investorAccount) return; + + setFrozen(params[1]); + }); }, [data]); return { frozen }; diff --git a/packages/apps/rwa-demo/src/hooks/freezeInvestor.ts b/packages/apps/rwa-demo/src/hooks/freezeInvestor.ts index 248873a2bb..55b21fea66 100644 --- a/packages/apps/rwa-demo/src/hooks/freezeInvestor.ts +++ b/packages/apps/rwa-demo/src/hooks/freezeInvestor.ts @@ -6,6 +6,7 @@ import { import type { ISetAddressFrozenProps } from '@/services/setAddressFrozen'; import { setAddressFrozen } from '@/services/setAddressFrozen'; import { getClient } from '@/utils/client'; +import { store } from '@/utils/store'; import { useNotifications } from '@kadena/kode-ui/patterns'; import { useEffect, useState } from 'react'; import { useAccount } from './account'; @@ -22,8 +23,11 @@ export const useFreezeInvestor = () => { const submit = async ( data: ISetAddressFrozenProps, ): Promise => { + if (!account) return; try { const tx = await setAddressFrozen(data, account!); + await store.setFrozenMessage(data); + const signedTransaction = await sign(tx); if (!signedTransaction) return; diff --git a/packages/apps/rwa-demo/src/services/batchSetAddressFrozen.ts b/packages/apps/rwa-demo/src/services/batchSetAddressFrozen.ts index 223bc8fd24..336be0c381 100644 --- a/packages/apps/rwa-demo/src/services/batchSetAddressFrozen.ts +++ b/packages/apps/rwa-demo/src/services/batchSetAddressFrozen.ts @@ -7,6 +7,7 @@ import { Pact } from '@kadena/client'; export interface IBatchSetAddressFrozenProps { investorAccounts: string[]; pause: boolean; + message?: string; } export const batchSetAddressFrozen = async ( diff --git a/packages/apps/rwa-demo/src/services/setAddressFrozen.ts b/packages/apps/rwa-demo/src/services/setAddressFrozen.ts index a623b4e433..8d5bafb8eb 100644 --- a/packages/apps/rwa-demo/src/services/setAddressFrozen.ts +++ b/packages/apps/rwa-demo/src/services/setAddressFrozen.ts @@ -7,6 +7,7 @@ import { Pact } from '@kadena/client'; export interface ISetAddressFrozenProps { investorAccount: string; pause: boolean; + message?: string; } export const setAddressFrozen = async ( diff --git a/packages/apps/rwa-demo/src/utils/store/index.ts b/packages/apps/rwa-demo/src/utils/store/index.ts index 9af1b168d3..c0a8430c27 100644 --- a/packages/apps/rwa-demo/src/utils/store/index.ts +++ b/packages/apps/rwa-demo/src/utils/store/index.ts @@ -1,6 +1,8 @@ import type { ITransaction } from '@/components/TransactionsProvider/TransactionsProvider'; import type { ICSVAccount } from '@/services/batchRegisterIdentity'; +import type { IBatchSetAddressFrozenProps } from '@/services/batchSetAddressFrozen'; import type { IRegisterIdentityProps } from '@/services/registerIdentity'; +import type { ISetAddressFrozenProps } from '@/services/setAddressFrozen'; import { get, off, onValue, ref, set } from 'firebase/database'; import { getAsset } from '../getAsset'; import { database } from './firebase'; @@ -59,7 +61,7 @@ const RWAStore = () => { const data = snapshot.toJSON(); if (!data) return []; return Object.entries(data).map( - ([key, value]) => value, + ([key, value]) => value.account, ) as IRegisterIdentityProps[]; }; @@ -71,7 +73,9 @@ const RWAStore = () => { const asset = getAssetFolder(); if (!asset) return; - const snapshot = await get(ref(database, `${asset}/accounts/${account}`)); + const snapshot = await get( + ref(database, `${asset}/accounts/${account}/account`), + ); return snapshot.toJSON() as IRegisterIdentityProps; }; @@ -83,7 +87,7 @@ const RWAStore = () => { const asset = getAssetFolder(); if (!asset) return; - await set(ref(database, `${asset}/accounts/${accountName}`), { + await set(ref(database, `${asset}/accounts/${accountName}/account`), { accountName, alias: alias ?? '', }); @@ -102,7 +106,7 @@ const RWAStore = () => { const asset = getAssetFolder(); if (!asset) return; - const accountRef = ref(database, `${asset}/accounts/${account}`); + const accountRef = ref(database, `${asset}/accounts/${account}/account`); onValue(accountRef, async (snapshot) => { const data = snapshot.toJSON(); @@ -124,7 +128,7 @@ const RWAStore = () => { setDataCallback( Object.entries(data).map( - ([key, value]) => value, + ([key, value]) => value.account, ) as IRegisterIdentityProps[], ); }); @@ -132,6 +136,42 @@ const RWAStore = () => { return () => off(accountRef); }; + const getFrozenMessage = async ( + account: string, + ): Promise => { + const asset = getAssetFolder(); + if (!asset) return; + + const snapshot = await get( + ref(database, `${asset}/accounts/${account}/frozenMessage`), + ); + + return snapshot.toJSON() as any; + }; + + const setFrozenMessage = async (data: ISetAddressFrozenProps) => { + const asset = getAssetFolder(); + if (!asset) return; + + await set( + ref(database, `${asset}/accounts/${data.investorAccount}/frozenMessage`), + data.message, + ); + }; + + const setFrozenMessages = async (data: IBatchSetAddressFrozenProps) => { + const asset = getAssetFolder(); + if (!asset) return; + + return data.investorAccounts.map((account) => + setFrozenMessage({ + investorAccount: account, + pause: data.pause, + message: data.message, + }), + ); + }; + return { addTransaction, removeTransaction, @@ -143,6 +183,9 @@ const RWAStore = () => { getAccounts, listenToAccount, listenToAccounts, + setFrozenMessage, + setFrozenMessages, + getFrozenMessage, }; };