From ce2b8fff0775684e8961f7f7263b8d08eae7fa67 Mon Sep 17 00:00:00 2001 From: hoailp Date: Tue, 19 Dec 2023 23:08:11 +0700 Subject: [PATCH] GSM-471 add enter all amount avalidate for withdraw modal --- .../components/common/notice/NoticeToast.tsx | 70 ++++++++++++++-- .../deposit-modal/DepositModal.styles.ts | 2 +- .../WalletBalanceDetailInfo.tsx | 8 +- .../WalletBalanceDetail.tsx | 9 +-- .../withdraw-modal/WithDrawModal.styles.ts | 30 ++++++- .../wallet/withdraw-modal/WithDrawModal.tsx | 19 ++++- .../withdraw-modal/useWithdrawTokens.tsx | 26 ++++-- .../SelectTokenContainer.tsx | 74 ++++++++++------- .../WalletBalanceContainer.tsx | 36 ++++++--- packages/web/src/context/NoticeContext.ts | 5 +- packages/web/tsconfig.json | 80 ++++++++++++++----- 11 files changed, 263 insertions(+), 96 deletions(-) diff --git a/packages/web/src/components/common/notice/NoticeToast.tsx b/packages/web/src/components/common/notice/NoticeToast.tsx index 448e14241..cce32c175 100644 --- a/packages/web/src/components/common/notice/NoticeToast.tsx +++ b/packages/web/src/components/common/notice/NoticeToast.tsx @@ -16,6 +16,9 @@ interface NoticeProps { onClose?: (id: number) => void; type: TNoticeType; id: number; + data?: { + [key: string]: string; + }; } const TEMP_URL = @@ -36,6 +39,24 @@ const SuccessContent: FC = () => { ); }; +const SuccessNormalContent: FC<{ title: string; description?: string }> = ({ + title, + description, +}) => { + return ( +
+ +
+
{title}
+ {description &&

{description}

} + + View transaction + +
+
+ ); +}; + const PendingContent: FC = () => { return (
@@ -66,7 +87,30 @@ const FailContent: FC = () => { ); }; -const NoticeUIItem: FC = ({ onClose, type = "success", id }) => { +const FailNormalContent: FC<{ title: string; description?: string }> = ({ + title, + description, +}) => { + return ( +
+ +
+
{title}
+ {description &&

{description}

} + + View transaction + +
+
+ ); +}; + +const NoticeUIItem: FC = ({ + onClose, + type = "success", + id, + data, +}) => { const [typeAnimation, setTypeAnimation] = useState< "toast-item" | "closing" | "" >("toast-item"); @@ -97,7 +141,15 @@ const NoticeUIItem: FC = ({ onClose, type = "success", id }) => { return ( {type === "success" && } + {type === "withdraw-success" && ( + + )} + {type === "error" && } + {type === "withdraw-error" && ( + + )} + {type === "pending" && }
@@ -117,7 +169,14 @@ const Notice: FC<{ children: React.ReactNode }> = ({ children }) => { setCurrentNotice({ ...options, }); - setList(prev => [...prev, { type: options.type, id: options.id }]); + setList(prev => [ + ...prev, + { + ...options, + type: options.type, + id: options.id, + }, + ]); }, [list], ); @@ -161,12 +220,7 @@ const Notice: FC<{ children: React.ReactNode }> = ({ children }) => { {list.map(item_ => { return ( - + ); })} diff --git a/packages/web/src/components/wallet/deposit-modal/DepositModal.styles.ts b/packages/web/src/components/wallet/deposit-modal/DepositModal.styles.ts index dee0e5b69..1b4ed7700 100644 --- a/packages/web/src/components/wallet/deposit-modal/DepositModal.styles.ts +++ b/packages/web/src/components/wallet/deposit-modal/DepositModal.styles.ts @@ -168,7 +168,7 @@ export const DepositLabel = styled.div` font-size: 14px; font-style: normal; font-weight: 400; - color: ${({ theme }) => theme.color.text05}; + color: ${({ theme }) => theme.color.text10}; } path { diff --git a/packages/web/src/components/wallet/wallet-balance-detail-info/WalletBalanceDetailInfo.tsx b/packages/web/src/components/wallet/wallet-balance-detail-info/WalletBalanceDetailInfo.tsx index 7bac78225..252f4162e 100644 --- a/packages/web/src/components/wallet/wallet-balance-detail-info/WalletBalanceDetailInfo.tsx +++ b/packages/web/src/components/wallet/wallet-balance-detail-info/WalletBalanceDetailInfo.tsx @@ -4,6 +4,7 @@ import { SHAPE_TYPES, skeletonBalanceDetail, } from "@constants/skeleton.constant"; +import { convertLargePrice } from "@utils/stake-position-utils"; import React from "react"; import { WalletBalanceDetailInfoTooltipContent, @@ -44,12 +45,7 @@ const WalletBalanceDetailInfo: React.FC = ({ />
) : ( - - $ - {Number(value).toLocaleString("en-US", { - maximumFractionDigits: 2, - })} - + ${convertLargePrice(value, 2)} ) ) : ( $0 diff --git a/packages/web/src/components/wallet/wallet-balance-detail/WalletBalanceDetail.tsx b/packages/web/src/components/wallet/wallet-balance-detail/WalletBalanceDetail.tsx index 14334a727..6ec893bd2 100644 --- a/packages/web/src/components/wallet/wallet-balance-detail/WalletBalanceDetail.tsx +++ b/packages/web/src/components/wallet/wallet-balance-detail/WalletBalanceDetail.tsx @@ -12,6 +12,7 @@ import { SHAPE_TYPES, skeletonBalanceDetail, } from "@constants/skeleton.constant"; +import { convertLargePrice } from "@utils/stake-position-utils"; interface WalletBalanceDetailProps { balanceDetailInfo: BalanceDetailInfo; @@ -72,13 +73,7 @@ const WalletBalanceDetail: React.FC = ({
) : ( - $ - {Number(balanceDetailInfo.claimableRewards).toLocaleString( - "en-US", - { - maximumFractionDigits: 2, - }, - )} + ${convertLargePrice(balanceDetailInfo.claimableRewards, 2)} ) ) : ( diff --git a/packages/web/src/components/wallet/withdraw-modal/WithDrawModal.styles.ts b/packages/web/src/components/wallet/withdraw-modal/WithDrawModal.styles.ts index 0c3c18ee5..394cdc436 100644 --- a/packages/web/src/components/wallet/withdraw-modal/WithDrawModal.styles.ts +++ b/packages/web/src/components/wallet/withdraw-modal/WithDrawModal.styles.ts @@ -102,7 +102,7 @@ export const WithdrawContent = styled.div` font-size: 14px; font-style: normal; font-weight: 400; - color: ${({ theme }) => theme.color.text05}; + color: ${({ theme }) => theme.color.text10}; } path { @@ -113,7 +113,7 @@ export const WithdrawContent = styled.div` font-size: 14px; font-style: normal; font-weight: 400; - color: ${({ theme }) => theme.color.text05}; + color: ${({ theme }) => theme.color.text10}; margin-bottom: 8px; } @@ -147,6 +147,11 @@ export const WithdrawContent = styled.div` font-style: normal; font-weight: 500; line-height: 130%; + + img { + width: 24px; + height: 24px; + } } .approximately { @@ -172,11 +177,28 @@ export const WithdrawContent = styled.div` } } + .estimate-box { + ${mixins.flexbox("row", "center", "space-between")}; + flex-wrap: wrap; + + width: 100%; + padding: 15px; + height: 60px; + + background-color: ${({ theme }) => theme.color.background01}; + border: 1px solid ${({ theme }) => theme.color.border02}; + border-radius: 8px; + ${media.mobile} { + padding: 11px; + } + } + .withdraw-address { width: 100%; .address-input { color: ${({ theme }) => theme.color.text01}; width: 100%; + font-size: 14px; ::placeholder { color: ${({ theme }) => theme.color.text04}; @@ -233,6 +255,10 @@ export const WithdrawContent = styled.div` ${fonts.p2} } } + + .balance-text { + cursor: pointer; + } `; export const BoxFromTo = styled.div` diff --git a/packages/web/src/components/wallet/withdraw-modal/WithDrawModal.tsx b/packages/web/src/components/wallet/withdraw-modal/WithDrawModal.tsx index da62cda95..d534d4cd9 100644 --- a/packages/web/src/components/wallet/withdraw-modal/WithDrawModal.tsx +++ b/packages/web/src/components/wallet/withdraw-modal/WithDrawModal.tsx @@ -111,7 +111,7 @@ const WithDrawModal: React.FC = ({ fromAddress: account?.address, toAddress: address, token: withdrawInfo, - tokenAmount: Number(amount), + tokenAmount: BigNumber(amount).multipliedBy(1000000).toNumber(), }, withdrawInfo.type, ); @@ -141,6 +141,12 @@ const WithDrawModal: React.FC = ({ const currentAvailableBalance = displayBalanceMap?.[withdrawInfo?.path ?? ""] ?? null; + const handleEnterAllBalanceAvailable = () => { + if (currentAvailableBalance) { + setAmount(`${currentAvailableBalance}`); + } + }; + if (isConfirm) { return ; } @@ -179,9 +185,14 @@ const WithDrawModal: React.FC = ({ {!Number(amount) ? "-" : `$${amount}`} - {`Available: ${ + {`Available: ${ currentAvailableBalance - ? BigNumber(currentAvailableBalance).toFormat() + ? BigNumber(currentAvailableBalance) + .decimalPlaces(2) + .toFormat() : "-" }`} @@ -252,7 +263,7 @@ const WithDrawModal: React.FC = ({ -
+

Estimated Network Fee

{`${estimateFee} GNOT${ estimateFeeUSD !== 0 ? ` ($${estimateFeeUSD})` : "" diff --git a/packages/web/src/components/wallet/withdraw-modal/useWithdrawTokens.tsx b/packages/web/src/components/wallet/withdraw-modal/useWithdrawTokens.tsx index 8901621e2..af72d3103 100644 --- a/packages/web/src/components/wallet/withdraw-modal/useWithdrawTokens.tsx +++ b/packages/web/src/components/wallet/withdraw-modal/useWithdrawTokens.tsx @@ -7,6 +7,7 @@ import { import { makeRandomId, parseJson } from "@utils/common"; import { useState } from "react"; import { TNoticeType } from "@context/NoticeContext"; +import BigNumber from "bignumber.js"; type Request = TransferGRC20TokenRequest | TransferNativeTokenRequest; export type WithdrawResponse = { @@ -27,6 +28,13 @@ const useWithdrawTokens = () => { const onSubmit = async (request: Request, type: "native" | "grc20") => { setLoading(true); + const descriptionFail = `Failed to Send ${BigNumber( + request?.tokenAmount, + ).div(1000000)} ${request?.token?.symbol}`; + const descriptionSuccess = `Sent ${BigNumber(request?.tokenAmount).div( + 1000000, + )} ${request?.token?.symbol}`; + const callAction = type === "native" ? walletRepository.transferGNOTToken(request) @@ -47,16 +55,20 @@ const useWithdrawTokens = () => { setTimeout(() => { setNotice(null, { timeout: 50000, - type: "success" as TNoticeType, + type: "withdraw-success" as TNoticeType, closeable: true, id: makeRandomId(), + data: { + description: descriptionSuccess, + }, }); }, 1000); }) .catch((error: Error) => { const { code } = parseJson(error?.message); + const isSuccess = code === 0; setResult({ - success: code === 0 ? true : false, + success: isSuccess, code, }); if (code !== 4000) { @@ -69,12 +81,14 @@ const useWithdrawTokens = () => { setTimeout(() => { setNotice(null, { timeout: 50000, - type: - code === 0 - ? ("success" as TNoticeType) - : ("error" as TNoticeType), + type: isSuccess + ? ("withdraw-success" as TNoticeType) + : ("withdraw-error" as TNoticeType), closeable: true, id: makeRandomId(), + data: { + description: isSuccess ? descriptionSuccess : descriptionFail, + }, }); }, 1000); } diff --git a/packages/web/src/containers/select-token-container/SelectTokenContainer.tsx b/packages/web/src/containers/select-token-container/SelectTokenContainer.tsx index 97d82cc74..c06994662 100644 --- a/packages/web/src/containers/select-token-container/SelectTokenContainer.tsx +++ b/packages/web/src/containers/select-token-container/SelectTokenContainer.tsx @@ -49,10 +49,10 @@ const customSortAll = (a: SortedProps, b: SortedProps): number => { } else { const priceA = parseFloat(a.price.replace(/,/g, "")); const priceB = parseFloat(b.price.replace(/,/g, "")); - + if (!isNaN(priceA) && !isNaN(priceB) && priceA < priceB) { return 1; - } else if (isNaN(priceA) && isNaN(priceB) ) { + } else if (isNaN(priceA) && isNaN(priceB)) { const numberRegex = /\d+/; const numberA = numberRegex.test(a.name); const numberB = numberRegex.test(b.name); @@ -63,7 +63,7 @@ const customSortAll = (a: SortedProps, b: SortedProps): number => { } else { return a.name.toLowerCase().localeCompare(b.name.toLowerCase()); } - } else if (!isNaN(priceA) || isNaN(priceB) ) { + } else if (!isNaN(priceA) || isNaN(priceB)) { if (priceA === 0 && priceB === 0) { if (a.tokenPrice < b.tokenPrice) { return 1; @@ -94,7 +94,14 @@ const SelectTokenContainer: React.FC = ({ modalRef, }) => { const { breakpoint } = useWindowSize(); - const { tokens, balances, updateTokens, updateBalances, tokenPrices, displayBalanceMap } = useTokenData(); + const { + tokens, + balances, + updateTokens, + updateBalances, + tokenPrices, + displayBalanceMap, + } = useTokenData(); const [keyword, setKeyword] = useState(""); const clearModal = useClearModal(); const themeKey = useAtomValue(ThemeState.themeKey); @@ -110,16 +117,15 @@ const SelectTokenContainer: React.FC = ({ setFromSelectToken(true); changeToken?.(value); close(); - } + }, }); - + useEffect(() => { updateTokens(); }, []); useEffect(() => { - if (tokens.length > 0) - updateBalances(); + if (tokens.length > 0) updateBalances(); }, [tokens]); const defaultTokens = useMemo(() => { @@ -127,7 +133,7 @@ const SelectTokenContainer: React.FC = ({ const sortedTokenList = temp.sort(customSort); return sortedTokenList.slice(0, 4); }, [tokens]); - + const filteredTokens = useMemo(() => { const lowerKeyword = keyword.toLowerCase(); const temp: SortedProps[] = tokens.map((item: TokenModel) => { @@ -139,28 +145,37 @@ const SelectTokenContainer: React.FC = ({ tokenPrice: tokenPrice || 0, }; } - return {...item, price: BigNumber(tokenPrice).multipliedBy(tokenPrices[item?.path]?.usd || "0").toFormat(), tokenPrice: tokenPrice || 0}; + return { + ...item, + price: BigNumber(tokenPrice) + .multipliedBy(tokenPrices[item?.path]?.usd || "0") + .toFormat(), + tokenPrice: tokenPrice || 0, + }; }); const sortedData = temp.sort(customSortAll); - return sortedData.filter(token => - token.name.toLowerCase().includes(lowerKeyword) || - token.symbol.toLowerCase().includes(lowerKeyword) || - token.path.toLowerCase().includes(lowerKeyword) + return sortedData.filter( + token => + token.name.toLowerCase().includes(lowerKeyword) || + token.symbol.toLowerCase().includes(lowerKeyword) || + token.path.toLowerCase().includes(lowerKeyword), ); }, [keyword, tokens, balances, tokenPrices]); - console.log(filteredTokens, "filteredTokens"); - - const selectToken = useCallback((token: TokenModel) => { - if (!changeToken) { - return; - } - if (token.logoURI) { - changeToken(token); - close(); - } else { - openTradingModal(token); - } - }, [changeToken, openTradingModal]); + + const selectToken = useCallback( + (token: TokenModel) => { + if (!changeToken) { + return; + } + if (token.logoURI) { + changeToken(token); + close(); + } else { + openTradingModal(token); + } + }, + [changeToken, openTradingModal], + ); const changeKeyword = useCallback((keyword: string) => { setKeyword(keyword); @@ -178,8 +193,7 @@ const SelectTokenContainer: React.FC = ({ }, []); useEffect(() => { - if (tokens.length > 0) - updateBalances(); + if (tokens.length > 0) updateBalances(); }, [tokens]); return ( @@ -199,4 +213,4 @@ const SelectTokenContainer: React.FC = ({ ); }; -export default SelectTokenContainer; \ No newline at end of file +export default SelectTokenContainer; diff --git a/packages/web/src/containers/wallet-balance-container/WalletBalanceContainer.tsx b/packages/web/src/containers/wallet-balance-container/WalletBalanceContainer.tsx index 6b1e669a7..906dd1080 100644 --- a/packages/web/src/containers/wallet-balance-container/WalletBalanceContainer.tsx +++ b/packages/web/src/containers/wallet-balance-container/WalletBalanceContainer.tsx @@ -9,8 +9,9 @@ import { useWindowSize } from "@hooks/common/use-window-size"; import { useTokenData } from "@hooks/token/use-token-data"; import { useWallet } from "@hooks/wallet/use-wallet"; import { TokenModel } from "@models/token/token-model"; -import { useQuery } from "@tanstack/react-query"; +import BigNumber from "bignumber.js"; import React, { useCallback, useEffect, useState } from "react"; +import { convertLargePrice } from "@utils/stake-position-utils"; export interface BalanceSummaryInfo { amount: string; @@ -75,22 +76,39 @@ const WalletBalanceContainer: React.FC = () => { const availableBalance: number = Object.keys(displayBalanceMap ?? {}) .map(x => displayBalanceMap[x] ?? 0) - .reduce((acc: number, cur: number) => acc + cur, 0); + .reduce( + (acc: number, cur: number) => BigNumber(acc).plus(cur).toNumber(), + 0, + ); const { stakedBalance, unStakedBalance, claimableRewards } = positions.reduce( (acc, cur) => { if (cur.staked) { - acc.stakedBalance + +cur.stakedUsdValue; + acc.stakedBalance = BigNumber(acc.stakedBalance) + .plus(cur.stakedUsdValue ?? "0") + .toNumber(); } else { - acc.unStakedBalance + +cur.stakedUsdValue; + acc.unStakedBalance = BigNumber(acc.unStakedBalance) + .plus(cur.stakedUsdValue ?? "0") + .toNumber(); } - cur.rewards.forEach(x => (acc.claimableRewards += +x.claimableUsdValue)); + cur.rewards.forEach(x => { + acc.claimableRewards = BigNumber(acc.claimableRewards) + .plus(x.claimableUsdValue ?? "0") + .toNumber(); + }); return acc; }, { stakedBalance: 0, unStakedBalance: 0, claimableRewards: 0 }, ); + const sumTotalBalance = BigNumber(availableBalance) + .plus(unStakedBalance) + .plus(stakedBalance) + .plus(claimableRewards) + .toString(); + const closeDeposit = () => { setIsShowDepositModal(false); }; @@ -114,13 +132,7 @@ const WalletBalanceContainer: React.FC = () => { void; + data?: { + [key: string]: string; + }; } export interface INoticeContext { diff --git a/packages/web/tsconfig.json b/packages/web/tsconfig.json index 4faa32f75..82a8e8638 100644 --- a/packages/web/tsconfig.json +++ b/packages/web/tsconfig.json @@ -1,7 +1,11 @@ { "compilerOptions": { "target": "esnext", - "lib": ["dom", "dom.iterable", "esnext"], + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], "allowJs": true, "skipLibCheck": true, "strict": true, @@ -16,24 +20,62 @@ "incremental": true, "baseUrl": "./", "paths": { - "@constants/*": ["src/constants/*"], - "@containers/*": ["src/containers/*"], - "@common/*": ["src/common/*"], - "@components/*": ["src/components/*"], - "@hooks/*": ["src/hooks/*"], - "@repositories/*": ["src/repositories/*"], - "@resources/*": ["src/resources/*"], - "@utils/*": ["src/utils/*"], - "@providers/*": ["src/providers/*"], - "@states/*": ["src/states/*"], - "@styles/*": ["src/styles/*"], - "@layouts/*": ["src/layouts/*"], - "@models/*": ["src/models/*"], - "@query/*": ["src/react-query/*"], - "@context/*": ["src/context/*"] + "@constants/*": [ + "src/constants/*" + ], + "@containers/*": [ + "src/containers/*" + ], + "@common/*": [ + "src/common/*" + ], + "@components/*": [ + "src/components/*" + ], + "@hooks/*": [ + "src/hooks/*" + ], + "@repositories/*": [ + "src/repositories/*" + ], + "@resources/*": [ + "src/resources/*" + ], + "@utils/*": [ + "src/utils/*" + ], + "@providers/*": [ + "src/providers/*" + ], + "@states/*": [ + "src/states/*" + ], + "@styles/*": [ + "src/styles/*" + ], + "@layouts/*": [ + "src/layouts/*" + ], + "@models/*": [ + "src/models/*" + ], + "@query/*": [ + "src/react-query/*" + ], + "@context/*": [ + "src/context/*" + ] }, "jsxImportSource": "@emotion/react" }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "jest.config.js"], - "exclude": ["node_modules"] -} + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + "jest.config.js" + ], + "exclude": [ + "node_modules" + ], + "declaration": true +} \ No newline at end of file