From 4356d1efcf7fa59d7676fb03417b6b24ecdd4ad1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Torres?= <26875009+luistorres@users.noreply.github.com> Date: Mon, 29 Apr 2024 09:38:17 +0100 Subject: [PATCH] Add USDC balance & dashboard (#225) Why: - We need to display the token payment balance instead of sepolia ETH - We want to display live values on dashboard --- packages/web-app/app/_lib/actions.tsx | 1 - packages/web-app/app/_lib/queries.tsx | 40 ++++++++++ .../acquire-access-grant-button.tsx | 6 +- .../components/dialogs/apply-dialog/index.tsx | 2 +- .../dialogs/settings-dialog/balance.tsx | 17 ++-- .../app/_ui/components/wallet-button.tsx | 23 +++--- .../web-app/app/_ui/project/token-metrics.tsx | 80 +++++++++++++++---- yarn.lock | 5 -- 8 files changed, 127 insertions(+), 47 deletions(-) rename packages/web-app/app/_ui/components/dialogs/{ => apply-dialog}/acquire-access-grant-button.tsx (94%) diff --git a/packages/web-app/app/_lib/actions.tsx b/packages/web-app/app/_lib/actions.tsx index b9b809bb..28f41742 100644 --- a/packages/web-app/app/_lib/actions.tsx +++ b/packages/web-app/app/_lib/actions.tsx @@ -204,7 +204,6 @@ export const useSignDelegatedAccessGrant = ( }; export const useContributeToCtznd = (address: `0x${string}`) => { - const [state, setState] = useState(); const [amount, setAmount] = useState(0); const amountInWei = useMemo(() => parseEther(amount.toString()), [amount]); const { data: merkleProof } = useFetchMerkleProof(); diff --git a/packages/web-app/app/_lib/queries.tsx b/packages/web-app/app/_lib/queries.tsx index 57cc19be..af7e9cda 100644 --- a/packages/web-app/app/_lib/queries.tsx +++ b/packages/web-app/app/_lib/queries.tsx @@ -12,6 +12,9 @@ import { getServerPublicInfo, } from '../_server/info'; import { fetchAndGenerateProof } from '../_server/projects/generate-merkle-root'; +import { useAccount, useBalance } from 'wagmi'; +import { useReadCtzndSalePaymentToken } from '@/wagmi.generated'; +import { formatEther } from 'viem'; export const usePublicInfo = () => { return useQuery({ @@ -211,3 +214,40 @@ export const useFetchMerkleProof = () => { enabled: !!address, }); }; + +export const usePaymentTokenBalance = () => { + const { address } = useAccount(); + const { + data: paymentToken, + isLoading: isLoadingToken, + error: errorToken, + } = useReadCtzndSalePaymentToken(); + + const { + data: balance, + isLoading: isLoadingBalance, + error: errorBalance, + } = useBalance({ + token: paymentToken, + address, + query: { + enabled: !!paymentToken, + }, + }); + + if (!paymentToken) { + return { + data: null, + formattedValue: null, + isLoading: isLoadingToken, + error: errorToken, + }; + } + + return { + data: balance, + formattedValue: balance ? formatEther(balance.value) : null, + isLoading: isLoadingBalance || isLoadingToken, + error: errorToken || errorBalance, + }; +}; diff --git a/packages/web-app/app/_ui/components/dialogs/acquire-access-grant-button.tsx b/packages/web-app/app/_ui/components/dialogs/apply-dialog/acquire-access-grant-button.tsx similarity index 94% rename from packages/web-app/app/_ui/components/dialogs/acquire-access-grant-button.tsx rename to packages/web-app/app/_ui/components/dialogs/apply-dialog/acquire-access-grant-button.tsx index 3d6eecbf..d3e2942c 100644 --- a/packages/web-app/app/_ui/components/dialogs/acquire-access-grant-button.tsx +++ b/packages/web-app/app/_ui/components/dialogs/apply-dialog/acquire-access-grant-button.tsx @@ -4,9 +4,9 @@ import { useSignDelegatedAccessGrant } from '@/app/_lib/actions'; import { useTransaction } from 'wagmi'; import { useKyc } from '@/app/_providers/kyc/context'; import { useEffect } from 'react'; -import { Spinner } from '../svg/spinner'; -import { Check } from '../svg/check'; -import { Error } from '../svg/error'; +import { Spinner } from '../../svg/spinner'; +import { Check } from '../../svg/check'; +import { Error } from '../../svg/error'; import { arbitrum, arbitrumSepolia } from 'viem/chains'; type AcquireAccessGrantButton = { diff --git a/packages/web-app/app/_ui/components/dialogs/apply-dialog/index.tsx b/packages/web-app/app/_ui/components/dialogs/apply-dialog/index.tsx index f31b4fce..156622e3 100644 --- a/packages/web-app/app/_ui/components/dialogs/apply-dialog/index.tsx +++ b/packages/web-app/app/_ui/components/dialogs/apply-dialog/index.tsx @@ -3,7 +3,7 @@ import { useProjectPublicInfo, usePublicInfo } from '@/app/_lib/queries'; import { useIdOS } from '@/app/_providers/idos'; import { Dialog } from '@headlessui/react'; -import { AcquireAccessGrantButton } from '../acquire-access-grant-button'; +import { AcquireAccessGrantButton } from './acquire-access-grant-button'; import { Button } from '../..'; import { TProps, useDialog } from '@/app/_providers/dialog/context'; import { useMemo } from 'react'; diff --git a/packages/web-app/app/_ui/components/dialogs/settings-dialog/balance.tsx b/packages/web-app/app/_ui/components/dialogs/settings-dialog/balance.tsx index 7eb59cf6..85bf5f45 100644 --- a/packages/web-app/app/_ui/components/dialogs/settings-dialog/balance.tsx +++ b/packages/web-app/app/_ui/components/dialogs/settings-dialog/balance.tsx @@ -3,23 +3,22 @@ import { useAccount, useBalance } from 'wagmi'; import { Wallet } from '../../svg/wallet'; import { formatEther } from 'viem'; +import { usePaymentTokenBalance } from '@/app/_lib/queries'; export const Balance = () => { - const { address } = useAccount(); - const balance = useBalance({ - address: address, - blockTag: 'latest', - }); + const { data: balance, formattedValue } = usePaymentTokenBalance(); + + if (!balance) + return ( +
+ ); return ( <>

Balance

-

- {balance?.data?.formatted ? formatEther(balance?.data.value) : null}{' '} - {balance.data?.symbol} -

+

{`${formattedValue} ${balance.symbol}`}

); diff --git a/packages/web-app/app/_ui/components/wallet-button.tsx b/packages/web-app/app/_ui/components/wallet-button.tsx index 1902a9a7..4aee34b3 100644 --- a/packages/web-app/app/_ui/components/wallet-button.tsx +++ b/packages/web-app/app/_ui/components/wallet-button.tsx @@ -5,24 +5,25 @@ import { useDialog } from '@/app/_providers/dialog/context'; import { SettingsDialog } from './dialogs'; import { EdgeBorderButton, EdgeButton } from './edge'; import { Avatar } from './avatar'; -import { useAccount } from 'wagmi'; +import { useAccount, useBalance } from 'wagmi'; +import { formatEther } from 'viem'; +import { usePaymentTokenBalance } from '@/app/_lib/queries'; -type TConnectedButtonProps = { - displayBalance: string; -}; - -const ConnectedButton = ({ displayBalance }: TConnectedButtonProps) => { +const ConnectedButton = () => { + const { data: balance, formattedValue } = usePaymentTokenBalance(); const { open } = useDialog(); - // should show USDC balance in future, which can be same hook that displays it - // inside the settings tab + if (!balance) + return ( +
+ ); return ( } onClick={() => open(SettingsDialog.displayName)} > - {displayBalance} + {`${formattedValue} ${balance.symbol}`} ); }; @@ -61,9 +62,7 @@ export function WalletButton() { } if (account.displayBalance) { - return ( - - ); + return ; } return ( diff --git a/packages/web-app/app/_ui/project/token-metrics.tsx b/packages/web-app/app/_ui/project/token-metrics.tsx index 50b58e6f..f938ffe1 100644 --- a/packages/web-app/app/_ui/project/token-metrics.tsx +++ b/packages/web-app/app/_ui/project/token-metrics.tsx @@ -1,5 +1,11 @@ +import { + useReadCtzndSaleTokenToPaymentToken, + useReadCtzndSaleTotalUncappedAllocations, +} from '@/wagmi.generated'; import { usdRange } from '../utils/intl-formaters/usd-range'; import { usdValue } from '../utils/intl-formaters/usd-value'; +import { formatEther, parseEther } from 'viem'; +import clsx from 'clsx'; type TTokenMetricsProps = { minTarget: bigint; @@ -15,19 +21,20 @@ const ProgressBar = ({ value, }: { title: string; - max: bigint; - value: bigint; + max: number; + value: number; }) => { - // REVIEW and optimize this code - const valueInMillions = (BigInt(value) / BigInt(1000000n)).toString(); - const halfInMillions = ( - BigInt(max) / - BigInt(2) / - BigInt(1000000n) - ).toString(); - const maxInMillions = (BigInt(max) / BigInt(1000000n)).toString(); - const division = parseFloat(valueInMillions) / parseFloat(maxInMillions); - const percentage = `${Number(division) * 100}%`; + const valueInMillions = value / 1000_000; + const maxInMillions = max / 1000_000; + const halfInMillions = maxInMillions / 2; + const currentRelativeValue = valueInMillions / maxInMillions; + const percentage = currentRelativeValue * 100; + const displayPercentage = percentage > 0 && percentage < 1 ? 1 : percentage; + const percentageRounded = Math.round(displayPercentage) + '%'; + const displayValue = + valueInMillions > 0 && valueInMillions < 0.1 + ? '<0.1' + : valueInMillions.toFixed(2); return (
@@ -47,11 +54,26 @@ const ProgressBar = ({ aria-valuemax={Number(maxInMillions)} >
+
+ {percentageRounded === '100%' ? null : ( + <> +
+
+ {displayValue}M +
+ + )} +
{halfInMillions}M
@@ -66,6 +88,32 @@ const ProgressBar = ({ ); }; +const ProgressBarWrapper = ({ maxTarget }: { maxTarget: bigint }) => { + const { data: ctzndTokensSold, isLoading: isLoadingSaleTokens } = + useReadCtzndSaleTotalUncappedAllocations(); + const { data: tokensInvested, isLoading: isLoadingPaymentTokens } = + useReadCtzndSaleTokenToPaymentToken({ + args: [ctzndTokensSold || 0n], + }); + const value = tokensInvested ? formatEther(tokensInvested) : '0'; + + if (isLoadingSaleTokens || isLoadingPaymentTokens) + return ( + <> +
+
+ + ); + + return ( + + ); +}; + export const TokenMetrics = ({ minTarget, maxTarget, @@ -85,9 +133,9 @@ export const TokenMetrics = ({

Token Metrics

- {process.env.NEXT_PUBLIC_APPLY_OPEN === 'false' ? ( + {process.env.NEXT_PUBLIC_CONTRIBUTE_OPEN === 'true' ? (
- +
) : null}
diff --git a/yarn.lock b/yarn.lock index c86947cc..60d910e0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8764,11 +8764,6 @@ human-signals@^5.0.0: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-5.0.0.tgz#42665a284f9ae0dade3ba41ebc37eb4b852f3a28" integrity sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ== -husky@^7.0.2: - version "7.0.4" - resolved "https://registry.yarnpkg.com/husky/-/husky-7.0.4.tgz#242048245dc49c8fb1bf0cc7cfb98dd722531535" - integrity sha512-vbaCKN2QLtP/vD4yvs6iz6hBEo6wkSzs8HpRah1Z6aGmF2KW5PdYuAd7uX5a+OyBZHBhd+TFLqgjUgytQr4RvQ== - i18next-browser-languagedetector@^7.1.0: version "7.2.1" resolved "https://registry.yarnpkg.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-7.2.1.tgz#1968196d437b4c8db847410c7c33554f6c448f6f"