Turnstile improvement
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
'use client'
|
||||
|
||||
import { createContext, useCallback, useContext, useState } from 'react'
|
||||
import { Turnstile } from './Turnstile.js'
|
||||
|
||||
type TurnstileContextValue = {
|
||||
/** Public site key from the provider (read server-side, passed once). */
|
||||
siteKey: string | null
|
||||
/** Current token (null until solved / after expiry). */
|
||||
token: string | null
|
||||
setToken: (t: string | null) => void
|
||||
}
|
||||
|
||||
const TurnstileContext = createContext<TurnstileContextValue | null>(null)
|
||||
|
||||
/**
|
||||
* Provides the Turnstile site key once for the whole app, like ConsentProvider
|
||||
* for cookies. The project reads the key server-side and passes it here in the
|
||||
* layout; forms then use <TurnstileWidget /> + useTurnstile() with NO per-form
|
||||
* key wiring.
|
||||
*
|
||||
* // layout.tsx (server) → read key, pass to provider
|
||||
* import { TurnstileProvider } from '@intecion/ipal-kit/client'
|
||||
* const siteKey = await getTurnstileSiteKey() // your server helper
|
||||
* <TurnstileProvider siteKey={siteKey}>{children}</TurnstileProvider>
|
||||
*
|
||||
* When siteKey is null (Turnstile not configured), widgets render nothing and
|
||||
* token stays null — forms should treat "no Turnstile" as allowed in dev.
|
||||
*/
|
||||
export function TurnstileProvider({
|
||||
siteKey,
|
||||
children,
|
||||
}: {
|
||||
siteKey: string | null
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
const [token, setToken] = useState<string | null>(null)
|
||||
return (
|
||||
<TurnstileContext.Provider value={{ siteKey, token, setToken }}>
|
||||
{children}
|
||||
</TurnstileContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook giving a form the Turnstile token + a widget bound to the provider's key.
|
||||
* No per-form siteKey plumbing — the provider supplies it.
|
||||
*
|
||||
* const { token, TurnstileWidget, reset } = useTurnstile()
|
||||
* // in JSX: <TurnstileWidget />
|
||||
* // at submit: submitForm({ ..., turnstileToken: token })
|
||||
* // after submit: reset() // clear for the next submission
|
||||
*/
|
||||
export function useTurnstile(): {
|
||||
token: string | null
|
||||
TurnstileWidget: (props?: { theme?: 'light' | 'dark' | 'auto' }) => React.ReactNode
|
||||
reset: () => void
|
||||
/** True when Turnstile is configured (site key present). */
|
||||
enabled: boolean
|
||||
} {
|
||||
const ctx = useContext(TurnstileContext)
|
||||
if (!ctx) {
|
||||
throw new Error('useTurnstile must be used within <TurnstileProvider>')
|
||||
}
|
||||
const { siteKey, token, setToken } = ctx
|
||||
|
||||
const reset = useCallback(() => setToken(null), [setToken])
|
||||
|
||||
const TurnstileWidget = useCallback(
|
||||
(props?: { theme?: 'light' | 'dark' | 'auto' }) => {
|
||||
if (!siteKey) return null
|
||||
return <Turnstile siteKey={siteKey} onToken={setToken} theme={props?.theme} />
|
||||
},
|
||||
[siteKey, setToken],
|
||||
)
|
||||
|
||||
return { token, TurnstileWidget, reset, enabled: Boolean(siteKey) }
|
||||
}
|
||||
@@ -3,3 +3,4 @@
|
||||
// the server-only verify never leaks into a browser bundle.
|
||||
export { Turnstile } from './Turnstile.js'
|
||||
export type { TurnstileProps } from './Turnstile.js'
|
||||
export { TurnstileProvider, useTurnstile } from './TurnstileProvider.js'
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export { TurnstileProvider, useTurnstile } from './TurnstileProvider.js'
|
||||
// Server-only exports. verify.ts imports 'server-only', so this must never be
|
||||
// imported from a client component — use ./client for the widget instead.
|
||||
export { verifyTurnstile } from './verify.js'
|
||||
|
||||
Reference in New Issue
Block a user