updated docs

This commit is contained in:
2026-08-04 19:37:23 +02:00
parent 9a45b3a50f
commit 90cbc4370f
19 changed files with 290 additions and 272 deletions
+51 -11
View File
@@ -33,14 +33,29 @@ theirs, and don't put it in any file that ends up in a repository.
### Store the token in your environment ### Store the token in your environment
Don't paste the token straight into project files. Keep it in an environment Don't paste the token straight into project files. Keep it in an environment
variable, so your install config carries no secret. variable, so your install config carries no secret. How you set it depends on
your system:
**macOS** (default shell is zsh):
```bash ```bash
# add to ~/.zshrc (or ~/.bashrc) echo 'export GITEA_TOKEN=paste_your_token_here' >> ~/.zshrc
export GITEA_TOKEN=paste_your_token_here source ~/.zshrc
``` ```
Reload your shell (`source ~/.zshrc`) or open a new terminal window. **Linux / Ubuntu** (default shell is bash):
```bash
echo 'export GITEA_TOKEN=paste_your_token_here' >> ~/.bashrc
source ~/.bashrc
```
**Windows (PowerShell)** — set it permanently for your user, then open a new
terminal so it takes effect:
```powershell
setx GITEA_TOKEN "paste_your_token_here"
```
> On Windows, `setx` writes the variable but does **not** affect the current
> window — close it and open a new PowerShell for the token to be visible.
--- ---
@@ -55,16 +70,40 @@ specific, unreleased commit straight from the repository.
The plugin is published to the package registry in Gitea. You pull a ready-built The plugin is published to the package registry in Gitea. You pull a ready-built
package and build nothing locally. package and build nothing locally.
**1. Configure the registry.** Create an `.npmrc` file in your project directory **1. Configure the registry.** Add these two lines to an `.npmrc` file:
(or add these two lines to `~/.npmrc` to make it work globally):
``` ```
@intecion:registry=https://git.intecion.net/api/packages/IntecionSoftware/npm/ @intecion:registry=https://git.intecion.net/api/packages/IntecionSoftware/npm/
//git.intecion.net/api/packages/IntecionSoftware/npm/:_authToken=${GITEA_TOKEN} //git.intecion.net/api/packages/IntecionSoftware/npm/:_authToken=${GITEA_TOKEN}
``` ```
Because the token lives in the `${GITEA_TOKEN}` variable, this file holds no You can put this file **in the project directory** (applies to that project) or
secret — you can safely commit it. **globally** (applies everywhere). The global location differs by system:
| System | Global `.npmrc` path |
|---|---|
| macOS | `~/.npmrc` (i.e. `/Users/you/.npmrc`) |
| Linux / Ubuntu | `~/.npmrc` (i.e. `/home/you/.npmrc`) |
| Windows | `%USERPROFILE%\.npmrc` (i.e. `C:\Users\you\.npmrc`) |
Fastest way to create the global file:
```bash
# macOS / Linux
npm config set @intecion:registry https://git.intecion.net/api/packages/IntecionSoftware/npm/
```
```powershell
# Windows (PowerShell) — same command, npm handles the path
npm config set @intecion:registry https://git.intecion.net/api/packages/IntecionSoftware/npm/
```
Then add the auth line manually (npm config doesn't set tokens with variables).
Because the token lives in the `${GITEA_TOKEN}` variable, the file holds no
secret — you can safely commit a project-level `.npmrc`.
> **Windows note:** the `${GITEA_TOKEN}` syntax in `.npmrc` is expanded by npm/pnpm
> itself, not by the shell — so it works the same on Windows as on macOS/Linux,
> as long as you set the variable with `setx` (see above).
**2. Install:** **2. Install:**
@@ -141,7 +180,7 @@ nobody gets stuck on a stale one from the cache.
Everything lives in the **[docs/](./docs)** directory. To get started: Everything lives in the **[docs/](./docs)** directory. To get started:
- **[getting-started.md](./docs/getting-started.md)** — project setup, step by step - **[getting-started.md](./docs/getting-started.md)** — project setup, step by step
- **[install.md](./docs/install.md)** — more on installation and troubleshooting - **[publishing.md](./docs/publishing.md)** — releasing new plugin versions
- **[README.md](./docs/README.md)** — index of the plugin's modules - **[README.md](./docs/README.md)** — index of the plugin's modules
--- ---
@@ -155,5 +194,6 @@ Everything lives in the **[docs/](./docs)** directory. To get started:
| `ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED` | an attempt to build on install from git — report it to whoever publishes the plugin | | `ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED` | an attempt to build on install from git — report it to whoever publishes the plugin |
| old code after reinstalling | cache: run `pnpm store prune`, then remove `.next` and `node_modules/@intecion` | | old code after reinstalling | cache: run `pnpm store prune`, then remove `.next` and `node_modules/@intecion` |
If you get stuck, check **[docs/install.md](./docs/install.md)** or message the If you get stuck, check the troubleshooting table above, see
team that maintains the plugin. **[docs/publishing.md](./docs/publishing.md)** for distribution issues, or message
the team that maintains the plugin.
+14 -10
View File
@@ -1,8 +1,11 @@
# IPAL — Dokumentacja modułów # IPAL — Dokumentacja modułów
**Stawiasz nowy projekt?** → [install.md](./install.md) — instalacja (github/rejestr/tarball) i diagnostyka **Instalacja pakietu** (token Gitea, rejestr vs repozytorium) → główny
[README](../README.md).
**Nowy projekt krok po kroku** → [getting-started.md](./getting-started.md).
Konfiguracja: [getting-started.md](./getting-started.md) Ta dokumentacja opisuje **konfigurację i moduły** pluginu — zakłada, że
`@intecion/ipal-kit` jest już zainstalowany.
IPAL (Intecion Payload Advanced Library) to plugin do Payload CMS 3, który IPAL (Intecion Payload Advanced Library) to plugin do Payload CMS 3, który
dostarcza logikę i konfigurację; projekt klienta zawiera tylko komponenty dostarcza logikę i konfigurację; projekt klienta zawiera tylko komponenty
@@ -14,7 +17,7 @@ wizualne i podłączenia do Next.js.
- **Klient (projekt)** = komponenty (wygląd), pliki-podłączenia Next.js - **Klient (projekt)** = komponenty (wygląd), pliki-podłączenia Next.js
(jednolinijkowe re-eksporty), konfiguracja front. (jednolinijkowe re-eksporty), konfiguracja front.
## Instalacja i wpięcie ## Konfiguracja i wpięcie
### Wymagane zależności ### Wymagane zależności
@@ -62,7 +65,7 @@ importMap`).
```ts ```ts
// payload.config.ts // payload.config.ts
import { ipalKit } from 'ipal-kit' import { ipalKit } from '@intecion/ipal-kit'
export default buildConfig({ export default buildConfig({
// ... // ...
@@ -88,11 +91,11 @@ export default buildConfig({
| Import | Zawiera | Kontekst | | Import | Zawiera | Kontekst |
|---|---|---| |---|---|---|
| `ipal-kit` | logika server-safe, plugin, helpery | server / config | | `@intecion/ipal-kit` | logika server-safe, plugin, helpery | server / config |
| `ipal-kit/server` | runtime server-only (sendEmail, verifyTurnstile, submitForm) | Server Actions / route handlers | | `@intecion/ipal-kit/server` | runtime server-only (sendEmail, verifyTurnstile, submitForm) | Server Actions / route handlers |
| `ipal-kit/client` | komponenty client (consent, Turnstile, Analytics) | `'use client'` | | `@intecion/ipal-kit/client` | komponenty client (consent, Turnstile, Analytics) | `'use client'` |
| `ipal-kit/rsc` | RenderBlocks (RSC) | server component | | `@intecion/ipal-kit/rsc` | RenderBlocks (RSC) | server component |
| `ipal-kit/next/middleware` | locale middleware | middleware.ts | | `@intecion/ipal-kit/next/middleware` | locale middleware | middleware.ts / proxy.ts |
## Moduły ## Moduły
@@ -114,6 +117,7 @@ export default buildConfig({
Nowy projekt krok po kroku: [getting-started.md](./getting-started.md) Nowy projekt krok po kroku: [getting-started.md](./getting-started.md)
Referencja wdrożenia frontu: [frontend-setup.md](./frontend-setup.md) Referencja wdrożenia frontu: [frontend-setup.md](./frontend-setup.md)
Wydawanie nowych wersji wtyczki: [publishing.md](./publishing.md)
## Zasady dla wszystkich modułów ## Zasady dla wszystkich modułów
@@ -122,6 +126,6 @@ Referencja wdrożenia frontu: [frontend-setup.md](./frontend-setup.md)
2. **Sekrety w panelu** — SMTP, Turnstile secret, R2 w SiteIntegrations 2. **Sekrety w panelu** — SMTP, Turnstile secret, R2 w SiteIntegrations
(admin-only). Odczyt server-side przez Local API. (admin-only). Odczyt server-side przez Local API.
3. **Client/server split** — kod z sekretami ma `server-only`; komponenty 3. **Client/server split** — kod z sekretami ma `server-only`; komponenty
client w `ipal-kit/client`. client w `@intecion/ipal-kit/client`.
4. **Generyki na typy klienta** — helpery przyjmują `<T>` (np. wygenerowany 4. **Generyki na typy klienta** — helpery przyjmują `<T>` (np. wygenerowany
`SiteSetting`), bo plugin nie zna typów projektu. `SiteSetting`), bo plugin nie zna typów projektu.
+4 -4
View File
@@ -25,7 +25,7 @@ zmieniać role) do wskazanej kolekcji. Kolekcja należy do Ciebie
## Predykaty (front / hooki / access) ## Predykaty (front / hooki / access)
```ts ```ts
import { isAdmin, isEditor, hasMinimumRole } from 'ipal-kit' import { isAdmin, isEditor, hasMinimumRole } from '@intecion/ipal-kit'
isAdmin(user) // admin? isAdmin(user) // admin?
isEditor(user) // editor lub admin (hierarchia) isEditor(user) // editor lub admin (hierarchia)
@@ -39,7 +39,7 @@ Przyjmują luźno typowanego usera (jak `req.user`), czytają `roles` defensywni
### Collection-level (zwracają boolean | Where) ### Collection-level (zwracają boolean | Where)
```ts ```ts
import { adminOnly, adminOrEditor, adminOrSelf, requireRole, authenticated } from 'ipal-kit' import { adminOnly, adminOrEditor, adminOrSelf, requireRole, authenticated } from '@intecion/ipal-kit'
export const Articles = { export const Articles = {
slug: 'articles', slug: 'articles',
@@ -62,7 +62,7 @@ access: { update: requireRole('editor') }
### Field-level (zwracają boolean) ### Field-level (zwracają boolean)
```ts ```ts
import { adminOnlyField, adminOrEditorField, requireRoleField } from 'ipal-kit' import { adminOnlyField, adminOrEditorField, requireRoleField } from '@intecion/ipal-kit'
{ {
name: 'internalNote', name: 'internalNote',
@@ -74,6 +74,6 @@ import { adminOnlyField, adminOrEditorField, requireRoleField } from 'ipal-kit'
## Hierarchia ## Hierarchia
```ts ```ts
import { ROLE_HIERARCHY } from 'ipal-kit' import { ROLE_HIERARCHY } from '@intecion/ipal-kit'
// ['user', 'editor', 'admin'] — wyższa rola spełnia wymóg niższej // ['user', 'editor', 'admin'] — wyższa rola spełnia wymóg niższej
``` ```
+75
View File
@@ -0,0 +1,75 @@
# analytics
Ładuje GA4 albo Google Tag Manager, spięte z modułem consent: Consent Mode
dostaje decyzję odwiedzającego, zanim tag się załaduje, i aktualizację w
momencie kliknięcia w banerze.
## Config
Brak opcji w `payload.config` — plugin czyta ID z globala **Site Integrations**
(Settings → Site Integrations):
- **GA4 Measurement ID** — `G-XXXXXXXXXX`
- **GTM Container ID** — `GTM-XXXXXXX`
Gdy ustawione są oba, wygrywa GTM. Gdy żadne — komponent nie robi nic.
To jedyne pola z Site Integrations, które trafiają na klienta — są publiczne
(widać je w źródle każdej strony z GA). `getAnalyticsConfig` czyta wyłącznie je,
więc sekrety (Turnstile, SMTP) nie mają jak wyciec.
## Front
W layoucie locale, **wewnątrz `ConsentProvider`**:
```tsx
import { getAnalyticsConfig } from '@intecion/ipal-kit'
import { ConsentProvider, CookieBanner, Analytics } from '@intecion/ipal-kit/client'
export default async function LocaleLayout({ children, params }) {
const payload = await getPayload({ config })
const analytics = await getAnalyticsConfig(payload) // server
return (
<ConsentProvider texts={texts}>
{children}
<CookieBanner />
<Analytics {...analytics} />
</ConsentProvider>
)
}
```
`Analytics` nie renderuje nic — wstrzykuje skrypty. Zamontuj raz, wysoko w
drzewie.
## Jak działa Consent Mode
1. Przed załadowaniem tagu: `gtag('consent', 'default', …)` z zapisaną decyzją
odwiedzającego (albo wszystko `denied`, gdy jeszcze nie zdecydował).
2. Tag się ładuje i respektuje ten stan od pierwszego trafienia.
3. Klik w banerze → `gtag('consent', 'update', …)` → tagi reagują natychmiast.
Mapowanie kategorii na sygnały Google:
| Kategoria | Sygnały |
|---|---|
| analytics | `analytics_storage` |
| marketing | `ad_storage`, `ad_user_data`, `ad_personalization` |
| functional | `functionality_storage`, `personalization_storage` |
| necessary | `security_storage` (zawsze `granted`) |
## Testowanie
W konsoli: `window.dataLayer` — powinien zawierać `Arguments(3)` z `consent` /
`default`, a po decyzji `consent` / `update`. Jeśli widzisz `Array` zamiast
`Arguments`, komenda nie zostanie rozpoznana przez Google.
Ciasteczko `_ga` pojawia się dopiero po zgodzie na analytics — to jest sedno
Consent Mode.
**GTM sam nie ustawia ciasteczek** — to pojemnik. Bez opublikowanego (Submit →
Publish, nie sam zapis) tagu GA4 w kontenerze wszystko wygląda dobrze:
`gtm.load` w dataLayer, Tag Assistant widzi kontener — a `_ga` nie ma, bo nic go
nie tworzy. Przy debugowaniu warto tymczasowo wyczyścić GTM Container ID i
zostawić samo GA4, żeby wyeliminować kontener jako zmienną.
+2 -2
View File
@@ -12,10 +12,10 @@ Brak opcji w payload.config — bloki definiujesz w swoich kolekcjach
## Front — RenderBlocks ## Front — RenderBlocks
Import z `ipal-kit/rsc` (to komponent serwerowy): Import z `@intecion/ipal-kit/rsc` (to komponent serwerowy):
```tsx ```tsx
import { RenderBlocks } from 'ipal-kit/rsc' import { RenderBlocks } from '@intecion/ipal-kit/rsc'
// Twój registry: blockType → komponent (komponenty są Twoje) // Twój registry: blockType → komponent (komponenty są Twoje)
import { Hero } from '@/blocks/Hero' import { Hero } from '@/blocks/Hero'
+6 -6
View File
@@ -23,12 +23,12 @@ localized. Link do polityki prywatności bierze się z system pages
## Front — Provider + banner ## Front — Provider + banner
Provider owija aplikację, banner i button renderują się same. Import z Provider owija aplikację, banner i button renderują się same. Import z
`ipal-kit/client`: `@intecion/ipal-kit/client`:
```tsx ```tsx
// app/(frontend)/[locale]/layout.tsx // app/(frontend)/[locale]/layout.tsx
import { ConsentProvider, CookieBanner, CookieButton } from 'ipal-kit/client' import { ConsentProvider, CookieBanner, CookieButton } from '@intecion/ipal-kit/client'
import { getConsentTexts } from 'ipal-kit' import { getConsentTexts } from '@intecion/ipal-kit'
export default async function Layout({ children, params }) { export default async function Layout({ children, params }) {
const { locale } = await params const { locale } = await params
@@ -66,15 +66,15 @@ Domyślne klasy Tailwind można nadpisać przez `classNames`:
## Gating skryptów wg zgody ## Gating skryptów wg zgody
```ts ```ts
import { updateConsent, setDefaultConsent } from 'ipal-kit' import { updateConsent, setDefaultConsent } from '@intecion/ipal-kit'
// wysyła sygnały do Google Consent Mode (gtag) na podstawie stanu zgody // wysyła sygnały do Google Consent Mode (gtag) na podstawie stanu zgody
``` ```
Logika (kategorie, storage, parsowanie) też jest dostępna server-safe z Logika (kategorie, storage, parsowanie) też jest dostępna server-safe z
`ipal-kit`: `@intecion/ipal-kit`:
```ts ```ts
import { parseConsent, CONSENT_COOKIE, CONSENT_CATEGORIES } from 'ipal-kit' import { parseConsent, CONSENT_COOKIE, CONSENT_CATEGORIES } from '@intecion/ipal-kit'
// np. gating skryptów server-side na podstawie cookie zgody // np. gating skryptów server-side na podstawie cookie zgody
``` ```
+6 -6
View File
@@ -26,7 +26,7 @@ zarządza tylko przypisaniem archiwum i jego adresem.
```ts ```ts
// content.config.ts — współdzielony przez payload.config i front // content.config.ts — współdzielony przez payload.config i front
import type { ContentOption } from 'ipal-kit' import type { ContentOption } from '@intecion/ipal-kit'
export const contentConfig: ContentOption = { export const contentConfig: ContentOption = {
collections: [ collections: [
@@ -59,7 +59,7 @@ Serce modułu. Catch-all `[[...slug]]` łapie wszystko, a `resolveRoute` mówi,
dana ścieżka jest: dana ścieżka jest:
```ts ```ts
import { resolveRoute } from 'ipal-kit' import { resolveRoute } from '@intecion/ipal-kit'
const route = await resolveRoute({ const route = await resolveRoute({
payload, payload,
@@ -91,7 +91,7 @@ Zamiast pisać cache'owane wrappery w każdym projekcie:
```ts ```ts
// src/lib/content.ts // src/lib/content.ts
import { createContentHelpers } from 'ipal-kit' import { createContentHelpers } from '@intecion/ipal-kit'
import config from '@/payload.config' import config from '@/payload.config'
import { contentConfig } from '@/content.config' import { contentConfig } from '@/content.config'
@@ -151,7 +151,7 @@ kolekcji ta strona jest archiwum. Ten sam blok obsługuje `/pl/artykuly` i
## Ścieżki i paginacja ## Ścieżki i paginacja
```ts ```ts
import { buildEntryPath, buildArchivePath, parsePageParam } from 'ipal-kit' import { buildEntryPath, buildArchivePath, parsePageParam } from '@intecion/ipal-kit'
buildEntryPath({ locale: 'pl', archiveSlug: 'artykuly', entrySlug: 'moj-post' }) buildEntryPath({ locale: 'pl', archiveSlug: 'artykuly', entrySlug: 'moj-post' })
// '/pl/artykuly/moj-post' // '/pl/artykuly/moj-post'
@@ -201,6 +201,6 @@ import {
parsePageParam, parsePageParam,
createContentHelpers, createContentHelpers,
archiveFieldName, archiveFieldName,
} from 'ipal-kit' } from '@intecion/ipal-kit'
import type { ContentOption, ResolvedRoute, ArchiveEntries } from 'ipal-kit' import type { ContentOption, ResolvedRoute, ArchiveEntries } from '@intecion/ipal-kit'
``` ```
+27 -2
View File
@@ -19,7 +19,7 @@ Brak opcji — SMTP (host, port, user, password, from) jest w SiteIntegrations
## Front — sendEmail (server) ## Front — sendEmail (server)
```ts ```ts
import { sendEmail } from 'ipal-kit/server' import { sendEmail } from '@intecion/ipal-kit/server'
const result = await sendEmail({ const result = await sendEmail({
payload, payload,
@@ -48,4 +48,29 @@ if (result.sent) {
Reset hasła / weryfikacja email idą przez wbudowany mechanizm Payload Reset hasła / weryfikacja email idą przez wbudowany mechanizm Payload
(`config.email`), którego ten moduł **nie** konfiguruje (celowo — wymagałby (`config.email`), którego ten moduł **nie** konfiguruje (celowo — wymagałby
SMTP w env). Jeśli ich potrzebujesz, to osobna konfiguracja adaptera przy SMTP w env). Jeśli ich potrzebujesz, to osobna konfiguracja adaptera przy
`buildConfig`. `buildConfig`.
## Adapter — SMTP z panelu dla całego Payloada
`sendEmail` wysyła własnym transportem. Osobno plugin daje adapter, który
podłącza tę samą skrzynkę pod `payload.sendEmail`:
```ts
// payload.config.ts
import { panelSmtpAdapter } from '@intecion/ipal-kit'
export default buildConfig({
email: panelSmtpAdapter(),
// panelSmtpAdapter({ fallbackFromAddress, fallbackFromName }) — używane tylko
// zanim panel zostanie wypełniony (Payload wymaga adresu synchronicznie przy
// starcie, zanim można odczytać globala)
})
```
Po wpięciu wszystko, co w Payloadzie wysyła maile, idzie przez SMTP z Site
Integrations — w tym wbudowane maile form-buildera (Forms → Emails), maile
resetu hasła i weryfikacji konta. Adapter czyta konfigurację przy każdym
wysłaniu, więc zmiana skrzynki w panelu działa bez restartu.
Bez adaptera Payload używa mocka, który tylko loguje do konsoli — maile
form-buildera nie wyjdą.
+2 -2
View File
@@ -31,7 +31,7 @@ Zwykle w Server Action wywoływanej przez formularz. HTML obu maili składasz
na froncie (Twój wygląd): na froncie (Twój wygląd):
```ts ```ts
import { submitForm } from 'ipal-kit/server' import { submitForm } from '@intecion/ipal-kit/server'
const result = await submitForm({ const result = await submitForm({
payload, payload,
@@ -86,7 +86,7 @@ wiadomości skonfigurowane przez edytora (Forms → formularz → Emails), przez
`payload.sendEmail`. Żeby wyszły, config musi mieć adapter: `payload.sendEmail`. Żeby wyszły, config musi mieć adapter:
```ts ```ts
email: panelSmtpAdapter(), // z 'ipal-kit' email: panelSmtpAdapter(), // z '@intecion/ipal-kit'
``` ```
Wtedy idą przez SMTP z Site Integrations. Edytor ustawia odbiorców, temat i Wtedy idą przez SMTP z Site Integrations. Edytor ustawia odbiorców, temat i
+7 -7
View File
@@ -22,7 +22,7 @@ export default { plugins: { '@tailwindcss/postcss': {} } }
W globalnym CSS (np. app/(frontend)/styles.css): W globalnym CSS (np. app/(frontend)/styles.css):
```css ```css
@import "tailwindcss"; @import "tailwindcss";
@source "../../../node_modules/ipal-kit/dist/**/*.js"; @source "../../../node_modules/@intecion/ipal-kit/dist/**/*.js";
``` ```
**@source jest kluczowy** — Tailwind domyślnie NIE skanuje node_modules, więc **@source jest kluczowy** — Tailwind domyślnie NIE skanuje node_modules, więc
@@ -107,7 +107,7 @@ też jedno źródło.
// src/middleware.ts // src/middleware.ts
import { NextResponse } from 'next/server' import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server' import type { NextRequest } from 'next/server'
import { createLocaleMiddleware } from 'ipal-kit/next/middleware' import { createLocaleMiddleware } from '@intecion/ipal-kit/next/middleware'
import { i18nConfig } from '@/i18n.config' import { i18nConfig } from '@/i18n.config'
const localeMiddleware = createLocaleMiddleware({ config: i18nConfig }) const localeMiddleware = createLocaleMiddleware({ config: i18nConfig })
@@ -140,8 +140,8 @@ Layout (server) czyta getConsentTexts, przekazuje jako prop do ConsentProvider
(client). Banner + button renderują się same: (client). Banner + button renderują się same:
```ts ```ts
import { getConsentTexts } from 'ipal-kit' import { getConsentTexts } from '@intecion/ipal-kit'
import { ConsentProvider, CookieBanner, CookieButton } from 'ipal-kit/client' import { ConsentProvider, CookieBanner, CookieButton } from '@intecion/ipal-kit/client'
const texts = await getConsentTexts({ config, locale, payload, privacyPolicy }) const texts = await getConsentTexts({ config, locale, payload, privacyPolicy })
// <ConsentProvider texts={texts}>{children}<CookieBanner/><CookieButton/></ConsentProvider> // <ConsentProvider texts={texts}>{children}<CookieBanner/><CookieButton/></ConsentProvider>
@@ -173,9 +173,9 @@ const enhanceProps = ({ block }) => {
- FormRenderer (client) — renderuje pola form-buildera (text/email/select/ - FormRenderer (client) — renderuje pola form-buildera (text/email/select/
country/checkbox/textarea/number/state/message) country/checkbox/textarea/number/state/message)
- Turnstile widget (ipal-kit/client) — siteKey jako prop, wstrzykiwany przez - Turnstile widget (@intecion/ipal-kit/client) — siteKey jako prop, wstrzykiwany przez
enhanceProps (z SiteIntegrations, publiczny — bezpieczny na kliencie) enhanceProps (z SiteIntegrations, publiczny — bezpieczny na kliencie)
- server action → submitForm (ipal-kit/server) — weryfikuje Turnstile i zapisuje - server action → submitForm (@intecion/ipal-kit/server) — weryfikuje Turnstile i zapisuje
Maili NIE składa się w kodzie. Po zapisie submission form-builder sam wysyła Maili NIE składa się w kodzie. Po zapisie submission form-builder sam wysyła
wiadomości skonfigurowane przez edytora (Forms → dany formularz → Emails: wiadomości skonfigurowane przez edytora (Forms → dany formularz → Emails:
@@ -185,7 +185,7 @@ Email To / CC / BCC / Subject / Message z placeholderami {{pole}}, {{*}},
Wymaga w payload.config: Wymaga w payload.config:
```ts ```ts
import { ipalKit, panelSmtpAdapter } from 'ipal-kit' import { ipalKit, panelSmtpAdapter } from '@intecion/ipal-kit'
export default buildConfig({ export default buildConfig({
email: panelSmtpAdapter(), email: panelSmtpAdapter(),
+19 -16
View File
@@ -1,8 +1,8 @@
# Nowy projekt — krok po kroku # Nowy projekt — krok po kroku
> **Instalacja:** najszybciej `pnpm add github:rasm-its/ipal-kit`. Pełne drogi > **Instalacja pluginu** (token Gitea, rejestr vs git) jest opisana w głównym
> (github / rejestr / tarball) i diagnostyka błędów — install.md. > [README](../README.md). Ten przewodnik zakłada, że `@intecion/ipal-kit` jest
> już zainstalowany, i przeprowadza przez **konfigurację** projektu.
Od pustego katalogu do działającej, wielojęzycznej strony z blokami, consentem i Od pustego katalogu do działającej, wielojęzycznej strony z blokami, consentem i
formularzem. Kolejność jest istotna: kilka kroków zależy od poprzednich (schemat formularzem. Kolejność jest istotna: kilka kroków zależy od poprzednich (schemat
@@ -20,10 +20,13 @@ npx create-payload-app@latest moj-projekt
cd moj-projekt cd moj-projekt
``` ```
## 2. Instalacja IPAL ## 2. Plugin i zależności
Zainstaluj `@intecion/ipal-kit` zgodnie z [README](../README.md) (rejestr Gitea
albo bezpośrednio z repozytorium — wymaga tokenu). Następnie dodaj zależności
współdzielone z Payloadem, których plugin nie zaciąga sam:
```bash ```bash
pnpm add ipal-kit
pnpm add @payloadcms/[email protected] @payloadcms/[email protected] \ pnpm add @payloadcms/[email protected] @payloadcms/[email protected] \
nodemailer lucide-react slugify server-only nodemailer lucide-react slugify server-only
``` ```
@@ -72,7 +75,7 @@ export const i18nConfig = {
## 4. payload.config.ts ## 4. payload.config.ts
```ts ```ts
import { ipalKit, panelSmtpAdapter } from 'ipal-kit' import { ipalKit, panelSmtpAdapter } from '@intecion/ipal-kit'
import { i18nConfig } from '@/i18n.config' import { i18nConfig } from '@/i18n.config'
import { Pages } from '@/collections/Pages' import { Pages } from '@/collections/Pages'
@@ -101,7 +104,7 @@ export default buildConfig({
```ts ```ts
// src/collections/Pages.ts // src/collections/Pages.ts
import type { CollectionConfig } from 'payload' import type { CollectionConfig } from 'payload'
import { buildSlugField } from 'ipal-kit' import { buildSlugField } from '@intecion/ipal-kit'
import { ContentBlock } from '@/blocks/Content/config' import { ContentBlock } from '@/blocks/Content/config'
export const Pages: CollectionConfig = { export const Pages: CollectionConfig = {
@@ -151,7 +154,7 @@ export function ContentBlockComponent({ heading, body }: { heading?: string; bod
```ts ```ts
// src/blocks/registry.ts // src/blocks/registry.ts
import type { BlockComponentMap } from 'ipal-kit/rsc' import type { BlockComponentMap } from '@intecion/ipal-kit/rsc'
import { ContentBlockComponent } from '@/blocks/Content/Component' import { ContentBlockComponent } from '@/blocks/Content/Component'
export const blockRegistry: BlockComponentMap = { export const blockRegistry: BlockComponentMap = {
@@ -177,7 +180,7 @@ export default { plugins: { '@tailwindcss/postcss': {} } }
```css ```css
/* src/app/(frontend)/styles.css — na górze */ /* src/app/(frontend)/styles.css — na górze */
@import "tailwindcss"; @import "tailwindcss";
@source "../../../node_modules/ipal-kit/dist/**/*.js"; @source "../../../node_modules/@intecion/ipal-kit/dist/**/*.js";
``` ```
`@source` jest **konieczny** — Tailwind nie skanuje `node_modules`, więc bez `@source` jest **konieczny** — Tailwind nie skanuje `node_modules`, więc bez
@@ -190,7 +193,7 @@ niego klasy komponentów pluginu nie powstaną i banner wyrenderuje się goły.
// src/middleware.ts // src/middleware.ts
import { NextResponse } from 'next/server' import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server' import type { NextRequest } from 'next/server'
import { createLocaleMiddleware } from 'ipal-kit/next/middleware' import { createLocaleMiddleware } from '@intecion/ipal-kit/next/middleware'
import { i18nConfig } from '@/i18n.config' import { i18nConfig } from '@/i18n.config'
const localeMiddleware = createLocaleMiddleware({ config: i18nConfig }) const localeMiddleware = createLocaleMiddleware({ config: i18nConfig })
@@ -294,8 +297,8 @@ tablicy (`slug.join is not a function`) i nie łapią samego `/pl`.
```tsx ```tsx
// src/app/(frontend)/[locale]/layout.tsx // src/app/(frontend)/[locale]/layout.tsx
import { notFound } from 'next/navigation' import { notFound } from 'next/navigation'
import { getConsentTexts, getAnalyticsConfig } from 'ipal-kit' import { getConsentTexts, getAnalyticsConfig } from '@intecion/ipal-kit'
import { ConsentProvider, CookieBanner, CookieButton, Analytics } from 'ipal-kit/client' import { ConsentProvider, CookieBanner, CookieButton, Analytics } from '@intecion/ipal-kit/client'
import { i18nConfig } from '@/i18n.config' import { i18nConfig } from '@/i18n.config'
import { getCachedPayload, getSettings } from '@/lib/payload' import { getCachedPayload, getSettings } from '@/lib/payload'
import { getConfiguredLocales } from '@/lib/locales' import { getConfiguredLocales } from '@/lib/locales'
@@ -347,8 +350,8 @@ export async function generateStaticParams() {
// src/app/(frontend)/[locale]/[[...slug]]/page.tsx // src/app/(frontend)/[locale]/[[...slug]]/page.tsx
import { notFound } from 'next/navigation' import { notFound } from 'next/navigation'
import type { Metadata } from 'next' import type { Metadata } from 'next'
import { RenderBlocks } from 'ipal-kit/rsc' import { RenderBlocks } from '@intecion/ipal-kit/rsc'
import { createPageMetadata } from 'ipal-kit' import { createPageMetadata } from '@intecion/ipal-kit'
import { i18nConfig } from '@/i18n.config' import { i18nConfig } from '@/i18n.config'
import { blockRegistry } from '@/blocks/registry' import { blockRegistry } from '@/blocks/registry'
import { getCachedPayload } from '@/lib/payload' import { getCachedPayload } from '@/lib/payload'
@@ -435,7 +438,7 @@ Pełny opis: content.md. W skrócie:
2. **content.config.ts** obok i18n.config.ts: 2. **content.config.ts** obok i18n.config.ts:
```ts ```ts
import type { ContentOption } from 'ipal-kit' import type { ContentOption } from '@intecion/ipal-kit'
export const contentConfig: ContentOption = { export const contentConfig: ContentOption = {
collections: [{ slug: 'posts', label: 'Artykuły', perPage: 10 }], collections: [{ slug: 'posts', label: 'Artykuły', perPage: 10 }],
} }
@@ -497,7 +500,7 @@ Pełna lista tokenów: consent.md.
|---|---| |---|---|
| Pusty tab SEO / brak kolekcji Forms | rozjazd wersji `@payloadcms/*` — sprawdź `pnpm.overrides` | | Pusty tab SEO / brak kolekcji Forms | rozjazd wersji `@payloadcms/*` — sprawdź `pnpm.overrides` |
| `PayloadComponent not found in importMap` | `pnpm payload generate:importmap` | | `PayloadComponent not found in importMap` | `pnpm payload generate:importmap` |
| Banner bez stylów | brak `@source` na `node_modules/ipal-kit` albo brak Tailwinda | | Banner bez stylów | brak `@source` na `node_modules/@intecion/ipal-kit` albo brak Tailwinda |
| `/admin` i `/_next` zwracają 500 | matcher w middleware nie jest inline | | `/admin` i `/_next` zwracają 500 | matcher w middleware nie jest inline |
| `slug.join is not a function` | katalog `[slug]` zamiast `[[...slug]]` | | `slug.join is not a function` | katalog `[slug]` zamiast `[[...slug]]` |
| `/pl` → 404 | Homepage nieustawiony w System Pages | | `/pl` → 404 | Homepage nieustawiony w System Pages |
+4 -4
View File
@@ -34,7 +34,7 @@ import {
getLocaleCodes, getDefaultLocale, isValidLocale, getLocaleDefinition, getLocaleCodes, getDefaultLocale, isValidLocale, getLocaleDefinition,
negotiateLocale, buildLocalizedPath, switchLocalePath, negotiateLocale, buildLocalizedPath, switchLocalePath,
getLocalizedSlugs, LOCALE_COOKIE_NAME, getLocalizedSlugs, LOCALE_COOKIE_NAME,
} from 'ipal-kit' } from '@intecion/ipal-kit'
const config = { defaultLocale: 'pl', locales: [{code:'pl',label:'Polski'},{code:'en',label:'English'}] } const config = { defaultLocale: 'pl', locales: [{code:'pl',label:'Polski'},{code:'en',label:'English'}] }
@@ -69,15 +69,15 @@ fallback na `/{locale}` (root), zamiast dead-endu na 404.
## Middleware — patrz osobno ## Middleware — patrz osobno
Negocjacja locale + redirect na wejściu (`domena.com` → `/pl`) jest w Negocjacja locale + redirect na wejściu (`domena.com` → `/pl`) jest w
`ipal-kit/next/middleware`. Zobacz [middleware w tej sekcji](#middleware) `@intecion/ipal-kit/next/middleware`. Zobacz [middleware w tej sekcji](#middleware)
niżej. niżej.
## Middleware ## Middleware
```ts ```ts
// next-middleware.ts (projekt klienta) — jedyna logika to podłączenie // middleware.ts (projekt klienta) — jedyna logika to podłączenie
import { NextResponse } from 'next/server' import { NextResponse } from 'next/server'
import { createLocaleMiddleware, DEFAULT_MIDDLEWARE_MATCHER } from 'ipal-kit/next/middleware' import { createLocaleMiddleware, DEFAULT_MIDDLEWARE_MATCHER } from '@intecion/ipal-kit/next/middleware'
const i18nConfig = { const i18nConfig = {
defaultLocale: 'pl', defaultLocale: 'pl',
-118
View File
@@ -1,118 +0,0 @@
# Instalacja ipal-kit
Trzy drogi. Wybierz jedną i trzymaj się jej — mieszanie (raz git, raz rejestr,
raz tarball) to najczęstsze źródło błędów instalacji.
## Droga A — z GitHuba (zalecana dla zespołu)
Wymaga, żeby `dist/` był zacommitowany w repo (nie budowany u instalującego).
```bash
pnpm add github:rasm-its/ipal-kit
# konkretny tag (stabilniej):
pnpm add github:rasm-its/ipal-kit#v1.0.0
```
Wymagania po stronie projektu:
1. **`onlyBuiltDependencies`** — pnpm blokuje skrypty build z paczek git.
Jeśli pakiet ma jakiekolwiek skrypty postinstall, dodaj w package.json
projektu:
```json
"pnpm": { "onlyBuiltDependencies": ["@intecion/ipal-kit"] }
```
(Jeśli plugin nie ma `prepare`/postinstall — patrz niżej — to niepotrzebne.)
2. **peer-zależności** — plugin ich nie zaciąga, projekt musi mieć:
```bash
pnpm add @payloadcms/[email protected] @payloadcms/[email protected] \
nodemailer lucide-react slugify server-only
```
Wymagania po stronie pluginu (raz, przy wydawaniu):
- **`dist/` w repo** — bo instalacja z git nie buduje. Zbuduj i zacommituj
`dist` przed każdym wydaniem.
- **BRAK `prepare: pnpm build`** w package.json — inaczej pnpm próbuje budować
przy instalacji i żąda `onlyBuiltDependencies`. Skoro `dist` jest w repo, build
jest zbędny.
- **główny `exports` wskazuje `dist`, nie `src`** — instalacja z git czyta
główny `exports` (publishConfig działa TYLKO przy `npm publish`, nie przy git).
Wszystkie ścieżki `./dist/*.js` i `./dist/*.d.ts`.
- **jeden blok `exports`** — nie zostawiaj `publishConfig.exports` obok głównego;
dwa bloki potrafią rozjechać rozwiązywanie modułów.
- **żadnej self-reference** — pakiet nie może mieć siebie w `dependencies`
(`"@intecion/ipal-kit": "git+..."`). Wchodzi, gdy odpalisz `pnpm add` w
katalogu pluginu — NIGDY tego nie rób.
## Droga B — GitHub Packages (rejestr)
Publikujesz zbudowany pakiet; instalujący pobiera gotowy `dist`, nie buduje.
Plugin — `publishConfig.registry` + token z `write:packages`:
```bash
echo "//npm.pkg.github.com/:_authToken=TOKEN" >> ~/.npmrc
npm version patch && npm publish
```
Projekt — `.npmrc` ze scope + token z `read:packages`:
```
@intecion:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=TOKEN
```
```bash
pnpm add @intecion/ipal-kit
```
Zaleta nad Drogą A: semver (koniec z ręcznym commitowaniem `dist`), `publishConfig`
działa (nie musisz ruszać głównego `exports`). Wada: token u każdego instalującego.
## Droga C — lokalny tarball (development, przekazanie pliku)
```bash
# w pluginie
pnpm build && pnpm pack --out ipal-kit.tgz
# w PROJEKCIE (nie w pluginie!)
pnpm add ~/sciezka/ipal-kit/ipal-kit.tgz
```
`--out ipal-kit.tgz` daje stałą nazwę — bez tego scope zamienia `/` na `-`
(`intecion-ipal-kit-1.0.0.tgz`).
## Twarde zasady (wyparzone w boju)
- **NIGDY `pnpm add ...ipal-kit...` w katalogu pluginu.** Tworzy self-reference,
która zatruwa każdą kolejną instalację. Zawsze w katalogu projektu. Sprawdzaj
`pwd` przed każdym `pnpm add`.
- **Sprawdzaj REPO, nie plik lokalny.** pnpm z git bierze stan repo. Po zmianie:
`git show origin/main:package.json | grep '"import"'` — musi pokazać `./dist/`.
Commit z nazwą "fix" nie znaczy, że fix jest w commicie.
- **Nie edytuj package.json `sed`em.** Rozjeżdża strukturę (dwa bloki exports).
Nadpisuj cały plik.
- **Zostajesz na 1.0.0? Czyść cache przy każdym reinstall:**
```bash
rm -rf node_modules/@intecion node_modules/.pnpm/*ipal-kit* .next
pnpm store prune && pnpm install
```
Publikacja z bumpem wersji (Droga B) to znosi.
- **Weryfikuj rozwiązanie modułu, nie tylko instalację:**
```bash
node -e "console.log(require.resolve('@intecion/ipal-kit/next/middleware'))"
```
Ma wypisać ścieżkę do `dist`, nie błąd. Jeśli pokazuje `src/...ts` → główny
`exports` wskazuje src (patrz Droga A).
## Diagnostyka — objaw → przyczyna
| Objaw | Przyczyna |
|---|---|
| `ERR_PNPM_FETCH_404` na `@scope/...` | pakiet nieopublikowany; użyto nazwy rejestrowej zamiast `github:` |
| `ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED` | `prepare` w package.json + brak `onlyBuiltDependencies` |
| `Cannot find module .../src/exports/X.ts` | główny `exports` wskazuje src; instalacja z git nie widzi publishConfig |
| `ENOENT ...ipal-kit-1.0.0.tgz` przy instalacji | self-reference w dependencies pluginu |
| Turbopack „Module not found" mimo pliku na dysku | dwa bloki exports w package.json; Node bierze zły |
| `pnpm add` przeszło, pakietu brak w node_modules | instalacja do złego katalogu, albo przerwana — sprawdź `pwd` |
| stary kod mimo reinstall (1.0.0) | cache; `pnpm store prune` + `rm -rf .next` |
Po instalacji — konfiguracja pluginu: getting-started.md.
+2 -2
View File
@@ -22,7 +22,7 @@ zna Twojej kolekcji — slug podajesz w opcji.
## Front — rozwiązanie ścieżki roli ## Front — rozwiązanie ścieżki roli
```ts ```ts
import { getSystemPagePath } from 'ipal-kit' import { getSystemPagePath } from '@intecion/ipal-kit'
// SiteSettings z locale:'all' + depth:1 (żeby relationship był obiektem, nie ID) // SiteSettings z locale:'all' + depth:1 (żeby relationship był obiektem, nie ID)
const settings = await payload.findGlobal({ const settings = await payload.findGlobal({
@@ -50,6 +50,6 @@ stopce / bannerze cookies bierzesz z `getSystemPagePath({ role: privacyPolicy })
## Role ## Role
```ts ```ts
import { ALL_SYSTEM_PAGE_ROLES } from 'ipal-kit' import { ALL_SYSTEM_PAGE_ROLES } from '@intecion/ipal-kit'
// ['homepage', 'privacyPolicy', 'cookiePolicy'] // ['homepage', 'privacyPolicy', 'cookiePolicy']
``` ```
+2 -2
View File
@@ -11,7 +11,7 @@ Brak — te globale są zawsze budowane przez plugin. Nie ma osobnej opcji.
## Front — odczyt globali ## Front — odczyt globali
```ts ```ts
import { getSiteSettings, getSiteIntegrations } from 'ipal-kit' import { getSiteSettings, getSiteIntegrations } from '@intecion/ipal-kit'
import type { SiteSetting, SiteIntegration } from '@/payload-types' import type { SiteSetting, SiteIntegration } from '@/payload-types'
const payload = await getPayload({ config }) const payload = await getPayload({ config })
@@ -49,6 +49,6 @@ return <Form siteKey={turnstileSiteKey} />
## Niższy poziom: getGlobal ## Niższy poziom: getGlobal
```ts ```ts
import { getGlobal } from 'ipal-kit' import { getGlobal } from '@intecion/ipal-kit'
const data = await getGlobal<MyType>(payload, 'moj-global', { locale: 'pl', depth: 1 }) const data = await getGlobal<MyType>(payload, 'moj-global', { locale: 'pl', depth: 1 })
``` ```
-70
View File
@@ -1,70 +0,0 @@
# Publikacja ipal-kit — checklist
## package.json — wymagane pola
```json
{
"name": "@intecion/ipal-kit", // scoped pod organizację
"version": "1.0.0", // BUMP przy każdej publikacji (semver)
"files": ["dist"], // tylko dist trafia do pakietu (NIE src)
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": { ... }, // wszystkie entry points na ./dist/*
"publishConfig": {
"registry": "https://npm.pkg.github.com",
"exports": { ... } // mirror z ./dist (masz to już zrobione)
},
"scripts": {
"build": "...",
"prepublishOnly": "pnpm clean && pnpm build" // build ZAWSZE przed publish
},
"peerDependencies": { // NIE dependencies — klient już je ma
"payload": "3.84.1",
"next": ">=15",
"react": ">=19"
}
}
```
## Krytyczne przed pierwszą publikacją
- [ ] **peerDependencies zamiast dependencies** dla payload/next/react —
inaczej pakiet zaciąga drugą kopię Payloada i wszystko się sypie.
@payloadcms/plugin-seo i plugin-form-builder też jako peer (klient pinuje).
- [ ] **`"files": ["dist"]`** — bez tego do pakietu trafia src/ (widzieliśmy to
w stack trace). Sam dist.
- [ ] **usuń self-reference** — sprawdź, że w dependencies NIE ma
"ipal-kit": "file:..." (ta zaraza z pnpm add w złym katalogu).
- [ ] **prepublishOnly** buduje przed publikacją — nigdy nie publikuj ręcznie
zbudowanego dist (łatwo o nieaktualny).
- [ ] **bump wersji** — koniec z 1.0.0 na zawsze. Każda publikacja = nowy numer.
To rozwiązuje cały cykl cache/store prune, który gryzł podczas developmentu.
## Publikacja (GitHub Packages)
```bash
# jednorazowo: token GitHuba z prawami write:packages w ~/.npmrc
echo "//npm.pkg.github.com/:_authToken=TWÓJ_TOKEN" >> ~/.npmrc
# przy każdym wydaniu
npm version patch # 1.0.0 → 1.0.1 (albo minor/major)
npm publish
```
## Instalacja u pracownika
```bash
# ~/.npmrc w projekcie albo globalnie
@intecion:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=ICH_TOKEN
# potem normalnie
pnpm add @intecion/ipal-kit
pnpm add @payloadcms/[email protected] @payloadcms/[email protected] \
nodemailer lucide-react slugify server-only
```
## README pakietu
Wskaż na docs/getting-started.md jako pierwszy krok. Pracownik z dostępem do
rejestru + getting-started postawi projekt bez pytania Ciebie o nic.
+6 -6
View File
@@ -60,7 +60,7 @@ Pages, grupa meta), więc nie trzeba mu tego opisywać:
```ts ```ts
// app/(frontend)/[locale]/[[...slug]]/page.tsx // app/(frontend)/[locale]/[[...slug]]/page.tsx
import { createPageMetadata } from 'ipal-kit' import { createPageMetadata } from '@intecion/ipal-kit'
import { i18nConfig } from '@/i18n.config' import { i18nConfig } from '@/i18n.config'
const pageMetadata = createPageMetadata({ const pageMetadata = createPageMetadata({
@@ -93,7 +93,7 @@ Dla tras spoza konwencji: inna kolekcja (blog), własna logika obrazka, inny
global. Klient dostarcza resolvery. global. Klient dostarcza resolvery.
```ts ```ts
import { createMetadataGenerator } from 'ipal-kit' import { createMetadataGenerator } from '@intecion/ipal-kit'
const generate = createMetadataGenerator({ const generate = createMetadataGenerator({
config: i18nConfig, config: i18nConfig,
@@ -148,7 +148,7 @@ Next uruchamia `generateMetadata` i komponent strony niezależnie — bez React
Gdy chcesz pełną kontrolę: Gdy chcesz pełną kontrolę:
```ts ```ts
import { buildMetadata, getLocalizedSlugs } from 'ipal-kit' import { buildMetadata, getLocalizedSlugs } from '@intecion/ipal-kit'
return buildMetadata({ return buildMetadata({
meta: doc.meta, // z plugin-seo meta: doc.meta, // z plugin-seo
@@ -168,7 +168,7 @@ return buildMetadata({
## Pomocnicze ## Pomocnicze
```ts ```ts
import { composeTitle, buildHreflangAlternates } from 'ipal-kit' import { composeTitle, buildHreflangAlternates } from '@intecion/ipal-kit'
composeTitle({ pageTitle: 'O nas', siteName: 'Acme' }) composeTitle({ pageTitle: 'O nas', siteName: 'Acme' })
// 'O nas | Acme' // 'O nas | Acme'
@@ -184,7 +184,7 @@ buildHreflangAlternates({ slugs, config, baseUrl }) // { pl: '...', en: '...' }
eksportowanych, gdybyś budował własny generator metadanych: eksportowanych, gdybyś budował własny generator metadanych:
```ts ```ts
import { readSiteMetaConfig, slugsAcrossLocales } from 'ipal-kit' import { readSiteMetaConfig, slugsAcrossLocales } from '@intecion/ipal-kit'
// nazwa witryny, separator (dopełniony), kolejność, homeSlug — z SiteSettings // nazwa witryny, separator (dopełniony), kolejność, homeSlug — z SiteSettings
const site = await readSiteMetaConfig({ payload, locale }) const site = await readSiteMetaConfig({ payload, locale })
@@ -239,7 +239,7 @@ Pomija: drafty (`_status !== 'published'`) i dokumenty z `meta.noindex`.
Niskopoziomowo (własna trasa zamiast handlera z fabryki): Niskopoziomowo (własna trasa zamiast handlera z fabryki):
```ts ```ts
import { buildSitemapEntries, buildRobots } from 'ipal-kit' import { buildSitemapEntries, buildRobots } from '@intecion/ipal-kit'
const entries = await buildSitemapEntries({ const entries = await buildSitemapEntries({
payload, config: i18nConfig, baseUrl, content: contentConfig, payload, config: i18nConfig, baseUrl, content: contentConfig,
+59
View File
@@ -0,0 +1,59 @@
# slug
Pole slug generowane automatycznie z tytułu, per locale.
## Użycie (kolekcja klienta)
```ts
import { buildSlugField } from '@intecion/ipal-kit'
export const Pages: CollectionConfig = {
slug: 'pages',
fields: [
{ name: 'title', type: 'text', required: true, localized: true },
buildSlugField({ from: 'title' }),
// ...
],
}
```
Zwraca gotowe pole: `slug`, text, localized, required, z hookiem
`beforeValidate`.
## Zachowanie
Slug generuje się z pola źródłowego **tylko gdy jest pusty**. Cokolwiek edytor
wpisze ręcznie, zostaje — także po zmianie tytułu. Zmiana adresu opublikowanej
strony to zerwane linki, więc plugin nigdy nie robi tego sam.
Diakrytyki idą przez `slugify`: „Strona główna" → `strona-glowna`.
## Per locale
Slug jest zlokalizowany — każdy język ma własny. Edytując w PL ustawiasz
`slug.pl`, w EN `slug.en`.
**Konsekwencja:** slug generuje się tylko dla locale, w którym wypełniono tytuł.
Utworzysz stronę po polsku, przełączysz front na `/en/…` — 404, bo `slug.en`
jest puste. Trzeba przełączyć locale w adminie i wpisać tytuł po angielsku.
To ta sama mapa slugów, z której powstają hreflang alternates (patrz seo.md) i
przełącznik języka (patrz i18n.md).
## Opcje
```ts
buildSlugField({
from: 'title', // pole źródłowe
name: 'slug', // nazwa pola (domyślnie 'slug')
required: true, // domyślnie true
})
```
## Pomocnicze
```ts
import { toSlug } from '@intecion/ipal-kit'
toSlug('Strona główna') // 'strona-glowna'
```
+4 -4
View File
@@ -11,11 +11,11 @@ SiteIntegrations (tab Turnstile). Edytor wpisuje klucze w panelu.
## Front — widget (client) ## Front — widget (client)
Import z `ipal-kit/client`. `siteKey` pobierz server-side i przekaż jako prop: Import z `@intecion/ipal-kit/client`. `siteKey` pobierz server-side i przekaż jako prop:
```tsx ```tsx
// server component — pobiera publiczny siteKey z panelu // server component — pobiera publiczny siteKey z panelu
import { getSiteIntegrations } from 'ipal-kit' import { getSiteIntegrations } from '@intecion/ipal-kit'
const { turnstileSiteKey } = await getSiteIntegrations(payload) const { turnstileSiteKey } = await getSiteIntegrations(payload)
// przekaż do swojego client-formularza → widget // przekaż do swojego client-formularza → widget
@@ -23,7 +23,7 @@ const { turnstileSiteKey } = await getSiteIntegrations(payload)
```tsx ```tsx
'use client' 'use client'
import { Turnstile } from 'ipal-kit/client' import { Turnstile } from '@intecion/ipal-kit/client'
import { useState } from 'react' import { useState } from 'react'
function ContactForm({ siteKey }) { function ContactForm({ siteKey }) {
@@ -44,7 +44,7 @@ Widget ładuje skrypt Turnstile sam (bez `next/script`), zwraca token przez
## Front — verify (server) ## Front — verify (server)
```ts ```ts
import { verifyTurnstile } from 'ipal-kit/server' import { verifyTurnstile } from '@intecion/ipal-kit/server'
const ok = await verifyTurnstile({ token, payload, ip }) const ok = await verifyTurnstile({ token, payload, ip })
if (!ok) { if (!ok) {