# turnstile Cloudflare Turnstile: widget (client) + weryfikacja (server). Klucze z SiteIntegrations (panel, nie env). Widget dostaje `siteKey` jako prop; verify czyta secret server-side. ## Config Brak opcji — pola `turnstileSiteKey` / `turnstileSecretKey` są w SiteIntegrations (tab Turnstile). Edytor wpisuje klucze w panelu. ## Front — widget (client) Import z `@intecion/ipal-kit/client`. `siteKey` pobierz server-side i przekaż jako prop: ```tsx // server component — pobiera publiczny siteKey z panelu import { getSiteIntegrations } from '@intecion/ipal-kit' const { turnstileSiteKey } = await getSiteIntegrations(payload) // przekaż do swojego client-formularza → widget ``` ```tsx 'use client' import { Turnstile } from '@intecion/ipal-kit/client' import { useState } from 'react' function ContactForm({ siteKey }) { const [token, setToken] = useState(null) return (
{/* pola */} ) } ``` Widget ładuje skrypt Turnstile sam (bez `next/script`), zwraca token przez `onToken` (null przy wygaśnięciu/błędzie). ## Front — verify (server) ```ts import { verifyTurnstile } from '@intecion/ipal-kit/server' const ok = await verifyTurnstile({ token, payload, ip }) if (!ok) { // odrzuć zgłoszenie } ``` `verifyTurnstile` czyta secret z SiteIntegrations (Local API), woła Cloudflare. Zwraca `false` na każdy problem (brak klucza, sieć, odrzucenie) — traktuj `false` jako „nie ufaj temu zgłoszeniu". `server-only` gwarantuje, że nie trafi do bundla przeglądarki. > W formularzach zwykle nie wołasz `verifyTurnstile` wprost — robi to > `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 ( {children} ) } ``` ### 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 (
{/* pola formularza */} {/* widget tam, gdzie ma być */} ) } ``` `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 `` + `token`. Reszta (weryfikacja) dzieje się w submitForm automatycznie. ### Stary sposób (nadal działa) `` bezpośrednio — jeśli potrzebujesz pełnej kontroli albo masz nietypowy przypadek. Provider to warstwa wygody nad tym.