@@ -3,12 +3,19 @@ import { Spinner } from "@/components/ui/Spinner/Spinner";
3
3
import { Button } from "@/components/ui/button" ;
4
4
import { DecimalInput } from "@/components/ui/decimal-input" ;
5
5
import { Label } from "@/components/ui/label" ;
6
+ import { Progress } from "@/components/ui/progress" ;
6
7
import { SkeletonContainer } from "@/components/ui/skeleton" ;
7
8
import { cn } from "@/lib/utils" ;
8
9
import { useMutation , useQuery } from "@tanstack/react-query" ;
9
10
import { TransactionButton } from "components/buttons/TransactionButton" ;
10
11
import { 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" ;
12
19
import { useTheme } from "next-themes" ;
13
20
import Link from "next/link" ;
14
21
import { useState } from "react" ;
@@ -31,7 +38,7 @@ import {
31
38
useActiveWallet ,
32
39
useSendTransaction ,
33
40
} from "thirdweb/react" ;
34
- import { getClaimParams } from "thirdweb/utils" ;
41
+ import { getClaimParams , maxUint256 } from "thirdweb/utils" ;
35
42
import { tryCatch } from "utils/try-catch" ;
36
43
import { getSDKTheme } from "../../../../../../../../components/sdk-component-theme" ;
37
44
import { PublicPageConnectButton } from "../../../_components/PublicPageConnectButton" ;
@@ -41,6 +48,11 @@ type ActiveClaimCondition = Awaited<ReturnType<typeof getActiveClaimCondition>>;
41
48
42
49
// TODO UI improvements - show how many tokens connected wallet can claim at max
43
50
51
+ const compactNumberFormatter = new Intl . NumberFormat ( "en-US" , {
52
+ notation : "compact" ,
53
+ maximumFractionDigits : 10 ,
54
+ } ) ;
55
+
44
56
export function ClaimTokenCardUI ( props : {
45
57
contract : ThirdwebContract ;
46
58
name : string ;
@@ -304,7 +316,9 @@ export function ClaimTokenCardUI(props: {
304
316
return (
305
317
< div className = "rounded-xl border bg-card " >
306
318
< 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 >
308
322
< p className = "text-muted-foreground text-sm" >
309
323
Buy tokens from the primary sale
310
324
</ p >
@@ -320,11 +334,18 @@ export function ClaimTokenCardUI(props: {
320
334
id = "token-amount"
321
335
symbol = { props . symbol }
322
336
/>
323
- { /* <p className="text-xs text-muted-foreground">Maximum purchasable: {tokenData.maxPurchasable} tokens</p> */ }
324
337
</ div >
325
338
326
339
< div className = "h-4" />
327
340
341
+ < SupplyRemaining
342
+ supplyClaimed = { props . claimCondition . supplyClaimed }
343
+ maxClaimableSupply = { props . claimCondition . maxClaimableSupply }
344
+ decimals = { props . decimals }
345
+ />
346
+
347
+ < div className = "h-4" />
348
+
328
349
< div className = "space-y-3 rounded-lg bg-muted/50 p-3" >
329
350
{ /* Price per token */ }
330
351
< div className = "flex justify-between font-medium text-sm" >
@@ -426,6 +447,58 @@ export function ClaimTokenCardUI(props: {
426
447
) ;
427
448
}
428
449
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
+
429
502
type Status = "idle" | "pending" | "success" | "error" ;
430
503
431
504
const statusToIcon : Record < Status , React . FC < { className : string } > > = {
@@ -472,7 +545,7 @@ function PriceInput(props: {
472
545
className = "!text-2xl h-auto truncate bg-muted/50 pr-14 font-bold"
473
546
/>
474
547
{ 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 " >
476
549
{ props . symbol }
477
550
</ div >
478
551
) }
0 commit comments