updated docs
This commit is contained in:
@@ -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
@@ -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
@@ -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
|
||||||
```
|
```
|
||||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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']
|
||||||
```
|
```
|
||||||
@@ -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 })
|
||||||
```
|
```
|
||||||
@@ -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
@@ -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,
|
||||||
|
|||||||
@@ -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
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user