Turnstile improvement
This commit is contained in:
Vendored
+3
-2
@@ -10,7 +10,8 @@ export { Analytics } from '../modules/analytics/client.js';
|
||||
*/
|
||||
export { ConsentProvider, CookieBanner, CookieButton, useConsent, useConsentContext, } from '../modules/consent/client.js';
|
||||
export type { CookieBannerClassNames } from '../modules/consent/client.js';
|
||||
export { Turnstile } from '../modules/turnstile/client.js';
|
||||
export type { TurnstileProps } from '../modules/turnstile/client.js';
|
||||
export { resolveFormMessage } from '../modules/notifications/resolveFormMessage.js';
|
||||
export type { FormNotificationTexts } from '../modules/notifications/types.js';
|
||||
export { Turnstile } from '../modules/turnstile/client.js';
|
||||
export { TurnstileProvider, useTurnstile } from '../modules/turnstile/client.js';
|
||||
export type { TurnstileProps } from '../modules/turnstile/client.js';
|
||||
|
||||
Vendored
+2
-1
@@ -9,7 +9,8 @@ export { Analytics } from '../modules/analytics/client.js';
|
||||
* components. Kept separate from the main entry so server bundles don't pull in
|
||||
* client-only code.
|
||||
*/ export { ConsentProvider, CookieBanner, CookieButton, useConsent, useConsentContext } from '../modules/consent/client.js';
|
||||
export { Turnstile } from '../modules/turnstile/client.js';
|
||||
export { resolveFormMessage } from '../modules/notifications/resolveFormMessage.js';
|
||||
export { Turnstile } from '../modules/turnstile/client.js';
|
||||
export { TurnstileProvider, useTurnstile } from '../modules/turnstile/client.js';
|
||||
|
||||
//# sourceMappingURL=client.js.map
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
{"version":3,"sources":["../../src/exports/client.ts"],"sourcesContent":["'use client'\nexport { MaskedField } from '../globals/SiteIntegrations/components/MaskedField.js'\nexport { TestEmailButton } from '../globals/SiteIntegrations/components/TestEmailButton.js'\nexport { Analytics } from '../modules/analytics/client.js'\n/**\n * Entry point: ipal-kit/client\n *\n * Client-side ('use client') exports — React hooks, providers, and UI\n * components. Kept separate from the main entry so server bundles don't pull in\n * client-only code.\n */\nexport {\n ConsentProvider,\n CookieBanner,\n CookieButton,\n useConsent,\n useConsentContext,\n} from '../modules/consent/client.js'\nexport type { CookieBannerClassNames } from '../modules/consent/client.js'\nexport { Turnstile } from '../modules/turnstile/client.js'\nexport type { TurnstileProps } from '../modules/turnstile/client.js'\n\nexport { resolveFormMessage } from '../modules/notifications/resolveFormMessage.js'\nexport type { FormNotificationTexts } from '../modules/notifications/types.js'\n"],"names":["MaskedField","TestEmailButton","Analytics","ConsentProvider","CookieBanner","CookieButton","useConsent","useConsentContext","Turnstile","resolveFormMessage"],"mappings":"AAAA;AACA,SAASA,WAAW,QAAQ,wDAAuD;AACnF,SAASC,eAAe,QAAQ,4DAA2D;AAC3F,SAASC,SAAS,QAAQ,iCAAgC;AAC1D;;;;;;CAMC,GACD,SACEC,eAAe,EACfC,YAAY,EACZC,YAAY,EACZC,UAAU,EACVC,iBAAiB,QACZ,+BAA8B;AAErC,SAASC,SAAS,QAAQ,iCAAgC;AAG1D,SAASC,kBAAkB,QAAQ,iDAAgD"}
|
||||
{"version":3,"sources":["../../src/exports/client.ts"],"sourcesContent":["'use client'\nexport { MaskedField } from '../globals/SiteIntegrations/components/MaskedField.js'\nexport { TestEmailButton } from '../globals/SiteIntegrations/components/TestEmailButton.js'\nexport { Analytics } from '../modules/analytics/client.js'\n/**\n * Entry point: ipal-kit/client\n *\n * Client-side ('use client') exports — React hooks, providers, and UI\n * components. Kept separate from the main entry so server bundles don't pull in\n * client-only code.\n */\nexport {\n ConsentProvider,\n CookieBanner,\n CookieButton,\n useConsent,\n useConsentContext,\n} from '../modules/consent/client.js'\nexport type { CookieBannerClassNames } from '../modules/consent/client.js'\nexport { resolveFormMessage } from '../modules/notifications/resolveFormMessage.js'\nexport type { FormNotificationTexts } from '../modules/notifications/types.js'\nexport { Turnstile } from '../modules/turnstile/client.js'\nexport { TurnstileProvider, useTurnstile } from '../modules/turnstile/client.js'\nexport type { TurnstileProps } from '../modules/turnstile/client.js'\n"],"names":["MaskedField","TestEmailButton","Analytics","ConsentProvider","CookieBanner","CookieButton","useConsent","useConsentContext","resolveFormMessage","Turnstile","TurnstileProvider","useTurnstile"],"mappings":"AAAA;AACA,SAASA,WAAW,QAAQ,wDAAuD;AACnF,SAASC,eAAe,QAAQ,4DAA2D;AAC3F,SAASC,SAAS,QAAQ,iCAAgC;AAC1D;;;;;;CAMC,GACD,SACEC,eAAe,EACfC,YAAY,EACZC,YAAY,EACZC,UAAU,EACVC,iBAAiB,QACZ,+BAA8B;AAErC,SAASC,kBAAkB,QAAQ,iDAAgD;AAEnF,SAASC,SAAS,QAAQ,iCAAgC;AAC1D,SAASC,iBAAiB,EAAEC,YAAY,QAAQ,iCAAgC"}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* 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 declare function TurnstileProvider({ siteKey, children, }: {
|
||||
siteKey: string | null;
|
||||
children: React.ReactNode;
|
||||
}): import("react/jsx-runtime").JSX.Element;
|
||||
/**
|
||||
* 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 declare 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;
|
||||
};
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
'use client';
|
||||
import { jsx as _jsx } from "react/jsx-runtime";
|
||||
import { createContext, useCallback, useContext, useState } from 'react';
|
||||
import { Turnstile } from './Turnstile.js';
|
||||
const TurnstileContext = /*#__PURE__*/ createContext(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 }) {
|
||||
const [token, setToken] = useState(null);
|
||||
return /*#__PURE__*/ _jsx(TurnstileContext.Provider, {
|
||||
value: {
|
||||
siteKey,
|
||||
token,
|
||||
setToken
|
||||
},
|
||||
children: children
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 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() {
|
||||
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)=>{
|
||||
if (!siteKey) return null;
|
||||
return /*#__PURE__*/ _jsx(Turnstile, {
|
||||
siteKey: siteKey,
|
||||
onToken: setToken,
|
||||
theme: props?.theme
|
||||
});
|
||||
}, [
|
||||
siteKey,
|
||||
setToken
|
||||
]);
|
||||
return {
|
||||
token,
|
||||
TurnstileWidget,
|
||||
reset,
|
||||
enabled: Boolean(siteKey)
|
||||
};
|
||||
}
|
||||
|
||||
//# sourceMappingURL=TurnstileProvider.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../../src/modules/turnstile/TurnstileProvider.tsx"],"sourcesContent":["'use client'\n\nimport { createContext, useCallback, useContext, useState } from 'react'\nimport { Turnstile } from './Turnstile.js'\n\ntype TurnstileContextValue = {\n /** Public site key from the provider (read server-side, passed once). */\n siteKey: string | null\n /** Current token (null until solved / after expiry). */\n token: string | null\n setToken: (t: string | null) => void\n}\n\nconst TurnstileContext = createContext<TurnstileContextValue | null>(null)\n\n/**\n * Provides the Turnstile site key once for the whole app, like ConsentProvider\n * for cookies. The project reads the key server-side and passes it here in the\n * layout; forms then use <TurnstileWidget /> + useTurnstile() with NO per-form\n * key wiring.\n *\n * // layout.tsx (server) → read key, pass to provider\n * import { TurnstileProvider } from '@intecion/ipal-kit/client'\n * const siteKey = await getTurnstileSiteKey() // your server helper\n * <TurnstileProvider siteKey={siteKey}>{children}</TurnstileProvider>\n *\n * When siteKey is null (Turnstile not configured), widgets render nothing and\n * token stays null — forms should treat \"no Turnstile\" as allowed in dev.\n */\nexport function TurnstileProvider({\n siteKey,\n children,\n}: {\n siteKey: string | null\n children: React.ReactNode\n}) {\n const [token, setToken] = useState<string | null>(null)\n return (\n <TurnstileContext.Provider value={{ siteKey, token, setToken }}>\n {children}\n </TurnstileContext.Provider>\n )\n}\n\n/**\n * Hook giving a form the Turnstile token + a widget bound to the provider's key.\n * No per-form siteKey plumbing — the provider supplies it.\n *\n * const { token, TurnstileWidget, reset } = useTurnstile()\n * // in JSX: <TurnstileWidget />\n * // at submit: submitForm({ ..., turnstileToken: token })\n * // after submit: reset() // clear for the next submission\n */\nexport function useTurnstile(): {\n token: string | null\n TurnstileWidget: (props?: { theme?: 'light' | 'dark' | 'auto' }) => React.ReactNode\n reset: () => void\n /** True when Turnstile is configured (site key present). */\n enabled: boolean\n} {\n const ctx = useContext(TurnstileContext)\n if (!ctx) {\n throw new Error('useTurnstile must be used within <TurnstileProvider>')\n }\n const { siteKey, token, setToken } = ctx\n\n const reset = useCallback(() => setToken(null), [setToken])\n\n const TurnstileWidget = useCallback(\n (props?: { theme?: 'light' | 'dark' | 'auto' }) => {\n if (!siteKey) return null\n return <Turnstile siteKey={siteKey} onToken={setToken} theme={props?.theme} />\n },\n [siteKey, setToken],\n )\n\n return { token, TurnstileWidget, reset, enabled: Boolean(siteKey) }\n}\n"],"names":["createContext","useCallback","useContext","useState","Turnstile","TurnstileContext","TurnstileProvider","siteKey","children","token","setToken","Provider","value","useTurnstile","ctx","Error","reset","TurnstileWidget","props","onToken","theme","enabled","Boolean"],"mappings":"AAAA;;AAEA,SAASA,aAAa,EAAEC,WAAW,EAAEC,UAAU,EAAEC,QAAQ,QAAQ,QAAO;AACxE,SAASC,SAAS,QAAQ,iBAAgB;AAU1C,MAAMC,iCAAmBL,cAA4C;AAErE;;;;;;;;;;;;;CAaC,GACD,OAAO,SAASM,kBAAkB,EAChCC,OAAO,EACPC,QAAQ,EAIT;IACC,MAAM,CAACC,OAAOC,SAAS,GAAGP,SAAwB;IAClD,qBACE,KAACE,iBAAiBM,QAAQ;QAACC,OAAO;YAAEL;YAASE;YAAOC;QAAS;kBAC1DF;;AAGP;AAEA;;;;;;;;CAQC,GACD,OAAO,SAASK;IAOd,MAAMC,MAAMZ,WAAWG;IACvB,IAAI,CAACS,KAAK;QACR,MAAM,IAAIC,MAAM;IAClB;IACA,MAAM,EAAER,OAAO,EAAEE,KAAK,EAAEC,QAAQ,EAAE,GAAGI;IAErC,MAAME,QAAQf,YAAY,IAAMS,SAAS,OAAO;QAACA;KAAS;IAE1D,MAAMO,kBAAkBhB,YACtB,CAACiB;QACC,IAAI,CAACX,SAAS,OAAO;QACrB,qBAAO,KAACH;YAAUG,SAASA;YAASY,SAAST;YAAUU,OAAOF,OAAOE;;IACvE,GACA;QAACb;QAASG;KAAS;IAGrB,OAAO;QAAED;QAAOQ;QAAiBD;QAAOK,SAASC,QAAQf;IAAS;AACpE"}
|
||||
Vendored
+1
@@ -1,2 +1,3 @@
|
||||
export { Turnstile } from './Turnstile.js';
|
||||
export type { TurnstileProps } from './Turnstile.js';
|
||||
export { TurnstileProvider, useTurnstile } from './TurnstileProvider.js';
|
||||
|
||||
Vendored
+1
@@ -2,5 +2,6 @@
|
||||
// Client-only exports — the Turnstile widget. Kept separate from index.ts so
|
||||
// the server-only verify never leaks into a browser bundle.
|
||||
export { Turnstile } from './Turnstile.js';
|
||||
export { TurnstileProvider, useTurnstile } from './TurnstileProvider.js';
|
||||
|
||||
//# sourceMappingURL=client.js.map
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
{"version":3,"sources":["../../../src/modules/turnstile/client.ts"],"sourcesContent":["'use client'\n// Client-only exports — the Turnstile widget. Kept separate from index.ts so\n// the server-only verify never leaks into a browser bundle.\nexport { Turnstile } from './Turnstile.js'\nexport type { TurnstileProps } from './Turnstile.js'\n"],"names":["Turnstile"],"mappings":"AAAA;AACA,6EAA6E;AAC7E,4DAA4D;AAC5D,SAASA,SAAS,QAAQ,iBAAgB"}
|
||||
{"version":3,"sources":["../../../src/modules/turnstile/client.ts"],"sourcesContent":["'use client'\n// Client-only exports — the Turnstile widget. Kept separate from index.ts so\n// the server-only verify never leaks into a browser bundle.\nexport { Turnstile } from './Turnstile.js'\nexport type { TurnstileProps } from './Turnstile.js'\nexport { TurnstileProvider, useTurnstile } from './TurnstileProvider.js'\n"],"names":["Turnstile","TurnstileProvider","useTurnstile"],"mappings":"AAAA;AACA,6EAA6E;AAC7E,4DAA4D;AAC5D,SAASA,SAAS,QAAQ,iBAAgB;AAE1C,SAASC,iBAAiB,EAAEC,YAAY,QAAQ,yBAAwB"}
|
||||
Vendored
+1
@@ -1 +1,2 @@
|
||||
export { TurnstileProvider, useTurnstile } from './TurnstileProvider.js';
|
||||
export { verifyTurnstile } from './verify.js';
|
||||
|
||||
Vendored
+1
@@ -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';
|
||||
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
{"version":3,"sources":["../../../src/modules/turnstile/index.ts"],"sourcesContent":["// Server-only exports. verify.ts imports 'server-only', so this must never be\n// imported from a client component — use ./client for the widget instead.\nexport { verifyTurnstile } from './verify.js'\n"],"names":["verifyTurnstile"],"mappings":"AAAA,8EAA8E;AAC9E,0EAA0E;AAC1E,SAASA,eAAe,QAAQ,cAAa"}
|
||||
{"version":3,"sources":["../../../src/modules/turnstile/index.ts"],"sourcesContent":["export { TurnstileProvider, useTurnstile } from './TurnstileProvider.js'\n// Server-only exports. verify.ts imports 'server-only', so this must never be\n// imported from a client component — use ./client for the widget instead.\nexport { verifyTurnstile } from './verify.js'\n"],"names":["TurnstileProvider","useTurnstile","verifyTurnstile"],"mappings":"AAAA,SAASA,iBAAiB,EAAEC,YAAY,QAAQ,yBAAwB;AACxE,8EAA8E;AAC9E,0EAA0E;AAC1E,SAASC,eAAe,QAAQ,cAAa"}
|
||||
+70
-1
@@ -58,4 +58,73 @@ Zwraca `false` na każdy problem (brak klucza, sieć, odrzucenie) — traktuj
|
||||
trafi do bundla przeglądarki.
|
||||
|
||||
> W formularzach zwykle nie wołasz `verifyTurnstile` wprost — robi to
|
||||
> `submitForm` (patrz [forms.md](./forms.md)).
|
||||
> `submitForm` (patrz [forms.md](./forms.md)).
|
||||
|
||||
## Uproszczone wpięcie — TurnstileProvider + useTurnstile (zalecane)
|
||||
|
||||
Jak CookieBanner: siteKey raz w layoutcie, formularze biorą z kontekstu. Koniec
|
||||
przekazywania siteKey do każdego formularza.
|
||||
|
||||
### 1. Provider w layoutcie (raz, siteKey z serwera)
|
||||
|
||||
```tsx
|
||||
// app/(frontend)/[locale]/layout.tsx (server)
|
||||
import { TurnstileProvider } from '@intecion/ipal-kit/client'
|
||||
import { getTurnstileSiteKey } from '@/lib/payload' // Twój helper server-side
|
||||
|
||||
export default async function Layout({ children }) {
|
||||
const siteKey = await getTurnstileSiteKey() // z panelu (SiteIntegrations)
|
||||
return (
|
||||
<html>
|
||||
<body>
|
||||
<TurnstileProvider siteKey={siteKey}>
|
||||
{children}
|
||||
</TurnstileProvider>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Formularz — useTurnstile (zero plumbingu siteKey)
|
||||
|
||||
```tsx
|
||||
'use client'
|
||||
import { useTurnstile } from '@intecion/ipal-kit/client'
|
||||
|
||||
function ContactForm() {
|
||||
const { token, TurnstileWidget, reset, enabled } = useTurnstile()
|
||||
|
||||
async function handleSubmit(data) {
|
||||
const result = await submitForm({ ...data, turnstileToken: token })
|
||||
if (result.ok) reset() // wyczyść token na następne wysłanie
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={...}>
|
||||
{/* pola formularza */}
|
||||
<TurnstileWidget /> {/* widget tam, gdzie ma być */}
|
||||
<button type="submit">Wyślij</button>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
`token` → do submitForm. `TurnstileWidget` → wstaw gdzie ma być. `reset()` → po
|
||||
wysłaniu. `enabled` → false gdy brak klucza (dev bez Turnstile).
|
||||
|
||||
### Dlaczego Turnstile NIE jest w pełni "wstaw i zapomnij" jak CookieBanner
|
||||
|
||||
CookieBanner jest samodzielny (renderuje się, zarządza zgodą, zero interakcji).
|
||||
Turnstile z natury jest CZĘŚCIĄ formularza — zwraca token, który formularz musi
|
||||
wysłać przy submit i zweryfikować server-side. Nie da się go „wstawić
|
||||
gdziekolwiek" — musi być w formularzu, przy jego logice wysyłki.
|
||||
|
||||
Provider+hook to maksimum uproszczenia: siteKey raz (jak CookieBanner), a w
|
||||
formularzu tylko `<TurnstileWidget/>` + `token`. Reszta (weryfikacja) dzieje się
|
||||
w submitForm automatycznie.
|
||||
|
||||
### Stary sposób (nadal działa)
|
||||
|
||||
`<Turnstile siteKey={...} onToken={...} />` bezpośrednio — jeśli potrzebujesz
|
||||
pełnej kontroli albo masz nietypowy przypadek. Provider to warstwa wygody nad tym.
|
||||
@@ -17,8 +17,8 @@ export {
|
||||
useConsentContext,
|
||||
} from '../modules/consent/client.js'
|
||||
export type { CookieBannerClassNames } from '../modules/consent/client.js'
|
||||
export { Turnstile } from '../modules/turnstile/client.js'
|
||||
export type { TurnstileProps } from '../modules/turnstile/client.js'
|
||||
|
||||
export { resolveFormMessage } from '../modules/notifications/resolveFormMessage.js'
|
||||
export type { FormNotificationTexts } from '../modules/notifications/types.js'
|
||||
export { Turnstile } from '../modules/turnstile/client.js'
|
||||
export { TurnstileProvider, useTurnstile } from '../modules/turnstile/client.js'
|
||||
export type { TurnstileProps } from '../modules/turnstile/client.js'
|
||||
|
||||
@@ -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