33import Image from 'next/image'
44import { useSearchParams } from 'next/navigation'
55import { useSession , signIn } from 'next-auth/react'
6- import { Suspense } from 'react'
6+ import { Suspense , useCallback , useRef , useState } from 'react'
77
88import { SignInCardFooter } from '@/components/sign-in/sign-in-card-footer'
9+ import { TurnstileWidget } from '@/components/turnstile-widget'
910import { Button } from '@/components/ui/button'
1011import {
1112 Card ,
@@ -15,9 +16,48 @@ import {
1516 CardFooter ,
1617} from '@/components/ui/card'
1718
19+ const TURNSTILE_SITE_KEY = process . env . NEXT_PUBLIC_TURNSTILE_SITE_KEY
20+
1821export function LoginCard ( { authCode } : { authCode ?: string | null } ) {
1922 const { data : session } = useSession ( )
2023 const searchParams = useSearchParams ( ) ?? new URLSearchParams ( )
24+ const [ turnstileVerified , setTurnstileVerified ] = useState (
25+ ! TURNSTILE_SITE_KEY ,
26+ )
27+ const [ turnstileError , setTurnstileError ] = useState < string | null > ( null )
28+ const turnstileErrorShownRef = useRef ( false )
29+
30+ const handleTurnstileVerify = useCallback ( async ( token : string ) => {
31+ try {
32+ const response = await fetch ( '/api/auth/verify-turnstile' , {
33+ method : 'POST' ,
34+ headers : { 'Content-Type' : 'application/json' } ,
35+ body : JSON . stringify ( { token } ) ,
36+ } )
37+ const result = await response . json ( )
38+ if ( result . success ) {
39+ setTurnstileVerified ( true )
40+ setTurnstileError ( null )
41+ turnstileErrorShownRef . current = false
42+ } else {
43+ setTurnstileError ( 'Verification failed. Please refresh and try again.' )
44+ }
45+ } catch {
46+ setTurnstileError ( 'Verification failed. Please refresh and try again.' )
47+ }
48+ } , [ ] )
49+
50+ const handleTurnstileError = useCallback ( ( errorCode : string ) => {
51+ console . error ( 'Turnstile error:' , errorCode )
52+ if ( ! turnstileErrorShownRef . current ) {
53+ turnstileErrorShownRef . current = true
54+ setTurnstileError ( 'Verification error. Please refresh and try again.' )
55+ }
56+ } , [ ] )
57+
58+ const handleTurnstileExpired = useCallback ( ( ) => {
59+ setTurnstileVerified ( false )
60+ } , [ ] )
2161
2262 const handleContinueAsUser = ( ) => {
2363 const referralCode = searchParams . get ( 'referral_code' )
@@ -129,7 +169,22 @@ export function LoginCard({ authCode }: { authCode?: string | null }) {
129169 </ CardFooter >
130170 </ >
131171 ) : (
132- < SignInCardFooter />
172+ < >
173+ { TURNSTILE_SITE_KEY && (
174+ < CardContent className = "flex flex-col items-center gap-2" >
175+ < TurnstileWidget
176+ siteKey = { TURNSTILE_SITE_KEY }
177+ onVerify = { handleTurnstileVerify }
178+ onError = { handleTurnstileError }
179+ onExpired = { handleTurnstileExpired }
180+ />
181+ { turnstileError && (
182+ < p className = "text-sm text-red-400" > { turnstileError } </ p >
183+ ) }
184+ </ CardContent >
185+ ) }
186+ < SignInCardFooter disabled = { ! turnstileVerified } />
187+ </ >
133188 ) }
134189 </ Card >
135190 </ Suspense >
0 commit comments