@@ -3,12 +3,19 @@ import { Spinner } from "@/components/ui/Spinner/Spinner";
33import { Button } from "@/components/ui/button" ;
44import { DecimalInput } from "@/components/ui/decimal-input" ;
55import { Label } from "@/components/ui/label" ;
6+ import { Progress } from "@/components/ui/progress" ;
67import { SkeletonContainer } from "@/components/ui/skeleton" ;
78import { cn } from "@/lib/utils" ;
89import { useMutation , useQuery } from "@tanstack/react-query" ;
910import { TransactionButton } from "components/buttons/TransactionButton" ;
1011import { useTrack } from "hooks/analytics/useTrack" ;
11- import { CheckIcon , CircleIcon , ExternalLinkIcon , XIcon } from "lucide-react" ;
12+ import {
13+ CheckIcon ,
14+ CircleIcon ,
15+ ExternalLinkIcon ,
16+ InfinityIcon ,
17+ XIcon ,
18+ } from "lucide-react" ;
1219import { useTheme } from "next-themes" ;
1320import Link from "next/link" ;
1421import { useState } from "react" ;
@@ -31,7 +38,7 @@ import {
3138 useActiveWallet ,
3239 useSendTransaction ,
3340} from "thirdweb/react" ;
34- import { getClaimParams } from "thirdweb/utils" ;
41+ import { getClaimParams , maxUint256 } from "thirdweb/utils" ;
3542import { tryCatch } from "utils/try-catch" ;
3643import { getSDKTheme } from "../../../../../../../../components/sdk-component-theme" ;
3744import { PublicPageConnectButton } from "../../../_components/PublicPageConnectButton" ;
@@ -41,6 +48,11 @@ type ActiveClaimCondition = Awaited<ReturnType<typeof getActiveClaimCondition>>;
4148
4249// TODO UI improvements - show how many tokens connected wallet can claim at max
4350
51+ const compactNumberFormatter = new Intl . NumberFormat ( "en-US" , {
52+ notation : "compact" ,
53+ maximumFractionDigits : 10 ,
54+ } ) ;
55+
4456export function ClaimTokenCardUI ( props : {
4557 contract : ThirdwebContract ;
4658 name : string ;
@@ -304,7 +316,9 @@ export function ClaimTokenCardUI(props: {
304316 return (
305317 < div className = "rounded-xl border bg-card " >
306318 < div className = "border-b px-4 py-5 lg:px-5" >
307- < h2 className = "font-bold text-lg" > Buy { props . symbol } </ h2 >
319+ < h2 className = "font-semibold text-lg tracking-tight" >
320+ Buy { props . symbol }
321+ </ h2 >
308322 < p className = "text-muted-foreground text-sm" >
309323 Buy tokens from the primary sale
310324 </ p >
@@ -320,11 +334,18 @@ export function ClaimTokenCardUI(props: {
320334 id = "token-amount"
321335 symbol = { props . symbol }
322336 />
323- { /* <p className="text-xs text-muted-foreground">Maximum purchasable: {tokenData.maxPurchasable} tokens</p> */ }
324337 </ div >
325338
326339 < div className = "h-4" />
327340
341+ < SupplyRemaining
342+ supplyClaimed = { props . claimCondition . supplyClaimed }
343+ maxClaimableSupply = { props . claimCondition . maxClaimableSupply }
344+ decimals = { props . decimals }
345+ />
346+
347+ < div className = "h-4" />
348+
328349 < div className = "space-y-3 rounded-lg bg-muted/50 p-3" >
329350 { /* Price per token */ }
330351 < div className = "flex justify-between font-medium text-sm" >
@@ -426,6 +447,58 @@ export function ClaimTokenCardUI(props: {
426447 ) ;
427448}
428449
450+ function SupplyRemaining ( props : {
451+ supplyClaimed : bigint ;
452+ maxClaimableSupply : bigint ;
453+ decimals : number ;
454+ } ) {
455+ const isMaxClaimableSupplyUnlimited = props . maxClaimableSupply === maxUint256 ;
456+ const supplyClaimedTokenNumber = Number (
457+ toTokens ( props . supplyClaimed , props . decimals ) ,
458+ ) ;
459+
460+ // if there is unlimited supply - show many are claimed
461+ if ( isMaxClaimableSupplyUnlimited ) {
462+ return (
463+ < p className = "flex items-center justify-between gap-2" >
464+ < span className = "font-medium text-sm" > Supply Claimed</ span >
465+ < span className = "flex items-center gap-1 font-bold text-sm" >
466+ { compactNumberFormatter . format ( supplyClaimedTokenNumber ) } /{ " " }
467+ < InfinityIcon className = "size-4" aria-label = "Unlimited" />
468+ </ span >
469+ </ p >
470+ ) ;
471+ }
472+
473+ const maxClaimableSupplyTokenNumber = Number (
474+ toTokens ( props . maxClaimableSupply , props . decimals ) ,
475+ ) ;
476+
477+ const soldPercentage = isMaxClaimableSupplyUnlimited
478+ ? 0
479+ : ( supplyClaimedTokenNumber / maxClaimableSupplyTokenNumber ) * 100 ;
480+
481+ const supplyRemainingTokenNumber =
482+ maxClaimableSupplyTokenNumber - supplyClaimedTokenNumber ;
483+
484+ // else - show supply remaining
485+ return (
486+ < div className = "space-y-2" >
487+ < div className = "flex items-center justify-between" >
488+ < span className = "font-medium text-sm" > Supply Remaining</ span >
489+ < span className = "font-bold text-sm" >
490+ { compactNumberFormatter . format ( supplyRemainingTokenNumber ) } /{ " " }
491+ { compactNumberFormatter . format ( maxClaimableSupplyTokenNumber ) }
492+ </ span >
493+ </ div >
494+ < Progress value = { soldPercentage } className = "h-2.5" />
495+ < p className = "font-medium text-muted-foreground text-xs" >
496+ { soldPercentage . toFixed ( 1 ) } % Sold
497+ </ p >
498+ </ div >
499+ ) ;
500+ }
501+
429502type Status = "idle" | "pending" | "success" | "error" ;
430503
431504const statusToIcon : Record < Status , React . FC < { className : string } > > = {
@@ -472,7 +545,7 @@ function PriceInput(props: {
472545 className = "!text-2xl h-auto truncate bg-muted/50 pr-14 font-bold"
473546 />
474547 { props . symbol && (
475- < div className = "-translate-y-1/2 absolute top-1/2 right-4 font-semibold text-base text- muted-foreground" >
548+ < div className = "-translate-y-1/2 absolute top-1/2 right-3 font-medium text-muted-foreground text-sm " >
476549 { props . symbol }
477550 </ div >
478551 ) }
0 commit comments