# Frontend — wymagane implementacje Co projekt klienta musi zrobić na froncie, żeby plugin działał. Zebrane z realnego wdrożenia (ipal-test). W przyszłości → pełny poradnik "first setup". ## Wymagania środowiska ### Tailwind CSS (WYMÓG) Komponenty pluginu (CookieBanner, Turnstile widget, i inne) są w czystym Tailwind. Klient MUSI mieć Tailwind + skanować pakiet pluginu: ```bash pnpm add tailwindcss @tailwindcss/postcss # v4 ``` `postcss.config.mjs` (root): ```js export default { plugins: { '@tailwindcss/postcss': {} } } ``` W globalnym CSS (np. app/(frontend)/styles.css): ```css @import "tailwindcss"; @source "../../../node_modules/ipal-kit/dist/**/*.js"; ``` **@source jest kluczowy** — Tailwind domyślnie NIE skanuje node_modules, więc bez tego klasy komponentów pluginu się nie wygenerują (komponenty renderują się bez stylów). Ścieżka relatywna do pliku CSS. ### Przestylowanie pod klienta Komponenty pluginu (CookieBanner, CookieButton) mają domyślny wygląd i działają bez konfiguracji. Kolory/zaokrąglenia przez CSS custom properties z fallbackami — nadpisz w swoim CSS: ```css :root { --ipal-primary: #16a34a; --ipal-radius: 1rem; } ``` Pełna lista tokenów + opcja classNames (gdy tokeny nie starczą): docs/consent.md. Bloki są Twoje — plugin ich nie stylizuje, RenderBlocks nie dodaje markupu. ### Wymagane zależności (transitive) Instalacja z npm zaciąga automatycznie. Przy lokalnym tarballu doinstaluj: ``` @payloadcms/plugin-seo @payloadcms/plugin-form-builder nodemailer lucide-react slugify server-only ``` ### Spójność wersji @payloadcms/* pnpm.overrides wymuszające jedną wersję (patrz README). ### generate:importmap Po wpięciu pluginu: `pnpm payload generate:importmap` (dla pól SEO w adminie). ## Struktura tras (lokalizacja) ``` src/app/(frontend)/ layout.tsx # root (
) — istniejący [locale]/ layout.tsx # walidacja locale + ConsentProvider [[...slug]]/ page.tsx # render strony (bloki) ``` **[[...slug]] MUSI być podwójny nawias** (opcjonalny catch-all): - `[slug]` → string (błąd `slug.join is not a function`) - `[[...slug]]` → tablica (poprawne), łapie /pl (home) i /pl/o-nas jednym plikiem ## i18n — jedno źródło prawdy Wydziel config locale do osobnego pliku, importuj wszędzie: ```ts // src/i18n.config.ts export const i18nConfig = { defaultLocale: 'pl', locales: [ { code: 'pl', label: 'Polski' }, { code: 'en', label: 'English' }, ], } as const // as const — inaczej TS: locales nie pasuje do niepustej tuple ``` Importuj w: payload.config (ipalKit({ i18n: i18nConfig })), middleware.ts. Layout może czytać locale z payload config (config.localization.locales) — też jedno źródło. ## Middleware > **Next 16:** konwencja `middleware.ts` jest deprecated na rzecz `proxy.ts` > (plik `proxy.ts`, funkcja `export function proxy`). Logika pluginu bez zmian — > `createLocaleMiddleware` działa tak samo, zmienia się tylko nazwa pliku i > funkcji po stronie projektu. Na razie `middleware.ts` działa z ostrzeżeniem. ```ts // src/middleware.ts import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server' import { createLocaleMiddleware } from 'ipal-kit/next/middleware' import { i18nConfig } from '@/i18n.config' const localeMiddleware = createLocaleMiddleware({ config: i18nConfig }) export function middleware(request: NextRequest) { const result = localeMiddleware(request) if (result.type === 'next') return NextResponse.next() const response = NextResponse.redirect(result.location) response.cookies.set(result.cookie.name, result.cookie.value) return response } // matcher MUSI być inline (Next analizuje statycznie, nie wykonuje importów — // import DEFAULT_MIDDLEWARE_MATCHER byłby zignorowany → middleware łapie // /admin /_next /api → 500) export const config = { matcher: ['/((?!api|admin|_next|.*\\..*).*)'], } ``` ## Rozwiązywanie strony (page.tsx) - brak slug (/pl) → home przez System Pages (settings.homepage), NIE hardkod slug - slug (/pl/o-nas) → payload.find by slug w danym locale - depth: 2 → żeby relacje w blokach (form) się populowały ## Consent (layout [locale]) Layout (server) czyta getConsentTexts, przekazuje jako prop do ConsentProvider (client). Banner + button renderują się same: ```ts import { getConsentTexts } from 'ipal-kit' import { ConsentProvider, CookieBanner, CookieButton } from 'ipal-kit/client' const texts = await getConsentTexts({ config, locale, payload, privacyPolicy }) //