From 90cbc4370f03a19b9f73b162e0ebc19b229b6090 Mon Sep 17 00:00:00 2001 From: rasm-its Date: Tue, 4 Aug 2026 19:37:23 +0200 Subject: [PATCH] updated docs --- README.md | 62 ++++++++++++++++---- docs/README.md | 24 ++++---- docs/access.md | 8 +-- docs/analytics.md | 75 ++++++++++++++++++++++++ docs/blocks.md | 4 +- docs/consent.md | 12 ++-- docs/content.md | 12 ++-- docs/email.md | 29 +++++++++- docs/forms.md | 4 +- docs/frontend-setup.md | 14 ++--- docs/getting-started.md | 35 +++++------ docs/i18n.md | 8 +-- docs/install.md | 118 -------------------------------------- docs/pages.md | 4 +- docs/payload-helpers.md | 4 +- docs/publish-checklist.md | 70 ---------------------- docs/seo.md | 12 ++-- docs/slug.md | 59 +++++++++++++++++++ docs/turnstile.md | 8 +-- 19 files changed, 290 insertions(+), 272 deletions(-) create mode 100644 docs/analytics.md delete mode 100644 docs/install.md delete mode 100644 docs/publish-checklist.md create mode 100644 docs/slug.md diff --git a/README.md b/README.md index 93973c3..60fda87 100644 --- a/README.md +++ b/README.md @@ -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 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 -# add to ~/.zshrc (or ~/.bashrc) -export GITEA_TOKEN=paste_your_token_here +echo 'export GITEA_TOKEN=paste_your_token_here' >> ~/.zshrc +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 package and build nothing locally. -**1. Configure the registry.** Create an `.npmrc` file in your project directory -(or add these two lines to `~/.npmrc` to make it work globally): +**1. Configure the registry.** Add these two lines to an `.npmrc` file: ``` @intecion:registry=https://git.intecion.net/api/packages/IntecionSoftware/npm/ //git.intecion.net/api/packages/IntecionSoftware/npm/:_authToken=${GITEA_TOKEN} ``` -Because the token lives in the `${GITEA_TOKEN}` variable, this file holds no -secret — you can safely commit it. +You can put this file **in the project directory** (applies to that project) or +**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:** @@ -141,7 +180,7 @@ nobody gets stuck on a stale one from the cache. Everything lives in the **[docs/](./docs)** directory. To get started: - **[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 --- @@ -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 | | 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 -team that maintains the plugin. \ No newline at end of file +If you get stuck, check the troubleshooting table above, see +**[docs/publishing.md](./docs/publishing.md)** for distribution issues, or message +the team that maintains the plugin. \ No newline at end of file diff --git a/docs/README.md b/docs/README.md index 8603689..ad3b491 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,8 +1,11 @@ # 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 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 (jednolinijkowe re-eksporty), konfiguracja front. -## Instalacja i wpięcie +## Konfiguracja i wpięcie ### Wymagane zależności @@ -62,7 +65,7 @@ importMap`). ```ts // payload.config.ts -import { ipalKit } from 'ipal-kit' +import { ipalKit } from '@intecion/ipal-kit' export default buildConfig({ // ... @@ -88,11 +91,11 @@ export default buildConfig({ | Import | Zawiera | Kontekst | |---|---|---| -| `ipal-kit` | logika server-safe, plugin, helpery | server / config | -| `ipal-kit/server` | runtime server-only (sendEmail, verifyTurnstile, submitForm) | Server Actions / route handlers | -| `ipal-kit/client` | komponenty client (consent, Turnstile, Analytics) | `'use client'` | -| `ipal-kit/rsc` | RenderBlocks (RSC) | server component | -| `ipal-kit/next/middleware` | locale middleware | middleware.ts | +| `@intecion/ipal-kit` | logika server-safe, plugin, helpery | server / config | +| `@intecion/ipal-kit/server` | runtime server-only (sendEmail, verifyTurnstile, submitForm) | Server Actions / route handlers | +| `@intecion/ipal-kit/client` | komponenty client (consent, Turnstile, Analytics) | `'use client'` | +| `@intecion/ipal-kit/rsc` | RenderBlocks (RSC) | server component | +| `@intecion/ipal-kit/next/middleware` | locale middleware | middleware.ts / proxy.ts | ## Moduły @@ -114,6 +117,7 @@ export default buildConfig({ Nowy projekt krok po kroku: [getting-started.md](./getting-started.md) Referencja wdrożenia frontu: [frontend-setup.md](./frontend-setup.md) +Wydawanie nowych wersji wtyczki: [publishing.md](./publishing.md) ## 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 (admin-only). Odczyt server-side przez Local API. 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ą `` (np. wygenerowany `SiteSetting`), bo plugin nie zna typów projektu. \ No newline at end of file diff --git a/docs/access.md b/docs/access.md index 7651ff6..4f6bc61 100644 --- a/docs/access.md +++ b/docs/access.md @@ -25,7 +25,7 @@ zmieniać role) do wskazanej kolekcji. Kolekcja należy do Ciebie ## Predykaty (front / hooki / access) ```ts -import { isAdmin, isEditor, hasMinimumRole } from 'ipal-kit' +import { isAdmin, isEditor, hasMinimumRole } from '@intecion/ipal-kit' isAdmin(user) // admin? 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) ```ts -import { adminOnly, adminOrEditor, adminOrSelf, requireRole, authenticated } from 'ipal-kit' +import { adminOnly, adminOrEditor, adminOrSelf, requireRole, authenticated } from '@intecion/ipal-kit' export const Articles = { slug: 'articles', @@ -62,7 +62,7 @@ access: { update: requireRole('editor') } ### Field-level (zwracają boolean) ```ts -import { adminOnlyField, adminOrEditorField, requireRoleField } from 'ipal-kit' +import { adminOnlyField, adminOrEditorField, requireRoleField } from '@intecion/ipal-kit' { name: 'internalNote', @@ -74,6 +74,6 @@ import { adminOnlyField, adminOrEditorField, requireRoleField } from 'ipal-kit' ## Hierarchia ```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 ``` \ No newline at end of file diff --git a/docs/analytics.md b/docs/analytics.md new file mode 100644 index 0000000..37c55d3 --- /dev/null +++ b/docs/analytics.md @@ -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 ( + + {children} + + + + ) +} +``` + +`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ą. \ No newline at end of file diff --git a/docs/blocks.md b/docs/blocks.md index 00d1a68..785735d 100644 --- a/docs/blocks.md +++ b/docs/blocks.md @@ -12,10 +12,10 @@ Brak opcji w payload.config — bloki definiujesz w swoich kolekcjach ## Front — RenderBlocks -Import z `ipal-kit/rsc` (to komponent serwerowy): +Import z `@intecion/ipal-kit/rsc` (to komponent serwerowy): ```tsx -import { RenderBlocks } from 'ipal-kit/rsc' +import { RenderBlocks } from '@intecion/ipal-kit/rsc' // Twój registry: blockType → komponent (komponenty są Twoje) import { Hero } from '@/blocks/Hero' diff --git a/docs/consent.md b/docs/consent.md index 5546750..ac6b8dc 100644 --- a/docs/consent.md +++ b/docs/consent.md @@ -23,12 +23,12 @@ localized. Link do polityki prywatności bierze się z system pages ## Front — Provider + banner Provider owija aplikację, banner i button renderują się same. Import z -`ipal-kit/client`: +`@intecion/ipal-kit/client`: ```tsx // app/(frontend)/[locale]/layout.tsx -import { ConsentProvider, CookieBanner, CookieButton } from 'ipal-kit/client' -import { getConsentTexts } from 'ipal-kit' +import { ConsentProvider, CookieBanner, CookieButton } from '@intecion/ipal-kit/client' +import { getConsentTexts } from '@intecion/ipal-kit' export default async function Layout({ children, params }) { const { locale } = await params @@ -66,15 +66,15 @@ Domyślne klasy Tailwind można nadpisać przez `classNames`: ## Gating skryptów wg zgody ```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 ``` Logika (kategorie, storage, parsowanie) też jest dostępna server-safe z -`ipal-kit`: +`@intecion/ipal-kit`: ```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 ``` diff --git a/docs/content.md b/docs/content.md index 54cd9c3..ff82c49 100644 --- a/docs/content.md +++ b/docs/content.md @@ -26,7 +26,7 @@ zarządza tylko przypisaniem archiwum i jego adresem. ```ts // 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 = { collections: [ @@ -59,7 +59,7 @@ Serce modułu. Catch-all `[[...slug]]` łapie wszystko, a `resolveRoute` mówi, dana ścieżka jest: ```ts -import { resolveRoute } from 'ipal-kit' +import { resolveRoute } from '@intecion/ipal-kit' const route = await resolveRoute({ payload, @@ -91,7 +91,7 @@ Zamiast pisać cache'owane wrappery w każdym projekcie: ```ts // src/lib/content.ts -import { createContentHelpers } from 'ipal-kit' +import { createContentHelpers } from '@intecion/ipal-kit' import config from '@/payload.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 ```ts -import { buildEntryPath, buildArchivePath, parsePageParam } from 'ipal-kit' +import { buildEntryPath, buildArchivePath, parsePageParam } from '@intecion/ipal-kit' buildEntryPath({ locale: 'pl', archiveSlug: 'artykuly', entrySlug: 'moj-post' }) // '/pl/artykuly/moj-post' @@ -201,6 +201,6 @@ import { parsePageParam, createContentHelpers, archiveFieldName, -} from 'ipal-kit' -import type { ContentOption, ResolvedRoute, ArchiveEntries } from 'ipal-kit' +} from '@intecion/ipal-kit' +import type { ContentOption, ResolvedRoute, ArchiveEntries } from '@intecion/ipal-kit' ``` \ No newline at end of file diff --git a/docs/email.md b/docs/email.md index 86711d2..a894b1f 100644 --- a/docs/email.md +++ b/docs/email.md @@ -19,7 +19,7 @@ Brak opcji — SMTP (host, port, user, password, from) jest w SiteIntegrations ## Front — sendEmail (server) ```ts -import { sendEmail } from 'ipal-kit/server' +import { sendEmail } from '@intecion/ipal-kit/server' const result = await sendEmail({ payload, @@ -48,4 +48,29 @@ if (result.sent) { Reset hasła / weryfikacja email idą przez wbudowany mechanizm Payload (`config.email`), którego ten moduł **nie** konfiguruje (celowo — wymagałby SMTP w env). Jeśli ich potrzebujesz, to osobna konfiguracja adaptera przy -`buildConfig`. \ No newline at end of file +`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ą. \ No newline at end of file diff --git a/docs/forms.md b/docs/forms.md index cf7fcad..5406003 100644 --- a/docs/forms.md +++ b/docs/forms.md @@ -31,7 +31,7 @@ Zwykle w Server Action wywoływanej przez formularz. HTML obu maili składasz na froncie (Twój wygląd): ```ts -import { submitForm } from 'ipal-kit/server' +import { submitForm } from '@intecion/ipal-kit/server' const result = await submitForm({ payload, @@ -86,7 +86,7 @@ wiadomości skonfigurowane przez edytora (Forms → formularz → Emails), przez `payload.sendEmail`. Żeby wyszły, config musi mieć adapter: ```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 diff --git a/docs/frontend-setup.md b/docs/frontend-setup.md index 84edad7..ae45bf7 100644 --- a/docs/frontend-setup.md +++ b/docs/frontend-setup.md @@ -22,7 +22,7 @@ export default { plugins: { '@tailwindcss/postcss': {} } } W globalnym CSS (np. app/(frontend)/styles.css): ```css @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 @@ -107,7 +107,7 @@ też jedno źródło. // src/middleware.ts import { NextResponse } 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' const localeMiddleware = createLocaleMiddleware({ config: i18nConfig }) @@ -140,8 +140,8 @@ 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' +import { getConsentTexts } from '@intecion/ipal-kit' +import { ConsentProvider, CookieBanner, CookieButton } from '@intecion/ipal-kit/client' const texts = await getConsentTexts({ config, locale, payload, privacyPolicy }) // {children} @@ -173,9 +173,9 @@ const enhanceProps = ({ block }) => { - FormRenderer (client) — renderuje pola form-buildera (text/email/select/ 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) -- 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 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: ```ts -import { ipalKit, panelSmtpAdapter } from 'ipal-kit' +import { ipalKit, panelSmtpAdapter } from '@intecion/ipal-kit' export default buildConfig({ email: panelSmtpAdapter(), diff --git a/docs/getting-started.md b/docs/getting-started.md index 6579825..8865feb 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -1,8 +1,8 @@ # Nowy projekt — krok po kroku -> **Instalacja:** najszybciej `pnpm add github:rasm-its/ipal-kit`. Pełne drogi -> (github / rejestr / tarball) i diagnostyka błędów — install.md. - +> **Instalacja pluginu** (token Gitea, rejestr vs git) jest opisana w głównym +> [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 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 ``` -## 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 -pnpm add ipal-kit pnpm add @payloadcms/plugin-seo@3.84.1 @payloadcms/plugin-form-builder@3.84.1 \ nodemailer lucide-react slugify server-only ``` @@ -72,7 +75,7 @@ export const i18nConfig = { ## 4. payload.config.ts ```ts -import { ipalKit, panelSmtpAdapter } from 'ipal-kit' +import { ipalKit, panelSmtpAdapter } from '@intecion/ipal-kit' import { i18nConfig } from '@/i18n.config' import { Pages } from '@/collections/Pages' @@ -101,7 +104,7 @@ export default buildConfig({ ```ts // src/collections/Pages.ts import type { CollectionConfig } from 'payload' -import { buildSlugField } from 'ipal-kit' +import { buildSlugField } from '@intecion/ipal-kit' import { ContentBlock } from '@/blocks/Content/config' export const Pages: CollectionConfig = { @@ -151,7 +154,7 @@ export function ContentBlockComponent({ heading, body }: { heading?: string; bod ```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' export const blockRegistry: BlockComponentMap = { @@ -177,7 +180,7 @@ export default { plugins: { '@tailwindcss/postcss': {} } } ```css /* src/app/(frontend)/styles.css — na górze */ @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 @@ -190,7 +193,7 @@ niego klasy komponentów pluginu nie powstaną i banner wyrenderuje się goły. // src/middleware.ts import { NextResponse } 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' const localeMiddleware = createLocaleMiddleware({ config: i18nConfig }) @@ -294,8 +297,8 @@ tablicy (`slug.join is not a function`) i nie łapią samego `/pl`. ```tsx // src/app/(frontend)/[locale]/layout.tsx import { notFound } from 'next/navigation' -import { getConsentTexts, getAnalyticsConfig } from 'ipal-kit' -import { ConsentProvider, CookieBanner, CookieButton, Analytics } from 'ipal-kit/client' +import { getConsentTexts, getAnalyticsConfig } from '@intecion/ipal-kit' +import { ConsentProvider, CookieBanner, CookieButton, Analytics } from '@intecion/ipal-kit/client' import { i18nConfig } from '@/i18n.config' import { getCachedPayload, getSettings } from '@/lib/payload' import { getConfiguredLocales } from '@/lib/locales' @@ -347,8 +350,8 @@ export async function generateStaticParams() { // src/app/(frontend)/[locale]/[[...slug]]/page.tsx import { notFound } from 'next/navigation' import type { Metadata } from 'next' -import { RenderBlocks } from 'ipal-kit/rsc' -import { createPageMetadata } from 'ipal-kit' +import { RenderBlocks } from '@intecion/ipal-kit/rsc' +import { createPageMetadata } from '@intecion/ipal-kit' import { i18nConfig } from '@/i18n.config' import { blockRegistry } from '@/blocks/registry' 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: ```ts -import type { ContentOption } from 'ipal-kit' +import type { ContentOption } from '@intecion/ipal-kit' export const contentConfig: ContentOption = { 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` | | `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 | | `slug.join is not a function` | katalog `[slug]` zamiast `[[...slug]]` | | `/pl` → 404 | Homepage nieustawiony w System Pages | diff --git a/docs/i18n.md b/docs/i18n.md index c50465e..e358639 100644 --- a/docs/i18n.md +++ b/docs/i18n.md @@ -34,7 +34,7 @@ import { getLocaleCodes, getDefaultLocale, isValidLocale, getLocaleDefinition, negotiateLocale, buildLocalizedPath, switchLocalePath, getLocalizedSlugs, LOCALE_COOKIE_NAME, -} from 'ipal-kit' +} from '@intecion/ipal-kit' 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 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. ## Middleware ```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 { createLocaleMiddleware, DEFAULT_MIDDLEWARE_MATCHER } from 'ipal-kit/next/middleware' +import { createLocaleMiddleware, DEFAULT_MIDDLEWARE_MATCHER } from '@intecion/ipal-kit/next/middleware' const i18nConfig = { defaultLocale: 'pl', diff --git a/docs/install.md b/docs/install.md deleted file mode 100644 index d3615ed..0000000 --- a/docs/install.md +++ /dev/null @@ -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/plugin-seo@3.84.1 @payloadcms/plugin-form-builder@3.84.1 \ - 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. \ No newline at end of file diff --git a/docs/pages.md b/docs/pages.md index 019b876..1fac1f4 100644 --- a/docs/pages.md +++ b/docs/pages.md @@ -22,7 +22,7 @@ zna Twojej kolekcji — slug podajesz w opcji. ## Front — rozwiązanie ścieżki roli ```ts -import { getSystemPagePath } from 'ipal-kit' +import { getSystemPagePath } from '@intecion/ipal-kit' // SiteSettings z locale:'all' + depth:1 (żeby relationship był obiektem, nie ID) const settings = await payload.findGlobal({ @@ -50,6 +50,6 @@ stopce / bannerze cookies bierzesz z `getSystemPagePath({ role: privacyPolicy }) ## Role ```ts -import { ALL_SYSTEM_PAGE_ROLES } from 'ipal-kit' +import { ALL_SYSTEM_PAGE_ROLES } from '@intecion/ipal-kit' // ['homepage', 'privacyPolicy', 'cookiePolicy'] ``` \ No newline at end of file diff --git a/docs/payload-helpers.md b/docs/payload-helpers.md index 598a2ce..272ce1d 100644 --- a/docs/payload-helpers.md +++ b/docs/payload-helpers.md @@ -11,7 +11,7 @@ Brak — te globale są zawsze budowane przez plugin. Nie ma osobnej opcji. ## Front — odczyt globali ```ts -import { getSiteSettings, getSiteIntegrations } from 'ipal-kit' +import { getSiteSettings, getSiteIntegrations } from '@intecion/ipal-kit' import type { SiteSetting, SiteIntegration } from '@/payload-types' const payload = await getPayload({ config }) @@ -49,6 +49,6 @@ return
## Niższy poziom: getGlobal ```ts -import { getGlobal } from 'ipal-kit' +import { getGlobal } from '@intecion/ipal-kit' const data = await getGlobal(payload, 'moj-global', { locale: 'pl', depth: 1 }) ``` \ No newline at end of file diff --git a/docs/publish-checklist.md b/docs/publish-checklist.md deleted file mode 100644 index c268ade..0000000 --- a/docs/publish-checklist.md +++ /dev/null @@ -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/plugin-seo@3.84.1 @payloadcms/plugin-form-builder@3.84.1 \ - 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. \ No newline at end of file diff --git a/docs/seo.md b/docs/seo.md index 258085a..952236f 100644 --- a/docs/seo.md +++ b/docs/seo.md @@ -60,7 +60,7 @@ Pages, grupa meta), więc nie trzeba mu tego opisywać: ```ts // app/(frontend)/[locale]/[[...slug]]/page.tsx -import { createPageMetadata } from 'ipal-kit' +import { createPageMetadata } from '@intecion/ipal-kit' import { i18nConfig } from '@/i18n.config' const pageMetadata = createPageMetadata({ @@ -93,7 +93,7 @@ Dla tras spoza konwencji: inna kolekcja (blog), własna logika obrazka, inny global. Klient dostarcza resolvery. ```ts -import { createMetadataGenerator } from 'ipal-kit' +import { createMetadataGenerator } from '@intecion/ipal-kit' const generate = createMetadataGenerator({ config: i18nConfig, @@ -148,7 +148,7 @@ Next uruchamia `generateMetadata` i komponent strony niezależnie — bez React Gdy chcesz pełną kontrolę: ```ts -import { buildMetadata, getLocalizedSlugs } from 'ipal-kit' +import { buildMetadata, getLocalizedSlugs } from '@intecion/ipal-kit' return buildMetadata({ meta: doc.meta, // z plugin-seo @@ -168,7 +168,7 @@ return buildMetadata({ ## Pomocnicze ```ts -import { composeTitle, buildHreflangAlternates } from 'ipal-kit' +import { composeTitle, buildHreflangAlternates } from '@intecion/ipal-kit' composeTitle({ pageTitle: 'O nas', siteName: 'Acme' }) // 'O nas | Acme' @@ -184,7 +184,7 @@ buildHreflangAlternates({ slugs, config, baseUrl }) // { pl: '...', en: '...' } eksportowanych, gdybyś budował własny generator metadanych: ```ts -import { readSiteMetaConfig, slugsAcrossLocales } from 'ipal-kit' +import { readSiteMetaConfig, slugsAcrossLocales } from '@intecion/ipal-kit' // nazwa witryny, separator (dopełniony), kolejność, homeSlug — z SiteSettings 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): ```ts -import { buildSitemapEntries, buildRobots } from 'ipal-kit' +import { buildSitemapEntries, buildRobots } from '@intecion/ipal-kit' const entries = await buildSitemapEntries({ payload, config: i18nConfig, baseUrl, content: contentConfig, diff --git a/docs/slug.md b/docs/slug.md new file mode 100644 index 0000000..c8f4189 --- /dev/null +++ b/docs/slug.md @@ -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' +``` \ No newline at end of file diff --git a/docs/turnstile.md b/docs/turnstile.md index 65842ab..7f3593e 100644 --- a/docs/turnstile.md +++ b/docs/turnstile.md @@ -11,11 +11,11 @@ SiteIntegrations (tab Turnstile). Edytor wpisuje klucze w panelu. ## 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 // server component — pobiera publiczny siteKey z panelu -import { getSiteIntegrations } from 'ipal-kit' +import { getSiteIntegrations } from '@intecion/ipal-kit' const { turnstileSiteKey } = await getSiteIntegrations(payload) // przekaż do swojego client-formularza → widget @@ -23,7 +23,7 @@ const { turnstileSiteKey } = await getSiteIntegrations(payload) ```tsx 'use client' -import { Turnstile } from 'ipal-kit/client' +import { Turnstile } from '@intecion/ipal-kit/client' import { useState } from 'react' function ContactForm({ siteKey }) { @@ -44,7 +44,7 @@ Widget ładuje skrypt Turnstile sam (bez `next/script`), zwraca token przez ## Front — verify (server) ```ts -import { verifyTurnstile } from 'ipal-kit/server' +import { verifyTurnstile } from '@intecion/ipal-kit/server' const ok = await verifyTurnstile({ token, payload, ip }) if (!ok) {