import { Turnstile, type TurnstileInstance } from "@marsidev/react-turnstile"; import { useCallback, useEffect, useRef, useState } from "react"; interface TurnstileWidgetProps { siteKey: string; action: string; onTokenChange: (token: string | null) => void; onUnavailable?: () => void; resetKey?: number; } // Fallback window for the Turnstile script + widget to mount and call // onWidgetLoad. If we never hear from the widget within this window we // surface unavailable so the parent form can show an actionable error // instead of an indefinite "Loading verification..." spinner. const LOAD_TIMEOUT_MS = 10_000; const TURNSTILE_OPTIONS = { appearance: "always" as const, refreshExpired: "auto" as const, refreshTimeout: "auto" as const, responseField: false, size: "flexible" as const, theme: "dark" as const, }; export const TurnstileWidget = ({ siteKey, action, onTokenChange, onUnavailable, resetKey }: TurnstileWidgetProps) => { const turnstileRef = useRef(null); const onTokenChangeRef = useRef(onTokenChange); const onUnavailableRef = useRef(onUnavailable); useEffect(() => { onTokenChangeRef.current = onTokenChange; onUnavailableRef.current = onUnavailable; }); const [loaded, setLoaded] = useState(false); const handleUnavailable = useCallback(() => { onTokenChangeRef.current(null); onUnavailableRef.current?.(); }, []); const handleTokenCleared = useCallback(() => { onTokenChangeRef.current(null); }, []); const handleWidgetLoad = useCallback(() => { setLoaded(true); }, []); // Don't pass scriptOptions.onError: @marsidev/react-turnstile can delete // window.onloadTurnstileCallback when an onError handler is provided, // racing Cloudflare api.js and producing a missing-callback console // error. The load-fallback timeout below covers the same // script-failed-to-load case at a UX level. useEffect(() => { if (loaded) return; const timer = window.setTimeout(() => { handleUnavailable(); }, LOAD_TIMEOUT_MS); return () => window.clearTimeout(timer); }, [loaded, handleUnavailable]); useEffect(() => { onTokenChangeRef.current(null); turnstileRef.current?.reset(); }, [resetKey]); return ( ); };