diff --git a/apps/watcher/app/(home)/@infoWidgets/page.tsx b/apps/watcher/app/(home)/@infoWidgets/page.tsx index 065bf9e3..e87769b7 100644 --- a/apps/watcher/app/(home)/@infoWidgets/page.tsx +++ b/apps/watcher/app/(home)/@infoWidgets/page.tsx @@ -92,7 +92,7 @@ const InfoWidgets = () => { { { >('/permit', mutator); useEffect(() => { - if (!isRsnTokenLoading && !rsnToken) { + if (!isRsnTokenLoading && !rsnToken?.amount) { setAlertData({ severity: 'error', message: 'RSN token does not exist', @@ -90,7 +90,7 @@ const LockForm = () => { ); - const disabled = isRsnTokenLoading || !rsnToken; + const disabled = isRsnTokenLoading || !rsnToken?.amount; const renderTokenAmountTextField = () => ( { fetcher, ); + const { rsnToken, isLoading: isRsnTokenLoading } = useRsnToken(); + const rwtPartialToken = useMemo< Pick | undefined >( @@ -38,10 +43,11 @@ const UnlockForm = () => { info?.permitCount.active ? { amount: info.permitCount.active, - decimals: 0, + decimals: rsnToken?.decimals ?? 0, + name: rsnToken?.name, } : undefined, - [info?.permitCount], + [info, rsnToken], ); const [alertData, setAlertData] = useState<{ @@ -63,24 +69,22 @@ const UnlockForm = () => { }); const { handleSubmit } = formMethods; - const noRwtToken = !!rwtPartialToken && !rwtPartialToken.amount; - useEffect(() => { - if (noRwtToken) { + if (!isInfoLoading && !rwtPartialToken?.amount) { setAlertData({ severity: 'error', - message: 'no Permit', + message: "You don't have any permit tokens", }); } else { setAlertData(null); } - }, [noRwtToken]); + }, [isInfoLoading, rwtPartialToken?.amount]); const onSubmit: SubmitHandler = async ( data, ) => { try { - const count = data.amount; + const count = getNonDecimalString(data.amount, rsnToken?.decimals ?? 0); const response = await trigger({ count }); if (response?.txId) { @@ -112,10 +116,13 @@ const UnlockForm = () => { ); + const disabled = + isInfoLoading || isRsnTokenLoading || !rwtPartialToken?.amount; + const renderTokenAmountTextField = () => ( ); @@ -129,7 +136,7 @@ const UnlockForm = () => { {renderTokenAmountTextField()} - + Unlock diff --git a/apps/watcher/app/actions/@form/withdraw/page.tsx b/apps/watcher/app/actions/@form/withdraw/page.tsx index 42b46e91..91b3a459 100644 --- a/apps/watcher/app/actions/@form/withdraw/page.tsx +++ b/apps/watcher/app/actions/@form/withdraw/page.tsx @@ -30,6 +30,8 @@ import TokenAmountTextField, { TokenAmountCompatibleFormSchema, } from '../../TokenAmountTextField'; +import useToken from '@/_hooks/useToken'; + import { ApiAddressAssetsResponse, ApiWithdrawRequestBody, @@ -43,7 +45,14 @@ interface Form extends TokenAmountCompatibleFormSchema { const WithdrawForm = () => { const { data, isLoading: isTokensListLoading } = - useSWR('/address/assets', fetcher); + useSWR('/address/assets', fetcher, {}); + + const { token: ergToken, isLoading: isErgTokenLoading } = useToken('erg'); + + const tokens = useMemo( + () => data?.items.filter((token) => !!token.amount), + [data], + ); const [alertData, setAlertData] = useState<{ severity: AlertProps['severity']; @@ -57,14 +66,23 @@ const WithdrawForm = () => { ApiWithdrawRequestBody >('/withdraw', mutator); + useEffect(() => { + if (!isErgTokenLoading && !ergToken?.amount) { + setAlertData({ + severity: 'error', + message: 'Your wallet is empty. There is nothing to withdraw.', + }); + } + }, [isErgTokenLoading, ergToken]); + const formMethods = useForm({ defaultValues: { address: '', - tokenId: data?.items?.[0].tokenId ?? '', + tokenId: tokens?.[0]?.tokenId ?? '', amount: '', }, }); - const { handleSubmit, control, resetField, register, setValue } = formMethods; + const { handleSubmit, control, resetField, register } = formMethods; const { field: tokenIdField } = useController({ control, @@ -72,15 +90,15 @@ const WithdrawForm = () => { }); const selectedToken = useMemo( - () => data?.items?.find((token) => token.tokenId === tokenIdField.value), - [data, tokenIdField.value], + () => tokens?.find((token) => token.tokenId === tokenIdField.value), + [tokens, tokenIdField.value], ); useEffect(() => { - if (data && !tokenIdField.value) { - resetField('tokenId', { defaultValue: data?.items[0].tokenId }); + if (tokens && !tokenIdField.value) { + resetField('tokenId', { defaultValue: tokens?.[0]?.tokenId ?? '' }); } - }, [data, resetField, tokenIdField.value]); + }, [tokens, resetField, tokenIdField.value]); const onSubmit: SubmitHandler
= async (data) => { try { @@ -122,10 +140,14 @@ const WithdrawForm = () => { ); + const disabled = + isTokensListLoading || isErgTokenLoading || !ergToken?.amount; + const renderAddressTextField = () => ( ); @@ -134,7 +156,7 @@ const WithdrawForm = () => { @@ -144,7 +166,7 @@ const WithdrawForm = () => { }} {...tokenIdField} > - {data?.items?.map((token) => ( + {tokens?.map((token) => ( {token.name ?? TOKEN_NAME_PLACEHOLDER}   @@ -159,10 +181,7 @@ const WithdrawForm = () => { ); const renderTokenAmountTextField = () => ( - + ); return ( @@ -183,7 +202,9 @@ const WithdrawForm = () => { {renderTokenAmountTextField()} - Withdraw + + Withdraw + ); diff --git a/apps/watcher/app/actions/TokenAmountTextField.tsx b/apps/watcher/app/actions/TokenAmountTextField.tsx index f39ca396..6ecb2bcf 100644 --- a/apps/watcher/app/actions/TokenAmountTextField.tsx +++ b/apps/watcher/app/actions/TokenAmountTextField.tsx @@ -1,4 +1,4 @@ -import { useEffect } from 'react'; +import { MouseEventHandler, useEffect } from 'react'; import { useController, useFormContext } from 'react-hook-form'; import { @@ -18,7 +18,7 @@ export interface TokenAmountCompatibleFormSchema { interface TokenAmountTextFieldProps { disabled: boolean; loading?: boolean; - token: Pick | undefined; + token: Pick | undefined; } /** * render a react-hook-form compatible text field for token amount input, @@ -58,8 +58,11 @@ const TokenAmountTextField = ({ const getMaxAvailableTokenAmount = () => getDecimalString(token!.amount.toString(), token!.decimals); - const setAmountToMaxAvailable = () => + const setAmountToMaxAvailable: MouseEventHandler = (event) => { + event.preventDefault(); + setAmountFieldValue(getMaxAvailableTokenAmount()); + }; return ( {getMaxAvailableTokenAmount()} {' '} - available + {token.name} available ) } InputProps={{ endAdornment: token && ( - diff --git a/package-lock.json b/package-lock.json index 86e34de0..04d14c5c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2320,6 +2320,21 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/lodash": { + "version": "4.14.199", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.199.tgz", + "integrity": "sha512-Vrjz5N5Ia4SEzWWgIVwnHNEnb1UE1XMkvY5DGXrAeOGE9imk0hgTHh5GyDjLDJi9OTCn9oo9dXH1uToK1VRfrg==", + "dev": true + }, + "node_modules/@types/lodash-es": { + "version": "4.17.9", + "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.9.tgz", + "integrity": "sha512-ZTcmhiI3NNU7dEvWLZJkzG6ao49zOIjEgIE0RgV7wbPxU0f2xT3VSAHw2gmst8swH6V0YkLRGp4qPlX/6I90MQ==", + "dev": true, + "dependencies": { + "@types/lodash": "*" + } + }, "node_modules/@types/moment": { "version": "2.13.0", "resolved": "https://registry.npmjs.org/@types/moment/-/moment-2.13.0.tgz", @@ -5163,6 +5178,11 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" + }, "node_modules/lodash.get": { "version": "4.4.2", "dev": true, @@ -7406,7 +7426,11 @@ "packages/utils": { "name": "@rosen-ui/utils", "version": "0.0.1", + "dependencies": { + "lodash-es": "^4.17.21" + }, "devDependencies": { + "@types/lodash-es": "^4.17.9", "@typescript-eslint/eslint-plugin": "^5.30.7", "@typescript-eslint/parser": "^5.26.0", "eslint": "^8.16.0", diff --git a/packages/utils/package.json b/packages/utils/package.json index 2f41f6b3..378a07ea 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -12,11 +12,15 @@ "type-check": "tsc --noEmit" }, "devDependencies": { + "@types/lodash-es": "^4.17.9", "@typescript-eslint/eslint-plugin": "^5.30.7", "@typescript-eslint/parser": "^5.26.0", "eslint": "^8.16.0", "eslint-config-prettier": "^9.0.0", "prettier": "^3.0.2", "typescript": "^5.0.0" + }, + "dependencies": { + "lodash-es": "^4.17.21" } } diff --git a/packages/utils/src/decimals.ts b/packages/utils/src/decimals.ts index dd2d685e..3e336a08 100644 --- a/packages/utils/src/decimals.ts +++ b/packages/utils/src/decimals.ts @@ -1,21 +1,25 @@ +import { trimEnd } from 'lodash-es'; + /** * convert a raw value to a string representation of the same value but - * considering decimals + * considering decimals (removing any leading redundant zeros) * * @param value * @param decimals * * @example * getDecimalString('123', 2) === '1.23' // true + * getDecimalString('1230', 2) === '12.3' // true */ export const getDecimalString = (value: string, decimals: number) => { if (!decimals) return value; - if (value.length > decimals) { - return `${value.slice(0, -1 * decimals)}.${value.slice(-1 * decimals)}`; - } + const untrimmedResult = + value.length > decimals + ? `${value.slice(0, -decimals)}.${value.slice(-decimals)}` + : `0.${value.padStart(decimals, '0')}`; - return `0.${'0'.repeat(decimals - value.length)}${value}`; + return trimEnd(trimEnd(untrimmedResult, '0'), '.') || '0'; }; /**