From 05f65ec150fd343b72e2fb277a3c4e304aa93287 Mon Sep 17 00:00:00 2001 From: MananTank Date: Thu, 5 Jun 2025 18:44:04 +0000 Subject: [PATCH] [TOOL-4686] Dashboard: Add supply remaining progress in asset page claim tokens card (#7283) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ## PR-Codex overview This PR enhances the `ClaimTokenCardUI` component by adding a new `SupplyRemaining` feature, which displays the supply of tokens claimed and remaining. It also improves the UI with a progress bar and formatting adjustments. ### Detailed summary - Added `Progress` component for visual representation of supply sold. - Introduced `SupplyRemaining` function to show claimed and remaining supply. - Formatted supply numbers using `compactNumberFormatter`. - Updated headings and styling for better UI consistency. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` ## Summary by CodeRabbit - **New Features** - Added a visual display showing claimed versus total claimable token supply, including a progress bar and percentage sold. - **Style** - Improved typography and spacing for headings and token symbol labels for better visual consistency. --- .../claim-tokens/claim-tokens-ui.tsx | 83 +++++++++++++++++-- 1 file changed, 78 insertions(+), 5 deletions(-) diff --git a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/_components/claim-tokens/claim-tokens-ui.tsx b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/_components/claim-tokens/claim-tokens-ui.tsx index b27e6187c55..baf972ae576 100644 --- a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/_components/claim-tokens/claim-tokens-ui.tsx +++ b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/_components/claim-tokens/claim-tokens-ui.tsx @@ -3,12 +3,19 @@ import { Spinner } from "@/components/ui/Spinner/Spinner"; import { Button } from "@/components/ui/button"; import { DecimalInput } from "@/components/ui/decimal-input"; import { Label } from "@/components/ui/label"; +import { Progress } from "@/components/ui/progress"; import { SkeletonContainer } from "@/components/ui/skeleton"; import { cn } from "@/lib/utils"; import { useMutation, useQuery } from "@tanstack/react-query"; import { TransactionButton } from "components/buttons/TransactionButton"; import { useTrack } from "hooks/analytics/useTrack"; -import { CheckIcon, CircleIcon, ExternalLinkIcon, XIcon } from "lucide-react"; +import { + CheckIcon, + CircleIcon, + ExternalLinkIcon, + InfinityIcon, + XIcon, +} from "lucide-react"; import { useTheme } from "next-themes"; import Link from "next/link"; import { useState } from "react"; @@ -31,7 +38,7 @@ import { useActiveWallet, useSendTransaction, } from "thirdweb/react"; -import { getClaimParams } from "thirdweb/utils"; +import { getClaimParams, maxUint256 } from "thirdweb/utils"; import { tryCatch } from "utils/try-catch"; import { getSDKTheme } from "../../../../../../../../components/sdk-component-theme"; import { PublicPageConnectButton } from "../../../_components/PublicPageConnectButton"; @@ -41,6 +48,11 @@ type ActiveClaimCondition = Awaited>; // TODO UI improvements - show how many tokens connected wallet can claim at max +const compactNumberFormatter = new Intl.NumberFormat("en-US", { + notation: "compact", + maximumFractionDigits: 10, +}); + export function ClaimTokenCardUI(props: { contract: ThirdwebContract; name: string; @@ -304,7 +316,9 @@ export function ClaimTokenCardUI(props: { return (
-

Buy {props.symbol}

+

+ Buy {props.symbol} +

Buy tokens from the primary sale

@@ -320,11 +334,18 @@ export function ClaimTokenCardUI(props: { id="token-amount" symbol={props.symbol} /> - {/*

Maximum purchasable: {tokenData.maxPurchasable} tokens

*/}
+ + +
+
{/* Price per token */}
@@ -426,6 +447,58 @@ export function ClaimTokenCardUI(props: { ); } +function SupplyRemaining(props: { + supplyClaimed: bigint; + maxClaimableSupply: bigint; + decimals: number; +}) { + const isMaxClaimableSupplyUnlimited = props.maxClaimableSupply === maxUint256; + const supplyClaimedTokenNumber = Number( + toTokens(props.supplyClaimed, props.decimals), + ); + + // if there is unlimited supply - show many are claimed + if (isMaxClaimableSupplyUnlimited) { + return ( +

+ Supply Claimed + + {compactNumberFormatter.format(supplyClaimedTokenNumber)} /{" "} + + +

+ ); + } + + const maxClaimableSupplyTokenNumber = Number( + toTokens(props.maxClaimableSupply, props.decimals), + ); + + const soldPercentage = isMaxClaimableSupplyUnlimited + ? 0 + : (supplyClaimedTokenNumber / maxClaimableSupplyTokenNumber) * 100; + + const supplyRemainingTokenNumber = + maxClaimableSupplyTokenNumber - supplyClaimedTokenNumber; + + // else - show supply remaining + return ( +
+
+ Supply Remaining + + {compactNumberFormatter.format(supplyRemainingTokenNumber)} /{" "} + {compactNumberFormatter.format(maxClaimableSupplyTokenNumber)} + +
+ +

+ {soldPercentage.toFixed(1)}% Sold +

+
+ ); +} + type Status = "idle" | "pending" | "success" | "error"; const statusToIcon: Record> = { @@ -472,7 +545,7 @@ function PriceInput(props: { className="!text-2xl h-auto truncate bg-muted/50 pr-14 font-bold" /> {props.symbol && ( -
+
{props.symbol}
)}