11"use client" ;
22
3- import { useDisclosure } from "@chakra-ui/react" ;
3+ import {
4+ Sheet ,
5+ SheetContent ,
6+ SheetHeader ,
7+ SheetTitle ,
8+ SheetTrigger ,
9+ } from "@/components/ui/sheet" ;
10+ import { FormControl , Input } from "@chakra-ui/react" ;
11+ import { TransactionButton } from "components/buttons/TransactionButton" ;
12+ import { useTrack } from "hooks/analytics/useTrack" ;
413import { GemIcon } from "lucide-react" ;
5- import type { ThirdwebContract } from "thirdweb" ;
6- import { Button , Drawer } from "tw-components" ;
7- import { NFTClaimForm } from "./claim-form" ;
14+ import { useState } from "react" ;
15+ import { useForm } from "react-hook-form" ;
16+ import { toast } from "sonner" ;
17+ import { type ThirdwebContract , ZERO_ADDRESS } from "thirdweb" ;
18+ import { getApprovalForTransaction } from "thirdweb/extensions/erc20" ;
19+ import { claimTo } from "thirdweb/extensions/erc721" ;
20+ import { useActiveAccount , useSendAndConfirmTransaction } from "thirdweb/react" ;
21+ import { Button } from "tw-components" ;
22+ import { FormErrorMessage , FormHelperText , FormLabel } from "tw-components" ;
23+
24+ const CLAIM_FORM_ID = "nft-claim-form" ;
825
926interface NFTClaimButtonProps {
1027 contract : ThirdwebContract ;
@@ -15,26 +32,133 @@ interface NFTClaimButtonProps {
1532 * For Edition Drop we have a dedicated ClaimTabERC1155 inside each Edition's page
1633 */
1734export const NFTClaimButton : React . FC < NFTClaimButtonProps > = ( { contract } ) => {
18- const { isOpen, onOpen, onClose } = useDisclosure ( ) ;
35+ const trackEvent = useTrack ( ) ;
36+ const address = useActiveAccount ( ) ?. address ;
37+ const { register, handleSubmit, formState } = useForm ( {
38+ defaultValues : { amount : "1" , to : address } ,
39+ } ) ;
40+ const { errors } = formState ;
41+ const sendAndConfirmTx = useSendAndConfirmTransaction ( ) ;
42+ const account = useActiveAccount ( ) ;
43+ const [ open , setOpen ] = useState ( false ) ;
1944
2045 return (
21- < >
22- < Drawer
23- allowPinchZoom
24- preserveScrollBarGap
25- size = "lg"
26- onClose = { onClose }
27- isOpen = { isOpen }
28- >
29- < NFTClaimForm contract = { contract } />
30- </ Drawer >
31- < Button
32- colorScheme = "primary"
33- leftIcon = { < GemIcon className = "size-4" /> }
34- onClick = { onOpen }
35- >
36- Claim
37- </ Button >
38- </ >
46+ < Sheet open = { open } onOpenChange = { setOpen } >
47+ < SheetTrigger asChild >
48+ < Button colorScheme = "primary" leftIcon = { < GemIcon className = "size-4" /> } >
49+ Claim
50+ </ Button >
51+ </ SheetTrigger >
52+ < SheetContent className = "z-[10000] overflow-y-auto sm:w-[540px] sm:max-w-[90%] lg:w-[700px]" >
53+ < SheetHeader >
54+ < SheetTitle > Claim NFTs</ SheetTitle >
55+ </ SheetHeader >
56+ < form className = "mt-8 flex w-full flex-col gap-3 md:flex-row" >
57+ < div className = "flex w-full flex-col gap-6 md:flex-row" >
58+ < FormControl isRequired isInvalid = { ! ! errors . to } >
59+ < FormLabel > To Address</ FormLabel >
60+ < Input placeholder = { ZERO_ADDRESS } { ...register ( "to" ) } />
61+ < FormHelperText > Enter the address to claim to.</ FormHelperText >
62+ < FormErrorMessage > { errors . to ?. message } </ FormErrorMessage >
63+ </ FormControl >
64+ < FormControl isRequired isInvalid = { ! ! errors . amount } >
65+ < FormLabel > Amount</ FormLabel >
66+ < Input
67+ type = "text"
68+ { ...register ( "amount" , {
69+ validate : ( value ) => {
70+ const valueNum = Number ( value ) ;
71+ if ( ! Number . isInteger ( valueNum ) ) {
72+ return "Amount must be an integer" ;
73+ }
74+ } ,
75+ } ) }
76+ />
77+ < FormHelperText > How many would you like to claim?</ FormHelperText >
78+ < FormErrorMessage > { errors . amount ?. message } </ FormErrorMessage >
79+ </ FormControl >
80+ </ div >
81+ </ form >
82+ < div className = "mt-4 flex justify-end" >
83+ < TransactionButton
84+ txChainID = { contract . chain . id }
85+ transactionCount = { 1 }
86+ form = { CLAIM_FORM_ID }
87+ isLoading = { formState . isSubmitting }
88+ type = "submit"
89+ colorScheme = "primary"
90+ onClick = { handleSubmit ( async ( d ) => {
91+ trackEvent ( {
92+ category : "nft" ,
93+ action : "claim" ,
94+ label : "attempt" ,
95+ } ) ;
96+ if ( ! account ) {
97+ return toast . error ( "No account detected" ) ;
98+ }
99+ if ( ! d . to ) {
100+ return toast . error (
101+ "Please enter the address that will receive the NFT" ,
102+ ) ;
103+ }
104+
105+ const transaction = claimTo ( {
106+ contract,
107+ to : d . to ,
108+ quantity : BigInt ( d . amount ) ,
109+ from : account . address ,
110+ } ) ;
111+
112+ const approveTx = await getApprovalForTransaction ( {
113+ transaction,
114+ account,
115+ } ) ;
116+
117+ if ( approveTx ) {
118+ const promise = sendAndConfirmTx . mutateAsync ( approveTx , {
119+ onError : ( error ) => {
120+ console . error ( error ) ;
121+ } ,
122+ } ) ;
123+ toast . promise ( promise , {
124+ loading : "Approving ERC20 tokens for this claim" ,
125+ success : "Tokens approved successfully" ,
126+ error : "Failed to approve token" ,
127+ } ) ;
128+
129+ await promise ;
130+ }
131+
132+ const promise = sendAndConfirmTx . mutateAsync ( transaction , {
133+ onSuccess : ( ) => {
134+ trackEvent ( {
135+ category : "nft" ,
136+ action : "claim" ,
137+ label : "success" ,
138+ } ) ;
139+ setOpen ( false ) ;
140+ } ,
141+ onError : ( error ) => {
142+ trackEvent ( {
143+ category : "nft" ,
144+ action : "claim" ,
145+ label : "error" ,
146+ error,
147+ } ) ;
148+ } ,
149+ } ) ;
150+
151+ toast . promise ( promise , {
152+ loading : "Claiming NFT(s)" ,
153+ success : "NFT(s) claimed successfully" ,
154+ error : "Failed to claim NFT(s)" ,
155+ } ) ;
156+ } ) }
157+ >
158+ Claim NFT
159+ </ TransactionButton >
160+ </ div >
161+ </ SheetContent >
162+ </ Sheet >
39163 ) ;
40164} ;
0 commit comments