11"use client" ;
2+ import { CopyTextButton } from "@/components/ui/CopyTextButton" ;
23import { Spinner } from "@/components/ui/Spinner/Spinner" ;
34import { Button } from "@/components/ui/button" ;
4- import { Form , FormControl , FormField , FormItem } from "@/components/ui/form" ;
5+ import {
6+ Dialog ,
7+ DialogContent ,
8+ DialogDescription ,
9+ DialogHeader ,
10+ DialogTitle ,
11+ DialogTrigger ,
12+ } from "@/components/ui/dialog" ;
13+ import {
14+ Form ,
15+ FormControl ,
16+ FormField ,
17+ FormItem ,
18+ FormLabel ,
19+ FormMessage ,
20+ } from "@/components/ui/form" ;
21+ import { Input } from "@/components/ui/input" ;
522import {
623 THIRDWEB_ENGINE_FAUCET_WALLET ,
724 TURNSTILE_SITE_KEY ,
825} from "@/constants/env" ;
926import { useThirdwebClient } from "@/constants/thirdweb.client" ;
27+ import { CustomConnectWallet } from "@3rdweb-sdk/react/components/connect-wallet" ;
1028import type { Account } from "@3rdweb-sdk/react/hooks/useApi" ;
29+ import { zodResolver } from "@hookform/resolvers/zod" ;
1130import { Turnstile } from "@marsidev/react-turnstile" ;
1231import { useMutation , useQuery , useQueryClient } from "@tanstack/react-query" ;
1332import type { CanClaimResponseType } from "app/api/testnet-faucet/can-claim/CanClaimResponseType" ;
@@ -17,9 +36,21 @@ import Link from "next/link";
1736import { usePathname } from "next/navigation" ;
1837import { useForm } from "react-hook-form" ;
1938import { toast } from "sonner" ;
20- import { toUnits } from "thirdweb" ;
39+ import {
40+ type ThirdwebClient ,
41+ prepareTransaction ,
42+ toUnits ,
43+ toWei ,
44+ } from "thirdweb" ;
2145import type { ChainMetadata } from "thirdweb/chains" ;
22- import { useActiveAccount , useWalletBalance } from "thirdweb/react" ;
46+ import type { Chain } from "thirdweb/chains" ;
47+ import {
48+ useActiveAccount ,
49+ useActiveWalletChain ,
50+ useSendTransaction ,
51+ useSwitchActiveWalletChain ,
52+ useWalletBalance ,
53+ } from "thirdweb/react" ;
2354import { z } from "zod" ;
2455import { isOnboardingComplete } from "../../../../../../login/onboarding/isOnboardingRequired" ;
2556
@@ -137,6 +168,35 @@ export function FaucetButton({
137168
138169 const form = useForm < z . infer < typeof claimFaucetSchema > > ( ) ;
139170
171+ // loading state
172+ if ( faucetWalletBalanceQuery . isPending || canClaimFaucetQuery . isPending ) {
173+ return (
174+ < Button variant = "outline" className = "w-full gap-2" >
175+ Checking Faucet < Spinner className = "size-3" />
176+ </ Button >
177+ ) ;
178+ }
179+
180+ // faucet is empty
181+ if ( isFaucetEmpty ) {
182+ return (
183+ < div className = "w-full" >
184+ < div className = "mb-3 text-center text-muted-foreground text-sm" >
185+ Faucet is empty right now
186+ </ div >
187+ < SendFundsToFaucetModalButton
188+ chain = { definedChain }
189+ isLoggedIn = { ! ! twAccount }
190+ client = { client }
191+ chainMeta = { chain }
192+ onFaucetRefill = { ( ) => {
193+ faucetWalletBalanceQuery . refetch ( ) ;
194+ } }
195+ />
196+ </ div >
197+ ) ;
198+ }
199+
140200 // Force users to log in to claim the faucet
141201 if ( ! address || ! twAccount ) {
142202 return (
@@ -164,24 +224,6 @@ export function FaucetButton({
164224 ) ;
165225 }
166226
167- // loading state
168- if ( faucetWalletBalanceQuery . isPending || canClaimFaucetQuery . isPending ) {
169- return (
170- < Button variant = "outline" className = "w-full gap-2" >
171- Checking Faucet < Spinner className = "size-3" />
172- </ Button >
173- ) ;
174- }
175-
176- // faucet is empty
177- if ( isFaucetEmpty ) {
178- return (
179- < Button variant = "outline" disabled className = "!opacity-100 w-full" >
180- Faucet is empty right now
181- </ Button >
182- ) ;
183- }
184-
185227 // Can not claim
186228 if ( canClaimFaucetQuery . data && canClaimFaucetQuery . data . canClaim === false ) {
187229 return (
@@ -250,3 +292,166 @@ export function FaucetButton({
250292 </ div >
251293 ) ;
252294}
295+
296+ const faucetFormSchema = z . object ( {
297+ amount : z . coerce . number ( ) . refine ( ( value ) => value > 0 , {
298+ message : "Amount must be greater than 0" ,
299+ } ) ,
300+ } ) ;
301+
302+ function SendFundsToFaucetModalButton ( props : {
303+ chain : Chain ;
304+ isLoggedIn : boolean ;
305+ client : ThirdwebClient ;
306+ chainMeta : ChainMetadata ;
307+ onFaucetRefill : ( ) => void ;
308+ } ) {
309+ return (
310+ < Dialog >
311+ < DialogTrigger asChild >
312+ < Button variant = "default" className = "w-full" >
313+ Refill Faucet
314+ </ Button >
315+ </ DialogTrigger >
316+ < DialogContent >
317+ < DialogHeader className = "mb-2" >
318+ < DialogTitle > Refill Faucet</ DialogTitle >
319+ < DialogDescription > Send funds to faucet wallet</ DialogDescription >
320+ </ DialogHeader >
321+
322+ < SendFundsToFaucetModalContent { ...props } />
323+ </ DialogContent >
324+ </ Dialog >
325+ ) ;
326+ }
327+
328+ function SendFundsToFaucetModalContent ( props : {
329+ chain : Chain ;
330+ isLoggedIn : boolean ;
331+ client : ThirdwebClient ;
332+ chainMeta : ChainMetadata ;
333+ onFaucetRefill : ( ) => void ;
334+ } ) {
335+ const account = useActiveAccount ( ) ;
336+ const activeChain = useActiveWalletChain ( ) ;
337+ const switchActiveWalletChain = useSwitchActiveWalletChain ( ) ;
338+ const sendTxMutation = useSendTransaction ( {
339+ payModal : false ,
340+ } ) ;
341+ const switchChainMutation = useMutation ( {
342+ mutationFn : async ( ) => {
343+ await switchActiveWalletChain ( props . chain ) ;
344+ } ,
345+ } ) ;
346+
347+ // 1. Define your form.
348+ const form = useForm < z . infer < typeof faucetFormSchema > > ( {
349+ resolver : zodResolver ( faucetFormSchema ) ,
350+ defaultValues : {
351+ amount : 0.1 ,
352+ } ,
353+ } ) ;
354+
355+ function onSubmit ( values : z . infer < typeof faucetFormSchema > ) {
356+ const sendNativeTokenTx = prepareTransaction ( {
357+ chain : props . chain ,
358+ client : props . client ,
359+ to : THIRDWEB_ENGINE_FAUCET_WALLET ,
360+ value : toWei ( values . amount . toString ( ) ) ,
361+ } ) ;
362+
363+ const promise = sendTxMutation . mutateAsync ( sendNativeTokenTx ) ;
364+
365+ toast . promise ( promise , {
366+ success : `Sent ${ values . amount } ${ props . chainMeta . nativeCurrency . symbol } to faucet` ,
367+ error : `Failed to send ${ values . amount } ${ props . chainMeta . nativeCurrency . symbol } to faucet` ,
368+ } ) ;
369+
370+ promise . then ( ( ) => {
371+ props . onFaucetRefill ( ) ;
372+ } ) ;
373+ }
374+
375+ return (
376+ < Form { ...form } >
377+ < form
378+ onSubmit = { form . handleSubmit ( onSubmit ) }
379+ className = "flex min-w-0 flex-col gap-5"
380+ >
381+ < div className = "min-w-0" >
382+ < p className = "mb-2 text-foreground text-sm" > Faucet Wallet </ p >
383+ < CopyTextButton
384+ copyIconPosition = "right"
385+ variant = "outline"
386+ className = "w-full justify-between bg-card py-2 font-mono"
387+ textToCopy = { THIRDWEB_ENGINE_FAUCET_WALLET }
388+ textToShow = { THIRDWEB_ENGINE_FAUCET_WALLET }
389+ tooltip = { undefined }
390+ />
391+ </ div >
392+
393+ < FormField
394+ control = { form . control }
395+ name = "amount"
396+ render = { ( { field } ) => (
397+ < FormItem >
398+ < FormLabel > Amount</ FormLabel >
399+ < FormControl >
400+ < div className = "relative" >
401+ < Input
402+ { ...field }
403+ type = "number"
404+ className = "h-auto bg-card text-2xl md:text-2xl [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none"
405+ />
406+ < div className = "-translate-y-1/2 absolute top-1/2 right-4 text-muted-foreground text-sm" >
407+ { props . chainMeta . nativeCurrency . symbol }
408+ </ div >
409+ </ div >
410+ </ FormControl >
411+
412+ < FormMessage />
413+ </ FormItem >
414+ ) }
415+ />
416+
417+ { ! account && (
418+ < CustomConnectWallet
419+ chain = { props . chain }
420+ loginRequired = { false }
421+ isLoggedIn = { props . isLoggedIn }
422+ connectButtonClassName = "!w-full"
423+ detailsButtonClassName = "!w-full"
424+ />
425+ ) }
426+
427+ { account && activeChain && (
428+ < div >
429+ { activeChain . id === props . chain . id ? (
430+ < Button
431+ key = "submit"
432+ type = "submit"
433+ className = "mt-4 w-full gap-2"
434+ disabled = { sendTxMutation . isPending }
435+ >
436+ { sendTxMutation . isPending && < Spinner className = "size-4" /> }
437+ Send funds to faucet
438+ </ Button >
439+ ) : (
440+ < Button
441+ key = "switch"
442+ className = "mt-4 w-full gap-2"
443+ disabled = { switchChainMutation . isPending }
444+ onClick = { ( ) => switchChainMutation . mutate ( ) }
445+ >
446+ Switch to { props . chainMeta . name } { " " }
447+ { switchChainMutation . isPending && (
448+ < Spinner className = "size-4" />
449+ ) }
450+ </ Button >
451+ ) }
452+ </ div >
453+ ) }
454+ </ form >
455+ </ Form >
456+ ) ;
457+ }
0 commit comments