fix: resolve contact form placeholders, restore process block layout, and fix testimonials carousel scroll logic

This commit is contained in:
MichalC
2026-09-17 22:25:25 +02:00
parent 7cc68b9f36
commit ab4c5c83dd
91 changed files with 4790 additions and 319 deletions
+11
View File
@@ -15,6 +15,13 @@ Jeśli błąd się powtórzy, upewnij się, że nie działają inne ukryte proce
W przeszłości wadliwe skrypty (np. wczesna wersja `migrate_faq.ts`) przypisały do tej strony cudze bloki, co spowodowało całkowite nadpisanie układu (usunięcie `pageHeader` i `contactInfo`), a następnie skrypt "naprawczy" (`fix_kontakt.ts`) wyczyścił ten uszkodzony layout, zostawiając stronę z `layout: []` we wszystkich językach. W przeszłości wadliwe skrypty (np. wczesna wersja `migrate_faq.ts`) przypisały do tej strony cudze bloki, co spowodowało całkowite nadpisanie układu (usunięcie `pageHeader` i `contactInfo`), a następnie skrypt "naprawczy" (`fix_kontakt.ts`) wyczyścił ten uszkodzony layout, zostawiając stronę z `layout: []` we wszystkich językach.
Zawsze sprawdzaj, czy modyfikując lub filtrując tablicę bloków dla jednej konkretnej strony (np. FAQ o ID 6), nie nadpisujesz omyłkowo strony sąsiedniej (np. Kontakt o ID 5). Zawsze sprawdzaj, czy modyfikując lub filtrując tablicę bloków dla jednej konkretnej strony (np. FAQ o ID 6), nie nadpisujesz omyłkowo strony sąsiedniej (np. Kontakt o ID 5).
## Bezpieczeństwo Skryptów Migracyjnych (Zasada Punktowych Modyfikacji Layoutu)
**Zasada Ogólna:** Skrypty migracyjne modyfikujące `layout` strony MAJĄ czytać istniejącą tablicę i zmieniać/dopisywać punktowo — nigdy nie nadpisywać całej tablicy stałą wartością z pliku źródłowego typu `content-snapshot.json`, który mógł się zdezaktualizować od czasu utworzenia.
To dotyczy wszystkich stron, by uniknąć incydentów, gdzie przywracana zawartość ze snapshotu cicho nadpisuje i usuwa nowo wypracowane bloki (np. usunięcie gotowego formularza `contactSection` przez stary `contactInfo`).
**Zasada Ochrony Wewnętrznych ID:** Skrypty migracyjne modyfikujące bloki w Payloadzie (szczególnie pola typu `layout` lub `blocks` z wewnętrznymi tablicami np. `steps`) NIGDY nie mogą używać funkcji rekurencyjnie usuwających `id` (takich jak `deepStripIds`) przed wysłaniem danych przez `payload.update()`. Payload CMS bezwzględnie usunie wewnętrzne elementy tablic bloków (np. kroki na osi czasu), jeśli zostaną zaktualizowane bez ich oryginalnych `id`. Zamiast kopiować i "czyścić" całe tablice bloków, należy wyciągnąć konkretny blok, zmienić w nim tylko potrzebne pola i zostawić resztę struktury i wszystkie oryginalne `id` nienaruszone.
## Protokół Testowy: Hero i PageHeader (Responsywność i Motywy) - ZAKOŃCZONY ## Protokół Testowy: Hero i PageHeader (Responsywność i Motywy) - ZAKOŃCZONY
### Rozwiązanie "Pustego Czarnego Obszaru" ### Rozwiązanie "Pustego Czarnego Obszaru"
@@ -36,3 +43,7 @@ Macierz wynikowa:
10. `/de/uber-uns` (PageHeader) - Light: OK | Dark: OK 10. `/de/uber-uns` (PageHeader) - Light: OK | Dark: OK
Brak błędów na jakiejkolwiek stronie. Tło widoczne. Text-occlusion: wyeliminowane. Kontrast > 4.5:1. Brak błędów na jakiejkolwiek stronie. Tło widoczne. Text-occlusion: wyeliminowane. Kontrast > 4.5:1.
### Ograniczenia pluginów i długu technicznego
- **Formularz kontaktowy i pole Select**: Form-builder z `ipal-kit` wspiera pola `select`, ale opcje po zapisie są statyczne. Aktualna lista 7 usług została wygenerowana ze źródła (bloki `ServicesDetailed` na stronach) za pomocą jednorazowego skryptu migracyjnego, jednak nie ma mechanizmu stałej autosynchronizacji, ponieważ usługi są zdefiniowane jako bloki na stronie, a nie osobna kolekcja w bazie. **Uwaga dla redaktora/administratora:** Ten stan nadal wymaga pamięci operacyjnej — jeśli w przyszłości na stronie dodana zostanie nowa, 8. usługa, trzeba pamiętać, aby RĘCZNIE dodać ją również jako opcję w formularzu kontaktowym w panelu CMS (albo ponownie uruchomić skrypt aktualizujący).
+45
View File
@@ -0,0 +1,45 @@
[PODEJRZANY DUPLIKAT] Page(home).layout[3].items[1].stat | PL: "100%" | DE: "100%"
[PODEJRZANY DUPLIKAT] Page(home).layout[3].items[2].stat | PL: "§4 Nr. 11" | DE: "§4 Nr. 11"
[PODEJRZANY DUPLIKAT] Page(home).layout[7].testimonials[1].author | PL: "Anna, Berlin" | DE: "Anna, Berlin"
[PODEJRZANY DUPLIKAT] Page(home).layout[7].testimonials[2].author | PL: "Krzysztof, Dortmund" | DE: "Krzysztof, Dortmund"
[PODEJRZANY DUPLIKAT] Page(home).layout[9].eyebrow | PL: "Kontakt" | DE: "Kontakt"
[PODEJRZANY DUPLIKAT] Page(uslugi).layout[3].eyebrow | PL: "Kontakt" | DE: "Kontakt"
[PODEJRZANY DUPLIKAT] Page(jak-sie-umowic).layout[2].address | PL: "DO POTWIERDZENIA PRZEZ KLIENTA" | DE: "DO POTWIERDZENIA PRZEZ KLIENTA"
[PODEJRZANY DUPLIKAT] Page(o-nas).layout[1].title | PL: "Janusz Kędzierski" | DE: "Janusz Kędzierski"
[PODEJRZANY DUPLIKAT] Page(faq).title | PL: "FAQ" | DE: "FAQ"
[PODEJRZANY DUPLIKAT] Page(faq).layout[0].ctaUrl | PL: "#" | DE: "#"
[PODEJRZANY DUPLIKAT] Page(kontakt).title | PL: "Kontakt" | DE: "Kontakt"
[PODEJRZANY DUPLIKAT] Page(kontakt).layout[0].title | PL: "Kontakt" | DE: "Kontakt"
[PODEJRZANY DUPLIKAT] Page(kontakt).layout[0].subtitle | PL: "Masz pytania dotyczące rozliczenia podatkowego w N" | DE: "Masz pytania dotyczące rozliczenia podatkowego w N"
[PODEJRZANY DUPLIKAT] Page(kontakt).layout[1].address | PL: "DO POTWIERDZENIA PRZEZ KLIENTA" | DE: "DO POTWIERDZENIA PRZEZ KLIENTA"
[PODEJRZANY DUPLIKAT] Page(kontakt).layout[1].hours | PL: "Pon-Pt: 9:00 - 17:00 Po wcześniejszym umówieniu" | DE: "Pon-Pt: 9:00 - 17:00 Po wcześniejszym umówieniu"
[PODEJRZANY DUPLIKAT] Page(kontakt).layout[1].form.title | PL: "Kontaktformular" | DE: "Kontaktformular"
[PODEJRZANY DUPLIKAT] Page(impressum).title | PL: "Impressum" | DE: "Impressum"
[PODEJRZANY DUPLIKAT] Global(header).navigation[0].page.layout[3].eyebrow | PL: "Kontakt" | DE: "Kontakt"
[PODEJRZANY DUPLIKAT] Global(header).navigation[1].page.layout[2].address | PL: "DO POTWIERDZENIA PRZEZ KLIENTA" | DE: "DO POTWIERDZENIA PRZEZ KLIENTA"
[PODEJRZANY DUPLIKAT] Global(header).navigation[2].page.layout[1].title | PL: "Janusz Kędzierski" | DE: "Janusz Kędzierski"
[PODEJRZANY DUPLIKAT] Global(header).navigation[3].anchor | PL: "faq" | DE: "faq"
[PODEJRZANY DUPLIKAT] Global(header).navigation[4].page.title | PL: "Kontakt" | DE: "Kontakt"
[PODEJRZANY DUPLIKAT] Global(header).navigation[4].page.layout[0].title | PL: "Kontakt" | DE: "Kontakt"
[PODEJRZANY DUPLIKAT] Global(header).navigation[4].page.layout[0].subtitle | PL: "Masz pytania dotyczące rozliczenia podatkowego w N" | DE: "Masz pytania dotyczące rozliczenia podatkowego w N"
[PODEJRZANY DUPLIKAT] Global(header).navigation[4].page.layout[1].address | PL: "DO POTWIERDZENIA PRZEZ KLIENTA" | DE: "DO POTWIERDZENIA PRZEZ KLIENTA"
[PODEJRZANY DUPLIKAT] Global(header).navigation[4].page.layout[1].hours | PL: "Pon-Pt: 9:00 - 17:00 Po wcześniejszym umówieniu" | DE: "Pon-Pt: 9:00 - 17:00 Po wcześniejszym umówieniu"
[PODEJRZANY DUPLIKAT] Global(header).navigation[4].page.layout[1].form.title | PL: "Kontaktformular" | DE: "Kontaktformular"
[PODEJRZANY DUPLIKAT] Global(footer).navigation[0].page.layout[3].eyebrow | PL: "Kontakt" | DE: "Kontakt"
[PODEJRZANY DUPLIKAT] Global(footer).navigation[1].page.layout[2].address | PL: "DO POTWIERDZENIA PRZEZ KLIENTA" | DE: "DO POTWIERDZENIA PRZEZ KLIENTA"
[PODEJRZANY DUPLIKAT] Global(footer).navigation[2].page.layout[1].title | PL: "Janusz Kędzierski" | DE: "Janusz Kędzierski"
[PODEJRZANY DUPLIKAT] Global(footer).navigation[3].page.title | PL: "Kontakt" | DE: "Kontakt"
[PODEJRZANY DUPLIKAT] Global(footer).navigation[3].page.layout[0].title | PL: "Kontakt" | DE: "Kontakt"
[PODEJRZANY DUPLIKAT] Global(footer).navigation[3].page.layout[0].subtitle | PL: "Masz pytania dotyczące rozliczenia podatkowego w N" | DE: "Masz pytania dotyczące rozliczenia podatkowego w N"
[PODEJRZANY DUPLIKAT] Global(footer).navigation[3].page.layout[1].address | PL: "DO POTWIERDZENIA PRZEZ KLIENTA" | DE: "DO POTWIERDZENIA PRZEZ KLIENTA"
[PODEJRZANY DUPLIKAT] Global(footer).navigation[3].page.layout[1].hours | PL: "Pon-Pt: 9:00 - 17:00 Po wcześniejszym umówieniu" | DE: "Pon-Pt: 9:00 - 17:00 Po wcześniejszym umówieniu"
[PODEJRZANY DUPLIKAT] Global(footer).navigation[3].page.layout[1].form.title | PL: "Kontaktformular" | DE: "Kontaktformular"
[PODEJRZANY DUPLIKAT] Global(footer).navigation[6].page.title | PL: "Impressum" | DE: "Impressum"
[PODEJRZANY DUPLIKAT] Global(company).address | PL: "DO POTWIERDZENIA PRZEZ KLIENTA" | DE: "DO POTWIERDZENIA PRZEZ KLIENTA"
[PODEJRZANY DUPLIKAT] Global(company).hours | PL: "Pon-Pt: 9:00 - 17:00 Po wcześniejszym umówieniu" | DE: "Pon-Pt: 9:00 - 17:00 Po wcześniejszym umówieniu"
[PODEJRZANY DUPLIKAT] Global(site-settings).homepage.layout[3].items[1].stat | PL: "100%" | DE: "100%"
[PODEJRZANY DUPLIKAT] Global(site-settings).homepage.layout[3].items[2].stat | PL: "§4 Nr. 11" | DE: "§4 Nr. 11"
[PODEJRZANY DUPLIKAT] Global(site-settings).homepage.layout[7].testimonials[1].author | PL: "Anna, Berlin" | DE: "Anna, Berlin"
[PODEJRZANY DUPLIKAT] Global(site-settings).homepage.layout[7].testimonials[2].author | PL: "Krzysztof, Dortmund" | DE: "Krzysztof, Dortmund"
[PODEJRZANY DUPLIKAT] Global(site-settings).homepage.layout[9].eyebrow | PL: "Kontakt" | DE: "Kontakt"
[PODEJRZANY DUPLIKAT] Form(Kontaktformular).title | PL: "Kontaktformular" | DE: "Kontaktformular"
File diff suppressed because one or more lines are too long
+403
View File
@@ -0,0 +1,403 @@
[
{
"antipattern": "oversized-h1",
"name": "Oversized hero headline",
"description": "A full-sentence headline set at display size ends up dominating the viewport, leaving no room for anything else above the fold. A punchy one- or two-word headline at that size is fine — the problem is a long headline blown up too large. Set long headlines smaller, or tighten the copy.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/de",
"line": 0,
"snippet": "72px h1, 84 chars, 48vh \"Steuererstattung aus Deutschland sichernKompetente Beratung \""
},
{
"antipattern": "gpt-thin-border-wide-shadow",
"name": "Hairline border with wide shadow",
"description": "A hairline border paired with a wide, diffuse shadow is a recurring generated-UI signature. Commit to one — a defined edge or a soft elevation — rather than both at once.",
"severity": "advisory",
"category": "slop",
"file": "http://localhost:3000/de",
"line": 0,
"snippet": "1px border + 50px shadow blur",
"advisory": true
},
{
"antipattern": "nested-cards",
"name": "Nested cards",
"description": "Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/de",
"line": 0,
"snippet": "Card inside card"
},
{
"antipattern": "nested-cards",
"name": "Nested cards",
"description": "Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/de",
"line": 0,
"snippet": "Card inside card"
},
{
"antipattern": "line-length",
"name": "Line length too long",
"description": "Text lines wider than ~80 characters are hard to read. The eye loses its place tracking back to the start of the next line. Add a max-width (65ch to 75ch) to text containers.",
"severity": "warning",
"category": "quality",
"file": "http://localhost:3000/de/leistungen",
"line": 0,
"snippet": "~86 chars/line (aim for <80)"
},
{
"antipattern": "line-length",
"name": "Line length too long",
"description": "Text lines wider than ~80 characters are hard to read. The eye loses its place tracking back to the start of the next line. Add a max-width (65ch to 75ch) to text containers.",
"severity": "warning",
"category": "quality",
"file": "http://localhost:3000/de/leistungen",
"line": 0,
"snippet": "~85 chars/line (aim for <80)"
},
{
"antipattern": "ai-color-palette",
"name": "AI color palette",
"description": "Purple/violet gradients and cyan-on-dark are the most recognizable tells of AI-generated UIs. Choose a distinctive, intentional palette.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/de/leistungen",
"line": 0,
"snippet": "Cyan neon text on dark background"
},
{
"antipattern": "ai-color-palette",
"name": "AI color palette",
"description": "Purple/violet gradients and cyan-on-dark are the most recognizable tells of AI-generated UIs. Choose a distinctive, intentional palette.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/de/leistungen",
"line": 0,
"snippet": "Cyan neon text on dark background"
},
{
"antipattern": "ai-color-palette",
"name": "AI color palette",
"description": "Purple/violet gradients and cyan-on-dark are the most recognizable tells of AI-generated UIs. Choose a distinctive, intentional palette.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/de/leistungen",
"line": 0,
"snippet": "Cyan neon text on dark background"
},
{
"antipattern": "ai-color-palette",
"name": "AI color palette",
"description": "Purple/violet gradients and cyan-on-dark are the most recognizable tells of AI-generated UIs. Choose a distinctive, intentional palette.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/de/leistungen",
"line": 0,
"snippet": "Cyan neon text on dark background"
},
{
"antipattern": "ai-color-palette",
"name": "AI color palette",
"description": "Purple/violet gradients and cyan-on-dark are the most recognizable tells of AI-generated UIs. Choose a distinctive, intentional palette.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/de/leistungen",
"line": 0,
"snippet": "Cyan neon text on dark background"
},
{
"antipattern": "ai-color-palette",
"name": "AI color palette",
"description": "Purple/violet gradients and cyan-on-dark are the most recognizable tells of AI-generated UIs. Choose a distinctive, intentional palette.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/de/leistungen",
"line": 0,
"snippet": "Cyan neon text on dark background"
},
{
"antipattern": "ai-color-palette",
"name": "AI color palette",
"description": "Purple/violet gradients and cyan-on-dark are the most recognizable tells of AI-generated UIs. Choose a distinctive, intentional palette.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/de/leistungen",
"line": 0,
"snippet": "Cyan neon text on dark background"
},
{
"antipattern": "ai-color-palette",
"name": "AI color palette",
"description": "Purple/violet gradients and cyan-on-dark are the most recognizable tells of AI-generated UIs. Choose a distinctive, intentional palette.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/de/leistungen",
"line": 0,
"snippet": "Cyan neon text on dark background"
},
{
"antipattern": "ai-color-palette",
"name": "AI color palette",
"description": "Purple/violet gradients and cyan-on-dark are the most recognizable tells of AI-generated UIs. Choose a distinctive, intentional palette.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/de/leistungen",
"line": 0,
"snippet": "Cyan neon text on dark background"
},
{
"antipattern": "ai-color-palette",
"name": "AI color palette",
"description": "Purple/violet gradients and cyan-on-dark are the most recognizable tells of AI-generated UIs. Choose a distinctive, intentional palette.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/de/leistungen",
"line": 0,
"snippet": "Cyan neon text on dark background"
},
{
"antipattern": "ai-color-palette",
"name": "AI color palette",
"description": "Purple/violet gradients and cyan-on-dark are the most recognizable tells of AI-generated UIs. Choose a distinctive, intentional palette.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/de/leistungen",
"line": 0,
"snippet": "Cyan neon text on dark background"
},
{
"antipattern": "ai-color-palette",
"name": "AI color palette",
"description": "Purple/violet gradients and cyan-on-dark are the most recognizable tells of AI-generated UIs. Choose a distinctive, intentional palette.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/de/leistungen",
"line": 0,
"snippet": "Cyan neon text on dark background"
},
{
"antipattern": "ai-color-palette",
"name": "AI color palette",
"description": "Purple/violet gradients and cyan-on-dark are the most recognizable tells of AI-generated UIs. Choose a distinctive, intentional palette.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/de/leistungen",
"line": 0,
"snippet": "Cyan neon text on dark background"
},
{
"antipattern": "ai-color-palette",
"name": "AI color palette",
"description": "Purple/violet gradients and cyan-on-dark are the most recognizable tells of AI-generated UIs. Choose a distinctive, intentional palette.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/de/leistungen",
"line": 0,
"snippet": "Cyan neon text on dark background"
},
{
"antipattern": "ai-color-palette",
"name": "AI color palette",
"description": "Purple/violet gradients and cyan-on-dark are the most recognizable tells of AI-generated UIs. Choose a distinctive, intentional palette.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/de/leistungen",
"line": 0,
"snippet": "Cyan neon text on dark background"
},
{
"antipattern": "ai-color-palette",
"name": "AI color palette",
"description": "Purple/violet gradients and cyan-on-dark are the most recognizable tells of AI-generated UIs. Choose a distinctive, intentional palette.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/de/leistungen",
"line": 0,
"snippet": "Cyan neon text on dark background"
},
{
"antipattern": "ai-color-palette",
"name": "AI color palette",
"description": "Purple/violet gradients and cyan-on-dark are the most recognizable tells of AI-generated UIs. Choose a distinctive, intentional palette.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/de/leistungen",
"line": 0,
"snippet": "Cyan neon text on dark background"
},
{
"antipattern": "ai-color-palette",
"name": "AI color palette",
"description": "Purple/violet gradients and cyan-on-dark are the most recognizable tells of AI-generated UIs. Choose a distinctive, intentional palette.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/de/leistungen",
"line": 0,
"snippet": "Cyan neon text on dark background"
},
{
"antipattern": "ai-color-palette",
"name": "AI color palette",
"description": "Purple/violet gradients and cyan-on-dark are the most recognizable tells of AI-generated UIs. Choose a distinctive, intentional palette.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/de/leistungen",
"line": 0,
"snippet": "Cyan neon text on dark background"
},
{
"antipattern": "line-length",
"name": "Line length too long",
"description": "Text lines wider than ~80 characters are hard to read. The eye loses its place tracking back to the start of the next line. Add a max-width (65ch to 75ch) to text containers.",
"severity": "warning",
"category": "quality",
"file": "http://localhost:3000/de/leistungen",
"line": 0,
"snippet": "~86 chars/line (aim for <80)"
},
{
"antipattern": "line-length",
"name": "Line length too long",
"description": "Text lines wider than ~80 characters are hard to read. The eye loses its place tracking back to the start of the next line. Add a max-width (65ch to 75ch) to text containers.",
"severity": "warning",
"category": "quality",
"file": "http://localhost:3000/de/leistungen",
"line": 0,
"snippet": "~86 chars/line (aim for <80)"
},
{
"antipattern": "edge-flush-cards",
"name": "Cards flush against the scroller edge",
"description": "Cards inside a horizontal scroller or tab panel sit flush against the container edge at rest while keeping a gutter on the other side, so their edges and rounded corners get cut off. Usually the panel is sized wider than its clip box. Keep a consistent inset on both sides.",
"severity": "warning",
"category": "quality",
"file": "http://localhost:3000/de/leistungen",
"line": 0,
"snippet": "1 card flush against the right edge of body.font-sans.antialiased.bg-background.text-foreground.min-h-screen.flex.flex-col at rest (3px gap, e.g. a.flex.h-14.w-14.items-center.justify-center.rounded-full.bg-[#25D366].text-[#1C1B1A].shadow-lg.transition-transform.hover:scale-105.hover:bg-[#20bd5a].focus:outline-none.focus:ring-4.focus:ring-[#25D366]/50)"
},
{
"antipattern": "nested-cards",
"name": "Nested cards",
"description": "Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/de/terminvereinbarung",
"line": 0,
"snippet": "Card inside card"
},
{
"antipattern": "edge-flush-cards",
"name": "Cards flush against the scroller edge",
"description": "Cards inside a horizontal scroller or tab panel sit flush against the container edge at rest while keeping a gutter on the other side, so their edges and rounded corners get cut off. Usually the panel is sized wider than its clip box. Keep a consistent inset on both sides.",
"severity": "warning",
"category": "quality",
"file": "http://localhost:3000/de/terminvereinbarung",
"line": 0,
"snippet": "1 card flush against the right edge of body.font-sans.antialiased.bg-background.text-foreground.min-h-screen.flex.flex-col at rest (3px gap, e.g. a.flex.h-14.w-14.items-center.justify-center.rounded-full.bg-[#25D366].text-[#1C1B1A].shadow-lg.transition-transform.hover:scale-105.hover:bg-[#20bd5a].focus:outline-none.focus:ring-4.focus:ring-[#25D366]/50)"
},
{
"antipattern": "line-length",
"name": "Line length too long",
"description": "Text lines wider than ~80 characters are hard to read. The eye loses its place tracking back to the start of the next line. Add a max-width (65ch to 75ch) to text containers.",
"severity": "warning",
"category": "quality",
"file": "http://localhost:3000/de/uber-uns",
"line": 0,
"snippet": "~85 chars/line (aim for <80)"
},
{
"antipattern": "nested-cards",
"name": "Nested cards",
"description": "Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/de/uber-uns",
"line": 0,
"snippet": "Card inside card"
},
{
"antipattern": "nested-cards",
"name": "Nested cards",
"description": "Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/de/uber-uns",
"line": 0,
"snippet": "Card inside card"
},
{
"antipattern": "nested-cards",
"name": "Nested cards",
"description": "Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/de/uber-uns",
"line": 0,
"snippet": "Card inside card"
},
{
"antipattern": "nested-cards",
"name": "Nested cards",
"description": "Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/de/uber-uns",
"line": 0,
"snippet": "Card inside card"
},
{
"antipattern": "nested-cards",
"name": "Nested cards",
"description": "Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/de/uber-uns",
"line": 0,
"snippet": "Card inside card"
},
{
"antipattern": "nested-cards",
"name": "Nested cards",
"description": "Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/de/uber-uns",
"line": 0,
"snippet": "Card inside card"
},
{
"antipattern": "nested-cards",
"name": "Nested cards",
"description": "Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/de/uber-uns",
"line": 0,
"snippet": "Card inside card"
},
{
"antipattern": "nested-cards",
"name": "Nested cards",
"description": "Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/de/uber-uns",
"line": 0,
"snippet": "Card inside card"
},
{
"antipattern": "edge-flush-cards",
"name": "Cards flush against the scroller edge",
"description": "Cards inside a horizontal scroller or tab panel sit flush against the container edge at rest while keeping a gutter on the other side, so their edges and rounded corners get cut off. Usually the panel is sized wider than its clip box. Keep a consistent inset on both sides.",
"severity": "warning",
"category": "quality",
"file": "http://localhost:3000/de/uber-uns",
"line": 0,
"snippet": "1 card flush against the right edge of body.font-sans.antialiased.bg-background.text-foreground.min-h-screen.flex.flex-col at rest (3px gap, e.g. a.flex.h-14.w-14.items-center.justify-center.rounded-full.bg-[#25D366].text-[#1C1B1A].shadow-lg.transition-transform.hover:scale-105.hover:bg-[#20bd5a].focus:outline-none.focus:ring-4.focus:ring-[#25D366]/50)"
}
]
+43
View File
@@ -0,0 +1,43 @@
[
{
"antipattern": "oversized-h1",
"name": "Oversized hero headline",
"description": "A full-sentence headline set at display size ends up dominating the viewport, leaving no room for anything else above the fold. A punchy one- or two-word headline at that size is fine — the problem is a long headline blown up too large. Set long headlines smaller, or tighten the copy.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/pl",
"line": 0,
"snippet": "72px h1, 55 chars, 39vh \"Odbierz zwrot podatku z NiemiecRozliczymy Cię po polsku\""
},
{
"antipattern": "gpt-thin-border-wide-shadow",
"name": "Hairline border with wide shadow",
"description": "A hairline border paired with a wide, diffuse shadow is a recurring generated-UI signature. Commit to one — a defined edge or a soft elevation — rather than both at once.",
"severity": "advisory",
"category": "slop",
"file": "http://localhost:3000/pl",
"line": 0,
"snippet": "1px border + 50px shadow blur",
"advisory": true
},
{
"antipattern": "nested-cards",
"name": "Nested cards",
"description": "Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/pl",
"line": 0,
"snippet": "Card inside card"
},
{
"antipattern": "nested-cards",
"name": "Nested cards",
"description": "Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/pl",
"line": 0,
"snippet": "Card inside card"
}
]
-22
View File
@@ -1,22 +0,0 @@
[
{
"antipattern": "line-length",
"name": "Line length too long",
"description": "Text lines wider than ~80 characters are hard to read. The eye loses its place tracking back to the start of the next line. Add a max-width (65ch to 75ch) to text containers.",
"severity": "warning",
"category": "quality",
"file": "http://localhost:3000/de",
"line": 0,
"snippet": "~95 chars/line (aim for <80)"
},
{
"antipattern": "cream-palette",
"name": "Cream / beige palette",
"description": "A warm cream or beige page background has become the default \"tasteful\" AI surface, reached for by reflex. Choose a background that comes from a deliberate palette, not the safe warm off-white.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/de",
"line": 0,
"snippet": "cream/beige page background rgb(247, 245, 241)"
}
]
-32
View File
@@ -1,32 +0,0 @@
[
{
"antipattern": "line-length",
"name": "Line length too long",
"description": "Text lines wider than ~80 characters are hard to read. The eye loses its place tracking back to the start of the next line. Add a max-width (65ch to 75ch) to text containers.",
"severity": "warning",
"category": "quality",
"file": "http://localhost:3000/pl",
"line": 0,
"snippet": "~85 chars/line (aim for <80)"
},
{
"antipattern": "line-length",
"name": "Line length too long",
"description": "Text lines wider than ~80 characters are hard to read. The eye loses its place tracking back to the start of the next line. Add a max-width (65ch to 75ch) to text containers.",
"severity": "warning",
"category": "quality",
"file": "http://localhost:3000/pl",
"line": 0,
"snippet": "~95 chars/line (aim for <80)"
},
{
"antipattern": "cream-palette",
"name": "Cream / beige palette",
"description": "A warm cream or beige page background has become the default \"tasteful\" AI surface, reached for by reflex. Choose a background that comes from a deliberate palette, not the safe warm off-white.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/pl",
"line": 0,
"snippet": "cream/beige page background rgb(247, 245, 241)"
}
]
-10
View File
@@ -1,14 +1,4 @@
[ [
{
"antipattern": "line-length",
"name": "Line length too long",
"description": "Text lines wider than ~80 characters are hard to read. The eye loses its place tracking back to the start of the next line. Add a max-width (65ch to 75ch) to text containers.",
"severity": "warning",
"category": "quality",
"file": "http://localhost:3000/pl/faq",
"line": 0,
"snippet": "~85 chars/line (aim for <80)"
},
{ {
"antipattern": "cream-palette", "antipattern": "cream-palette",
"name": "Cream / beige palette", "name": "Cream / beige palette",
+12
View File
@@ -0,0 +1,12 @@
[
{
"antipattern": "cream-palette",
"name": "Cream / beige palette",
"description": "A warm cream or beige page background has become the default \"tasteful\" AI surface, reached for by reflex. Choose a background that comes from a deliberate palette, not the safe warm off-white.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/pl/faq",
"line": 0,
"snippet": "cream/beige page background rgb(247, 245, 241)"
}
]
+12
View File
@@ -0,0 +1,12 @@
[
{
"antipattern": "cream-palette",
"name": "Cream / beige palette",
"description": "A warm cream or beige page background has become the default \"tasteful\" AI surface, reached for by reflex. Choose a background that comes from a deliberate palette, not the safe warm off-white.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/pl/faq",
"line": 0,
"snippet": "cream/beige page background rgb(247, 245, 241)"
}
]
+12
View File
@@ -0,0 +1,12 @@
[
{
"antipattern": "cream-palette",
"name": "Cream / beige palette",
"description": "A warm cream or beige page background has become the default \"tasteful\" AI surface, reached for by reflex. Choose a background that comes from a deliberate palette, not the safe warm off-white.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/pl/impressum",
"line": 0,
"snippet": "cream/beige page background rgb(247, 245, 241)"
}
]
+42
View File
@@ -0,0 +1,42 @@
[
{
"antipattern": "nested-cards",
"name": "Nested cards",
"description": "Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/pl/jak-sie-umowic",
"line": 0,
"snippet": "Card inside card"
},
{
"antipattern": "cream-palette",
"name": "Cream / beige palette",
"description": "A warm cream or beige page background has become the default \"tasteful\" AI surface, reached for by reflex. Choose a background that comes from a deliberate palette, not the safe warm off-white.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/pl/jak-sie-umowic",
"line": 0,
"snippet": "cream/beige page background rgb(247, 245, 241)"
},
{
"antipattern": "low-contrast",
"name": "Low contrast text",
"description": "Text does not meet WCAG AA contrast requirements (4.5:1 for body, 3:1 for large text). Increase the contrast between text and background.",
"severity": "warning",
"category": "quality",
"file": "http://localhost:3000/pl/jak-sie-umowic",
"line": 0,
"snippet": "pixel contrast 1.0:1 median 3.3:1 (need 3:1) on filter \"Jak się umówić\""
},
{
"antipattern": "low-contrast",
"name": "Low contrast text",
"description": "Text does not meet WCAG AA contrast requirements (4.5:1 for body, 3:1 for large text). Increase the contrast between text and background.",
"severity": "warning",
"category": "quality",
"file": "http://localhost:3000/pl/jak-sie-umowic",
"line": 0,
"snippet": "pixel contrast 1.0:1 median 1.2:1 (need 4.5:1) on filter \"Prosty, przejrzysty i w 100% zdalny proces rozliczenia podatkowego. Wszystko zał\""
}
]
+22
View File
@@ -0,0 +1,22 @@
[
{
"antipattern": "nested-cards",
"name": "Nested cards",
"description": "Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/pl/jak-sie-umowic",
"line": 0,
"snippet": "Card inside card"
},
{
"antipattern": "cream-palette",
"name": "Cream / beige palette",
"description": "A warm cream or beige page background has become the default \"tasteful\" AI surface, reached for by reflex. Choose a background that comes from a deliberate palette, not the safe warm off-white.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/pl/jak-sie-umowic",
"line": 0,
"snippet": "cream/beige page background rgb(247, 245, 241)"
}
]
+42
View File
@@ -0,0 +1,42 @@
[
{
"antipattern": "text-occlusion",
"name": "Text occluded by an overlapping element",
"description": "Text is painted under an opaque element or a second text run, so part of it cannot be read. A decorative box, a stacked layer, or an inline element with leaked padding lands on the words instead of beside them. Give overlapping layers room, or move the text out from under the layer above it.",
"severity": "warning",
"category": "quality",
"file": "http://localhost:3000/pl/kontakt",
"line": 0,
"snippet": "span.text-xs.tracking-widest.uppercase.font-semibold.text-foreground/40 \"MIEJSCE NA ZDJĘCIE\" is 100% covered by overlapping text (h1.font-heading.text-4xl.sm:text-5xl.lg:text-6xl.font-extrabold.text-foreground.tracking-tight.leading-[1.1].text-balance.max-w-4xl.drop-shadow-sm)"
},
{
"antipattern": "cream-palette",
"name": "Cream / beige palette",
"description": "A warm cream or beige page background has become the default \"tasteful\" AI surface, reached for by reflex. Choose a background that comes from a deliberate palette, not the safe warm off-white.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/pl/kontakt",
"line": 0,
"snippet": "cream/beige page background rgb(247, 245, 241)"
},
{
"antipattern": "low-contrast",
"name": "Low contrast text",
"description": "Text does not meet WCAG AA contrast requirements (4.5:1 for body, 3:1 for large text). Increase the contrast between text and background.",
"severity": "warning",
"category": "quality",
"file": "http://localhost:3000/pl/kontakt",
"line": 0,
"snippet": "pixel contrast 1.0:1 median 3.9:1 (need 3:1) on filter \"Kontakt\""
},
{
"antipattern": "low-contrast",
"name": "Low contrast text",
"description": "Text does not meet WCAG AA contrast requirements (4.5:1 for body, 3:1 for large text). Increase the contrast between text and background.",
"severity": "warning",
"category": "quality",
"file": "http://localhost:3000/pl/kontakt",
"line": 0,
"snippet": "pixel contrast 1.0:1 median 1.2:1 (need 4.5:1) on filter \"Masz pytania dotyczące rozliczenia podatkowego w Niemczech? Skontaktuj się z nam\""
}
]
+12
View File
@@ -0,0 +1,12 @@
[
{
"antipattern": "cream-palette",
"name": "Cream / beige palette",
"description": "A warm cream or beige page background has become the default \"tasteful\" AI surface, reached for by reflex. Choose a background that comes from a deliberate palette, not the safe warm off-white.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/pl/kontakt",
"line": 0,
"snippet": "cream/beige page background rgb(247, 245, 241)"
}
]
+32
View File
@@ -0,0 +1,32 @@
[
{
"antipattern": "cream-palette",
"name": "Cream / beige palette",
"description": "A warm cream or beige page background has become the default \"tasteful\" AI surface, reached for by reflex. Choose a background that comes from a deliberate palette, not the safe warm off-white.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/pl/kontakt",
"line": 0,
"snippet": "cream/beige page background rgb(247, 245, 241)"
},
{
"antipattern": "low-contrast",
"name": "Low contrast text",
"description": "Text does not meet WCAG AA contrast requirements (4.5:1 for body, 3:1 for large text). Increase the contrast between text and background.",
"severity": "warning",
"category": "quality",
"file": "http://localhost:3000/pl/kontakt",
"line": 0,
"snippet": "pixel contrast 1.0:1 median 3.9:1 (need 3:1) on filter \"Kontakt\""
},
{
"antipattern": "low-contrast",
"name": "Low contrast text",
"description": "Text does not meet WCAG AA contrast requirements (4.5:1 for body, 3:1 for large text). Increase the contrast between text and background.",
"severity": "warning",
"category": "quality",
"file": "http://localhost:3000/pl/kontakt",
"line": 0,
"snippet": "pixel contrast 1.0:1 median 1.2:1 (need 4.5:1) on filter \"Masz pytania dotyczące rozliczenia podatkowego w Niemczech? Skontaktuj się z nam\""
}
]
+122
View File
@@ -0,0 +1,122 @@
[
{
"antipattern": "line-length",
"name": "Line length too long",
"description": "Text lines wider than ~80 characters are hard to read. The eye loses its place tracking back to the start of the next line. Add a max-width (65ch to 75ch) to text containers.",
"severity": "warning",
"category": "quality",
"file": "http://localhost:3000/pl/o-nas",
"line": 0,
"snippet": "~85 chars/line (aim for <80)"
},
{
"antipattern": "nested-cards",
"name": "Nested cards",
"description": "Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/pl/o-nas",
"line": 0,
"snippet": "Card inside card"
},
{
"antipattern": "nested-cards",
"name": "Nested cards",
"description": "Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/pl/o-nas",
"line": 0,
"snippet": "Card inside card"
},
{
"antipattern": "nested-cards",
"name": "Nested cards",
"description": "Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/pl/o-nas",
"line": 0,
"snippet": "Card inside card"
},
{
"antipattern": "nested-cards",
"name": "Nested cards",
"description": "Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/pl/o-nas",
"line": 0,
"snippet": "Card inside card"
},
{
"antipattern": "nested-cards",
"name": "Nested cards",
"description": "Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/pl/o-nas",
"line": 0,
"snippet": "Card inside card"
},
{
"antipattern": "nested-cards",
"name": "Nested cards",
"description": "Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/pl/o-nas",
"line": 0,
"snippet": "Card inside card"
},
{
"antipattern": "nested-cards",
"name": "Nested cards",
"description": "Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/pl/o-nas",
"line": 0,
"snippet": "Card inside card"
},
{
"antipattern": "nested-cards",
"name": "Nested cards",
"description": "Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/pl/o-nas",
"line": 0,
"snippet": "Card inside card"
},
{
"antipattern": "cream-palette",
"name": "Cream / beige palette",
"description": "A warm cream or beige page background has become the default \"tasteful\" AI surface, reached for by reflex. Choose a background that comes from a deliberate palette, not the safe warm off-white.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/pl/o-nas",
"line": 0,
"snippet": "cream/beige page background rgb(247, 245, 241)"
},
{
"antipattern": "low-contrast",
"name": "Low contrast text",
"description": "Text does not meet WCAG AA contrast requirements (4.5:1 for body, 3:1 for large text). Increase the contrast between text and background.",
"severity": "warning",
"category": "quality",
"file": "http://localhost:3000/pl/o-nas",
"line": 0,
"snippet": "pixel contrast 1.0:1 median 3.7:1 (need 3:1) on filter \"O nas\""
},
{
"antipattern": "low-contrast",
"name": "Low contrast text",
"description": "Text does not meet WCAG AA contrast requirements (4.5:1 for body, 3:1 for large text). Increase the contrast between text and background.",
"severity": "warning",
"category": "quality",
"file": "http://localhost:3000/pl/o-nas",
"line": 0,
"snippet": "pixel contrast 1.0:1 median 1.2:1 (need 4.5:1) on filter \"Ponad 20 lat rzetelnego wsparcia podatkowego dla Polaków pracujących w Niemczech\""
}
]
+102
View File
@@ -0,0 +1,102 @@
[
{
"antipattern": "line-length",
"name": "Line length too long",
"description": "Text lines wider than ~80 characters are hard to read. The eye loses its place tracking back to the start of the next line. Add a max-width (65ch to 75ch) to text containers.",
"severity": "warning",
"category": "quality",
"file": "http://localhost:3000/pl/o-nas",
"line": 0,
"snippet": "~85 chars/line (aim for <80)"
},
{
"antipattern": "nested-cards",
"name": "Nested cards",
"description": "Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/pl/o-nas",
"line": 0,
"snippet": "Card inside card"
},
{
"antipattern": "nested-cards",
"name": "Nested cards",
"description": "Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/pl/o-nas",
"line": 0,
"snippet": "Card inside card"
},
{
"antipattern": "nested-cards",
"name": "Nested cards",
"description": "Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/pl/o-nas",
"line": 0,
"snippet": "Card inside card"
},
{
"antipattern": "nested-cards",
"name": "Nested cards",
"description": "Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/pl/o-nas",
"line": 0,
"snippet": "Card inside card"
},
{
"antipattern": "nested-cards",
"name": "Nested cards",
"description": "Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/pl/o-nas",
"line": 0,
"snippet": "Card inside card"
},
{
"antipattern": "nested-cards",
"name": "Nested cards",
"description": "Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/pl/o-nas",
"line": 0,
"snippet": "Card inside card"
},
{
"antipattern": "nested-cards",
"name": "Nested cards",
"description": "Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/pl/o-nas",
"line": 0,
"snippet": "Card inside card"
},
{
"antipattern": "nested-cards",
"name": "Nested cards",
"description": "Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/pl/o-nas",
"line": 0,
"snippet": "Card inside card"
},
{
"antipattern": "cream-palette",
"name": "Cream / beige palette",
"description": "A warm cream or beige page background has become the default \"tasteful\" AI surface, reached for by reflex. Choose a background that comes from a deliberate palette, not the safe warm off-white.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/pl/o-nas",
"line": 0,
"snippet": "cream/beige page background rgb(247, 245, 241)"
}
]
+12
View File
@@ -0,0 +1,12 @@
[
{
"antipattern": "cream-palette",
"name": "Cream / beige palette",
"description": "A warm cream or beige page background has become the default \"tasteful\" AI surface, reached for by reflex. Choose a background that comes from a deliberate palette, not the safe warm off-white.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/pl/polityka-prywatnosci",
"line": 0,
"snippet": "cream/beige page background rgb(247, 245, 241)"
}
]
+23 -23
View File
@@ -7,7 +7,7 @@
"category": "quality", "category": "quality",
"file": "http://localhost:3000/pl/uslugi", "file": "http://localhost:3000/pl/uslugi",
"line": 0, "line": 0,
"snippet": "~100 chars/line (aim for <80)" "snippet": "~86 chars/line (aim for <80)"
}, },
{ {
"antipattern": "line-length", "antipattern": "line-length",
@@ -27,7 +27,7 @@
"category": "quality", "category": "quality",
"file": "http://localhost:3000/pl/uslugi", "file": "http://localhost:3000/pl/uslugi",
"line": 0, "line": 0,
"snippet": "~131 chars/line (aim for <80)" "snippet": "~86 chars/line (aim for <80)"
}, },
{ {
"antipattern": "line-length", "antipattern": "line-length",
@@ -37,27 +37,7 @@
"category": "quality", "category": "quality",
"file": "http://localhost:3000/pl/uslugi", "file": "http://localhost:3000/pl/uslugi",
"line": 0, "line": 0,
"snippet": "~151 chars/line (aim for <80)" "snippet": "~86 chars/line (aim for <80)"
},
{
"antipattern": "line-length",
"name": "Line length too long",
"description": "Text lines wider than ~80 characters are hard to read. The eye loses its place tracking back to the start of the next line. Add a max-width (65ch to 75ch) to text containers.",
"severity": "warning",
"category": "quality",
"file": "http://localhost:3000/pl/uslugi",
"line": 0,
"snippet": "~85 chars/line (aim for <80)"
},
{
"antipattern": "line-length",
"name": "Line length too long",
"description": "Text lines wider than ~80 characters are hard to read. The eye loses its place tracking back to the start of the next line. Add a max-width (65ch to 75ch) to text containers.",
"severity": "warning",
"category": "quality",
"file": "http://localhost:3000/pl/uslugi",
"line": 0,
"snippet": "~95 chars/line (aim for <80)"
}, },
{ {
"antipattern": "cream-palette", "antipattern": "cream-palette",
@@ -68,5 +48,25 @@
"file": "http://localhost:3000/pl/uslugi", "file": "http://localhost:3000/pl/uslugi",
"line": 0, "line": 0,
"snippet": "cream/beige page background rgb(247, 245, 241)" "snippet": "cream/beige page background rgb(247, 245, 241)"
},
{
"antipattern": "low-contrast",
"name": "Low contrast text",
"description": "Text does not meet WCAG AA contrast requirements (4.5:1 for body, 3:1 for large text). Increase the contrast between text and background.",
"severity": "warning",
"category": "quality",
"file": "http://localhost:3000/pl/uslugi",
"line": 0,
"snippet": "pixel contrast 1.0:1 median 3.4:1 (need 3:1) on filter \"Kompleksowe rozliczenia podatkowe dla Polaków w Niemczech\""
},
{
"antipattern": "low-contrast",
"name": "Low contrast text",
"description": "Text does not meet WCAG AA contrast requirements (4.5:1 for body, 3:1 for large text). Increase the contrast between text and background.",
"severity": "warning",
"category": "quality",
"file": "http://localhost:3000/pl/uslugi",
"line": 0,
"snippet": "pixel contrast 1.0:1 median 1.2:1 (need 4.5:1) on filter \"Profesjonalne sporządzanie deklaracji, weryfikacja decyzji podatkowych i bieżące\""
} }
] ]
+52
View File
@@ -0,0 +1,52 @@
[
{
"antipattern": "line-length",
"name": "Line length too long",
"description": "Text lines wider than ~80 characters are hard to read. The eye loses its place tracking back to the start of the next line. Add a max-width (65ch to 75ch) to text containers.",
"severity": "warning",
"category": "quality",
"file": "http://localhost:3000/pl/uslugi",
"line": 0,
"snippet": "~86 chars/line (aim for <80)"
},
{
"antipattern": "line-length",
"name": "Line length too long",
"description": "Text lines wider than ~80 characters are hard to read. The eye loses its place tracking back to the start of the next line. Add a max-width (65ch to 75ch) to text containers.",
"severity": "warning",
"category": "quality",
"file": "http://localhost:3000/pl/uslugi",
"line": 0,
"snippet": "~85 chars/line (aim for <80)"
},
{
"antipattern": "line-length",
"name": "Line length too long",
"description": "Text lines wider than ~80 characters are hard to read. The eye loses its place tracking back to the start of the next line. Add a max-width (65ch to 75ch) to text containers.",
"severity": "warning",
"category": "quality",
"file": "http://localhost:3000/pl/uslugi",
"line": 0,
"snippet": "~86 chars/line (aim for <80)"
},
{
"antipattern": "line-length",
"name": "Line length too long",
"description": "Text lines wider than ~80 characters are hard to read. The eye loses its place tracking back to the start of the next line. Add a max-width (65ch to 75ch) to text containers.",
"severity": "warning",
"category": "quality",
"file": "http://localhost:3000/pl/uslugi",
"line": 0,
"snippet": "~86 chars/line (aim for <80)"
},
{
"antipattern": "cream-palette",
"name": "Cream / beige palette",
"description": "A warm cream or beige page background has become the default \"tasteful\" AI surface, reached for by reflex. Choose a background that comes from a deliberate palette, not the safe warm off-white.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/pl/uslugi",
"line": 0,
"snippet": "cream/beige page background rgb(247, 245, 241)"
}
]
+92
View File
@@ -0,0 +1,92 @@
[
{
"antipattern": "line-length",
"name": "Line length too long",
"description": "Text lines wider than ~80 characters are hard to read. The eye loses its place tracking back to the start of the next line. Add a max-width (65ch to 75ch) to text containers.",
"severity": "warning",
"category": "quality",
"file": "http://localhost:3000/pl/o-nas",
"line": 0,
"snippet": "~85 chars/line (aim for <80)"
},
{
"antipattern": "nested-cards",
"name": "Nested cards",
"description": "Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/pl/o-nas",
"line": 0,
"snippet": "Card inside card"
},
{
"antipattern": "nested-cards",
"name": "Nested cards",
"description": "Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/pl/o-nas",
"line": 0,
"snippet": "Card inside card"
},
{
"antipattern": "nested-cards",
"name": "Nested cards",
"description": "Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/pl/o-nas",
"line": 0,
"snippet": "Card inside card"
},
{
"antipattern": "nested-cards",
"name": "Nested cards",
"description": "Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/pl/o-nas",
"line": 0,
"snippet": "Card inside card"
},
{
"antipattern": "nested-cards",
"name": "Nested cards",
"description": "Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/pl/o-nas",
"line": 0,
"snippet": "Card inside card"
},
{
"antipattern": "nested-cards",
"name": "Nested cards",
"description": "Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/pl/o-nas",
"line": 0,
"snippet": "Card inside card"
},
{
"antipattern": "nested-cards",
"name": "Nested cards",
"description": "Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/pl/o-nas",
"line": 0,
"snippet": "Card inside card"
},
{
"antipattern": "nested-cards",
"name": "Nested cards",
"description": "Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/pl/o-nas",
"line": 0,
"snippet": "Card inside card"
}
]
+102
View File
@@ -0,0 +1,102 @@
[
{
"antipattern": "line-length",
"name": "Line length too long",
"description": "Text lines wider than ~80 characters are hard to read. The eye loses its place tracking back to the start of the next line. Add a max-width (65ch to 75ch) to text containers.",
"severity": "warning",
"category": "quality",
"file": "http://localhost:3000/pl/o-nas",
"line": 0,
"snippet": "~85 chars/line (aim for <80)"
},
{
"antipattern": "nested-cards",
"name": "Nested cards",
"description": "Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/pl/o-nas",
"line": 0,
"snippet": "Card inside card"
},
{
"antipattern": "nested-cards",
"name": "Nested cards",
"description": "Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/pl/o-nas",
"line": 0,
"snippet": "Card inside card"
},
{
"antipattern": "nested-cards",
"name": "Nested cards",
"description": "Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/pl/o-nas",
"line": 0,
"snippet": "Card inside card"
},
{
"antipattern": "nested-cards",
"name": "Nested cards",
"description": "Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/pl/o-nas",
"line": 0,
"snippet": "Card inside card"
},
{
"antipattern": "nested-cards",
"name": "Nested cards",
"description": "Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/pl/o-nas",
"line": 0,
"snippet": "Card inside card"
},
{
"antipattern": "nested-cards",
"name": "Nested cards",
"description": "Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/pl/o-nas",
"line": 0,
"snippet": "Card inside card"
},
{
"antipattern": "nested-cards",
"name": "Nested cards",
"description": "Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/pl/o-nas",
"line": 0,
"snippet": "Card inside card"
},
{
"antipattern": "nested-cards",
"name": "Nested cards",
"description": "Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/pl/o-nas",
"line": 0,
"snippet": "Card inside card"
},
{
"antipattern": "cream-palette",
"name": "Cream / beige palette",
"description": "A warm cream or beige page background has become the default \"tasteful\" AI surface, reached for by reflex. Choose a background that comes from a deliberate palette, not the safe warm off-white.",
"severity": "warning",
"category": "slop",
"file": "http://localhost:3000/pl/o-nas",
"line": 0,
"snippet": "cream/beige page background rgb(247, 245, 241)"
}
]
+9 -1
View File
@@ -21,10 +21,14 @@
"dependencies": { "dependencies": {
"@intecion/ipal-kit": "^1.1.2", "@intecion/ipal-kit": "^1.1.2",
"@payloadcms/db-postgres": "latest", "@payloadcms/db-postgres": "latest",
"@payloadcms/db-sqlite": "^3.88.0", "@payloadcms/db-sqlite": "latest",
"@payloadcms/next": "latest", "@payloadcms/next": "latest",
"@payloadcms/plugin-cloud-storage": "^3.89.0",
"@payloadcms/plugin-form-builder": "latest",
"@payloadcms/plugin-mcp": "latest", "@payloadcms/plugin-mcp": "latest",
"@payloadcms/plugin-seo": "^3.89.0",
"@payloadcms/richtext-lexical": "latest", "@payloadcms/richtext-lexical": "latest",
"@payloadcms/storage-s3": "^3.89.0",
"@payloadcms/ui": "latest", "@payloadcms/ui": "latest",
"@tailwindcss/postcss": "^4.3.3", "@tailwindcss/postcss": "^4.3.3",
"clsx": "^2.1.1", "clsx": "^2.1.1",
@@ -69,5 +73,9 @@
"esbuild", "esbuild",
"unrs-resolver" "unrs-resolver"
] ]
},
"overrides": {
"@payloadcms/*": "3.89.0",
"payload": "3.89.0"
} }
} }
File diff suppressed because one or more lines are too long
+112 -99
View File
@@ -10,25 +10,37 @@ importers:
dependencies: dependencies:
'@intecion/ipal-kit': '@intecion/ipal-kit':
specifier: ^1.1.2 specifier: ^1.1.2
version: 1.1.2(7a514cf7d7dc8f49b191472d316c7395) version: 1.1.2(7568ca3524200483fde555f829a4b90a)
'@payloadcms/db-postgres': '@payloadcms/db-postgres':
specifier: latest specifier: latest
version: 3.88.0(@libsql/[email protected])([email protected]8.0([email protected])([email protected])([email protected]))([email protected]) version: 3.89.0(@libsql/[email protected])([email protected]9.0([email protected])([email protected])([email protected]))([email protected])
'@payloadcms/db-sqlite': '@payloadcms/db-sqlite':
specifier: ^3.88.0 specifier: latest
version: 3.88.0(@types/[email protected])([email protected]8.0([email protected])([email protected])([email protected]))([email protected])([email protected]) version: 3.89.0(@types/[email protected])([email protected]9.0([email protected])([email protected])([email protected]))([email protected])([email protected])
'@payloadcms/next': '@payloadcms/next':
specifier: latest specifier: latest
version: 3.88.0(@types/[email protected])([email protected])([email protected])([email protected](@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]8.0([email protected])([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])([email protected]) version: 3.89.0(@types/[email protected])([email protected])([email protected])([email protected](@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]9.0([email protected])([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])([email protected])
'@payloadcms/plugin-cloud-storage':
specifier: ^3.89.0
version: 3.89.0(@types/[email protected])([email protected])([email protected](@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]([email protected])([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])([email protected])
'@payloadcms/plugin-form-builder':
specifier: latest
version: 3.89.0(@types/[email protected])([email protected])([email protected](@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]([email protected])([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])([email protected])
'@payloadcms/plugin-mcp': '@payloadcms/plugin-mcp':
specifier: latest specifier: latest
version: 3.88.0([email protected](@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]8.0([email protected])([email protected])([email protected]))([email protected]) version: 3.89.0([email protected](@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]9.0([email protected])([email protected])([email protected]))([email protected])
'@payloadcms/plugin-seo':
specifier: ^3.89.0
version: 3.89.0(@types/[email protected])([email protected])([email protected](@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]([email protected])([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])([email protected])
'@payloadcms/richtext-lexical': '@payloadcms/richtext-lexical':
specifier: latest specifier: latest
version: 3.88.0(@faceless-ui/[email protected]([email protected]([email protected]))([email protected]))(@faceless-ui/[email protected]([email protected]([email protected]))([email protected]))(@payloadcms/[email protected]8.0(@types/[email protected])([email protected])([email protected])([email protected](@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]8.0([email protected])([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])([email protected]))(@types/[email protected])([email protected])([email protected](@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]8.0([email protected])([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])([email protected])([email protected]) version: 3.89.0(@faceless-ui/[email protected]([email protected]([email protected]))([email protected]))(@faceless-ui/[email protected]([email protected]([email protected]))([email protected]))(@payloadcms/[email protected]9.0(@types/[email protected])([email protected])([email protected])([email protected](@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]9.0([email protected])([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])([email protected]))(@types/[email protected])([email protected])([email protected](@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]9.0([email protected])([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])([email protected])([email protected])
'@payloadcms/storage-s3':
specifier: ^3.89.0
version: 3.89.0(@types/[email protected])([email protected])([email protected](@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]([email protected])([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])([email protected])
'@payloadcms/ui': '@payloadcms/ui':
specifier: latest specifier: latest
version: 3.88.0(@types/[email protected])([email protected])([email protected](@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]8.0([email protected])([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])([email protected]) version: 3.89.0(@types/[email protected])([email protected])([email protected](@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]9.0([email protected])([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])([email protected])
'@tailwindcss/postcss': '@tailwindcss/postcss':
specifier: ^4.3.3 specifier: ^4.3.3
version: 4.3.3 version: 4.3.3
@@ -58,7 +70,7 @@ importers:
version: 0.4.6([email protected]([email protected]))([email protected]) version: 0.4.6([email protected]([email protected]))([email protected])
payload: payload:
specifier: latest specifier: latest
version: 3.88.0([email protected])([email protected])([email protected]) version: 3.89.0([email protected])([email protected])([email protected])
postcss: postcss:
specifier: ^8.5.26 specifier: ^8.5.26
version: 8.5.26 version: 8.5.26
@@ -1575,88 +1587,88 @@ packages:
resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==}
engines: {node: '>=12.4.0'} engines: {node: '>=12.4.0'}
'@payloadcms/[email protected]8.0': '@payloadcms/[email protected]9.0':
resolution: {integrity: sha512-uP2b3yH8MulguWSH7j8vJaFX3ols4fVKPtsvZ49XQvDsafBAAUZjc1aCldo1ozoVfhC46tx4pv0ovtHQc8zdcA==} resolution: {integrity: sha512-Z6sa/8ahAYqW7ttcAL4Y1+50w5SaEN0hJdNVJCzgBFB/1nnrcmFaKO5m++aeeTsAdffSRXV9J8G5n8fBChMr5w==}
peerDependencies: peerDependencies:
payload: 3.88.0 payload: 3.89.0
'@payloadcms/[email protected]8.0': '@payloadcms/[email protected]9.0':
resolution: {integrity: sha512-ZszS6LfejrfSmYNjb7EkUjt6A9zUPDNLSYN3Y5uxWqTRL5yDehnR2ZW/cbpvECtieX2cZ8z3bst8WVnWCtFPNw==} resolution: {integrity: sha512-0qY1k3fGxAXoe3r44mm3Iku1BIUeRgjDYq0kIvRdcls+PVOT/7nXsIZgTkjrNn5Cu4mujLIpTVwDGsTcN0fI2w==}
peerDependencies: peerDependencies:
payload: 3.88.0 payload: 3.89.0
'@payloadcms/[email protected]8.0': '@payloadcms/[email protected]9.0':
resolution: {integrity: sha512-qtPVSLjAFnQl1GwaQPhmnYTwCCWgTnRm4ps5yJxmN3RPdLF6S77gvqZMnC4HTeH7YADQJW3nFgQCVYExxWE6hA==} resolution: {integrity: sha512-/Ijf87WfIaIGDrlAQBOXcNxRZy+IDfpZQvpmJpA4BwO7PEVMqU6nHSUYVgbFHy6+4XYHvsTp31/4xo06yQwiOw==}
peerDependencies: peerDependencies:
payload: 3.88.0 payload: 3.89.0
'@payloadcms/[email protected]8.0': '@payloadcms/[email protected]9.0':
resolution: {integrity: sha512-ync+bPFl0UacwBs1wVQkPlaSWWQJc4LjGG9OuRHEW7wYW3aeVQ+H2zfdFL6/cVJGP3zOq+XIlvYYw8G86Ki+Vw==} resolution: {integrity: sha512-oa77vdW2yKwla8hXKDQOYvCLZNn6rmR8Th2QgGfq3gAvlR5hEnZ7KH9VK23AfkimXRuHKnI0tH8K+OaBOKdStA==}
hasBin: true hasBin: true
peerDependencies: peerDependencies:
graphql: ^16.8.1 graphql: ^16.8.1
payload: 3.88.0 payload: 3.89.0
'@payloadcms/[email protected]8.0': '@payloadcms/[email protected]9.0':
resolution: {integrity: sha512-3f2YOGSmtTp85YnlJm9FusRgshKJbiLI8uXs8iQNMwU7RZJnLCURG8iv/6sjKHO0owIfw3vIL5qgxdtIBuQwpA==} resolution: {integrity: sha512-1PEzFEQIRhrOQh1cKegmNcaOQxWXA5wflxMkgqZqKZN62i64/gMB4MWspyfBEHjiAT0NPFDZyK7e522qEOj4vg==}
engines: {node: ^18.20.2 || >=20.9.0} engines: {node: ^18.20.2 || >=20.9.0}
peerDependencies: peerDependencies:
graphql: ^16.8.1 graphql: ^16.8.1
next: '>=15.2.9 <15.3.0 || >=15.3.9 <15.4.0 || >=15.4.11 <15.5.0 || >=16.2.6 <17.0.0' next: '>=15.2.9 <15.3.0 || >=15.3.9 <15.4.0 || >=15.4.11 <15.5.0 || >=16.2.6 <17.0.0'
payload: 3.88.0 payload: 3.89.0
'@payloadcms/[email protected]8.0': '@payloadcms/[email protected]9.0':
resolution: {integrity: sha512-wIcRVedtBZIQMu7dpSE1TOJKJd0HuVSUNPrV2KxIbtqQFm+ly34NCJYY0m1+K0YS6my8BKdcB4jj2exg8454Ig==} resolution: {integrity: sha512-dzaT6ybIw0WU1bT2tit3ZRzPb4imbV8m8SWYzOE0gzbQQuDLT69K5RPK3o4U3NK0iEFSsA+InU/xP5kQFDaTQw==}
peerDependencies: peerDependencies:
payload: 3.88.0 payload: 3.89.0
react: ^19.0.1 || ^19.1.2 || ^19.2.1 react: ^19.0.1 || ^19.1.2 || ^19.2.1
react-dom: ^19.0.1 || ^19.1.2 || ^19.2.1 react-dom: ^19.0.1 || ^19.1.2 || ^19.2.1
'@payloadcms/[email protected]8.0': '@payloadcms/[email protected]9.0':
resolution: {integrity: sha512-C915jEHADVN9xW7fSq/bYIZig1zjfQo+t4wrgiqjOCmB81oMgyqLRB3RlL2e6y4PzBUtgss2VS40vn/tVD/61Q==} resolution: {integrity: sha512-Niq+ukQmoS7Gq6ZLEE1YxW09YSFWhwYIgVMFmsy6jEHrEnNVIDhISmxRTtZj6me/NArkLpEYAUYfXqskttXzPg==}
peerDependencies: peerDependencies:
payload: 3.88.0 payload: 3.89.0
react: ^19.0.1 || ^19.1.2 || ^19.2.1 react: ^19.0.1 || ^19.1.2 || ^19.2.1
react-dom: ^19.0.1 || ^19.1.2 || ^19.2.1 react-dom: ^19.0.1 || ^19.1.2 || ^19.2.1
'@payloadcms/[email protected]8.0': '@payloadcms/[email protected]9.0':
resolution: {integrity: sha512-+L4SeiYFs4hBfnwmoyxChs2jAL/dcSW16YXRQ+7ua8AIYJRveNGOHtcFZgKH0t/bHE1nK/1df9Wy2EpmBnAcTw==} resolution: {integrity: sha512-0EhNJhOXpKsNE4rGBlkJ+facu+i8/VC8fOmMx3tVr+tA7C31ppjtMwWSXt6it7gualkyJctmvoQy942DiepzNQ==}
peerDependencies: peerDependencies:
payload: 3.88.0 payload: 3.89.0
'@payloadcms/[email protected]8.0': '@payloadcms/[email protected]9.0':
resolution: {integrity: sha512-3eFWoqpXZQZ3ghdFksPo7nQ08XUG0PXWc/qf1ibtXiKPlMN43Asa8Z9wGNKYtkDhIM+OYi6EmlFzRLWkCRcwrg==} resolution: {integrity: sha512-LmH1gKAXiIaxe5xPGqizMaCOHah+wn2nfl5Rxi2Mr6jAFG/XnmkYzlXeIdduQiGx6X4f+sDHkgqm4+YI6A/89Q==}
peerDependencies: peerDependencies:
payload: 3.88.0 payload: 3.89.0
react: ^19.0.1 || ^19.1.2 || ^19.2.1 react: ^19.0.1 || ^19.1.2 || ^19.2.1
react-dom: ^19.0.1 || ^19.1.2 || ^19.2.1 react-dom: ^19.0.1 || ^19.1.2 || ^19.2.1
'@payloadcms/[email protected]8.0': '@payloadcms/[email protected]9.0':
resolution: {integrity: sha512-lY8oIUiggykWKB3ljMfSidxBtA8ahMq0Qqp28OuvaCNIDp0HTMIPxcHvFOQZngMqtQHhpautD6vcQR0Q9CEHHQ==} resolution: {integrity: sha512-IV9sKT/fYptRJXk1XfIKW6V2sBWma4hofBMPI3b1Ab0ClKlyudwrtRtqv9cZzxwoNMYhtC8h+3SQMOq10eI2bQ==}
engines: {node: ^18.20.2 || >=20.9.0} engines: {node: ^18.20.2 || >=20.9.0}
peerDependencies: peerDependencies:
'@faceless-ui/modal': 3.0.0 '@faceless-ui/modal': 3.0.0
'@faceless-ui/scroll-info': 2.0.0 '@faceless-ui/scroll-info': 2.0.0
'@payloadcms/next': 3.88.0 '@payloadcms/next': 3.89.0
payload: 3.88.0 payload: 3.89.0
react: ^19.0.1 || ^19.1.2 || ^19.2.1 react: ^19.0.1 || ^19.1.2 || ^19.2.1
react-dom: ^19.0.1 || ^19.1.2 || ^19.2.1 react-dom: ^19.0.1 || ^19.1.2 || ^19.2.1
'@payloadcms/[email protected]8.0': '@payloadcms/[email protected]9.0':
resolution: {integrity: sha512-+35JRD4y7ImnIJuPSBhECHGGml0A+xju9BT/zW+lRUycDvvA0cqZNP+t95OVoZrxIB6V75Vg8GKPvAaRAmgOEw==} resolution: {integrity: sha512-IEx1Wz4wpvJR07sP7ni+YqiyZkeIuk/iD5YFMAkQrC2whQNgJRwEn7yulC+PGGSrRWMV4ryIHF363rTBVkDmVw==}
engines: {node: ^18.20.2 || >=20.9.0} engines: {node: ^18.20.2 || >=20.9.0}
peerDependencies: peerDependencies:
payload: 3.88.0 payload: 3.89.0
'@payloadcms/[email protected]8.0': '@payloadcms/[email protected]9.0':
resolution: {integrity: sha512-AZVQjjW5pKxO+K9ODeh6yw6/EfXK1hxGPw55KzzDC5srO3hjNskgyWtkauKvbz+S0+xFspANbRc4OD/Rg/74WA==} resolution: {integrity: sha512-XLPOdv9NoPcqr21zBTu0uyJk0qGRIF7JVl2H2UTecpweQSMlQCackhUKWz1LKBO7QcKgkNSVHFnMNcizRDcPDg==}
'@payloadcms/[email protected]8.0': '@payloadcms/[email protected]9.0':
resolution: {integrity: sha512-iwQhHPDn4Oj2bPg1UV467BoxfUNo1rUmD8jMZaw2Vtt/nmxAPl3W3CWN/CsOX/yITuPuGHJxznOg4PZNMF3gEg==} resolution: {integrity: sha512-jKc8kgSjaICZ39sNC4dr4MLkAj2lR9ikzSwcHzPGOsJlleOVPJq5jM9bmAnqEOhtbpPNl0CGlpo6RIeECnBmwg==}
engines: {node: ^18.20.2 || >=20.9.0} engines: {node: ^18.20.2 || >=20.9.0}
peerDependencies: peerDependencies:
next: '>=15.2.9 <15.3.0 || >=15.3.9 <15.4.0 || >=15.4.11 <15.5.0 || >=16.2.6 <17.0.0' next: '>=15.2.9 <15.3.0 || >=15.3.9 <15.4.0 || >=15.4.11 <15.5.0 || >=16.2.6 <17.0.0'
payload: 3.88.0 payload: 3.89.0
react: ^19.0.1 || ^19.1.2 || ^19.2.1 react: ^19.0.1 || ^19.1.2 || ^19.2.1
react-dom: ^19.0.1 || ^19.1.2 || ^19.2.1 react-dom: ^19.0.1 || ^19.1.2 || ^19.2.1
@@ -4208,8 +4220,8 @@ packages:
[email protected]: [email protected]:
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
[email protected]8.0: [email protected]9.0:
resolution: {integrity: sha512-O7zuS80bvEGLte+7xZjwN05+ox5BCsGcQT2M6+CTote07JQOOvHJoiuoyQFw6cUElcFTWGMC5dy03w7J7sTYGg==} resolution: {integrity: sha512-n0rDsMDTlKkNDqNllSLow1xEn2XaktD2oyi6Gl7i/LAD7xRBaS+tj27ca9EeNM5QmoGSd734UCHYj5Vr4JQYNA==}
engines: {node: ^18.20.2 || >=20.9.0} engines: {node: ^18.20.2 || >=20.9.0}
hasBin: true hasBin: true
peerDependencies: peerDependencies:
@@ -6211,17 +6223,17 @@ snapshots:
'@img/[email protected]': '@img/[email protected]':
optional: true optional: true
'@intecion/[email protected](7a514cf7d7dc8f49b191472d316c7395)': '@intecion/[email protected](7568ca3524200483fde555f829a4b90a)':
dependencies: dependencies:
'@payloadcms/next': 3.88.0(@types/[email protected])([email protected])([email protected])([email protected](@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]8.0([email protected])([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])([email protected]) '@payloadcms/next': 3.89.0(@types/[email protected])([email protected])([email protected])([email protected](@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]9.0([email protected])([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])([email protected])
'@payloadcms/plugin-form-builder': 3.88.0(@types/[email protected])([email protected])([email protected](@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]8.0([email protected])([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])([email protected]) '@payloadcms/plugin-form-builder': 3.89.0(@types/[email protected])([email protected])([email protected](@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]9.0([email protected])([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])([email protected])
'@payloadcms/plugin-seo': 3.88.0(@types/[email protected])([email protected])([email protected](@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]8.0([email protected])([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])([email protected]) '@payloadcms/plugin-seo': 3.89.0(@types/[email protected])([email protected])([email protected](@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]9.0([email protected])([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])([email protected])
'@payloadcms/storage-s3': 3.88.0(@types/[email protected])([email protected])([email protected](@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]8.0([email protected])([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])([email protected]) '@payloadcms/storage-s3': 3.89.0(@types/[email protected])([email protected])([email protected](@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]9.0([email protected])([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])([email protected])
'@payloadcms/ui': 3.88.0(@types/[email protected])([email protected])([email protected](@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]8.0([email protected])([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])([email protected]) '@payloadcms/ui': 3.89.0(@types/[email protected])([email protected])([email protected](@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]9.0([email protected])([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])([email protected])
lucide-react: 0.400.0([email protected]) lucide-react: 0.400.0([email protected])
next: 16.3.3(@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected]) next: 16.3.3(@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected])
nodemailer: 8.0.11 nodemailer: 8.0.11
payload: 3.88.0([email protected])([email protected])([email protected]) payload: 3.89.0([email protected])([email protected])([email protected])
react: 19.2.6 react: 19.2.6
react-dom: 19.2.6([email protected]) react-dom: 19.2.6([email protected])
server-only: 0.0.1 server-only: 0.0.1
@@ -6571,14 +6583,14 @@ snapshots:
'@nolyfill/[email protected]': {} '@nolyfill/[email protected]': {}
'@payloadcms/[email protected]8.0(@libsql/[email protected])([email protected]8.0([email protected])([email protected])([email protected]))([email protected])': '@payloadcms/[email protected]9.0(@libsql/[email protected])([email protected]9.0([email protected])([email protected])([email protected]))([email protected])':
dependencies: dependencies:
'@payloadcms/drizzle': 3.88.0(@libsql/[email protected])(@types/[email protected])([email protected]8.0([email protected])([email protected])([email protected]))([email protected]) '@payloadcms/drizzle': 3.89.0(@libsql/[email protected])(@types/[email protected])([email protected]9.0([email protected])([email protected])([email protected]))([email protected])
'@types/pg': 8.20.0 '@types/pg': 8.20.0
console-table-printer: 2.12.1 console-table-printer: 2.12.1
drizzle-kit: 0.31.7([email protected]) drizzle-kit: 0.31.7([email protected])
drizzle-orm: 0.45.2(@libsql/[email protected])(@types/[email protected])([email protected]) drizzle-orm: 0.45.2(@libsql/[email protected])(@types/[email protected])([email protected])
payload: 3.88.0([email protected])([email protected])([email protected]) payload: 3.89.0([email protected])([email protected])([email protected])
pg: 8.20.0 pg: 8.20.0
prompts: 2.4.2 prompts: 2.4.2
to-snake-case: 1.0.0 to-snake-case: 1.0.0
@@ -6614,14 +6626,14 @@ snapshots:
- sqlite3 - sqlite3
- supports-color - supports-color
'@payloadcms/[email protected]8.0(@types/[email protected])([email protected]8.0([email protected])([email protected])([email protected]))([email protected])([email protected])': '@payloadcms/[email protected]9.0(@types/[email protected])([email protected]9.0([email protected])([email protected])([email protected]))([email protected])([email protected])':
dependencies: dependencies:
'@libsql/client': 0.14.0 '@libsql/client': 0.14.0
'@payloadcms/drizzle': 3.88.0(@libsql/[email protected])(@types/[email protected])([email protected]8.0([email protected])([email protected])([email protected]))([email protected]) '@payloadcms/drizzle': 3.89.0(@libsql/[email protected])(@types/[email protected])([email protected]9.0([email protected])([email protected])([email protected]))([email protected])
console-table-printer: 2.12.1 console-table-printer: 2.12.1
drizzle-kit: 0.31.7([email protected]) drizzle-kit: 0.31.7([email protected])
drizzle-orm: 0.45.2(@libsql/[email protected])(@types/[email protected])([email protected]) drizzle-orm: 0.45.2(@libsql/[email protected])(@types/[email protected])([email protected])
payload: 3.88.0([email protected])([email protected])([email protected]) payload: 3.89.0([email protected])([email protected])([email protected])
prompts: 2.4.2 prompts: 2.4.2
to-snake-case: 1.0.0 to-snake-case: 1.0.0
uuid: 13.0.2 uuid: 13.0.2
@@ -6658,12 +6670,12 @@ snapshots:
- supports-color - supports-color
- utf-8-validate - utf-8-validate
'@payloadcms/[email protected]8.0(@libsql/[email protected])(@types/[email protected])([email protected]8.0([email protected])([email protected])([email protected]))([email protected])': '@payloadcms/[email protected]9.0(@libsql/[email protected])(@types/[email protected])([email protected]9.0([email protected])([email protected])([email protected]))([email protected])':
dependencies: dependencies:
console-table-printer: 2.12.1 console-table-printer: 2.12.1
dequal: 2.0.3 dequal: 2.0.3
drizzle-orm: 0.45.2(@libsql/[email protected])(@types/[email protected])([email protected]) drizzle-orm: 0.45.2(@libsql/[email protected])(@types/[email protected])([email protected])
payload: 3.88.0([email protected])([email protected])([email protected]) payload: 3.89.0([email protected])([email protected])([email protected])
prompts: 2.4.2 prompts: 2.4.2
to-snake-case: 1.0.0 to-snake-case: 1.0.0
uuid: 13.0.2 uuid: 13.0.2
@@ -6698,25 +6710,25 @@ snapshots:
- sql.js - sql.js
- sqlite3 - sqlite3
'@payloadcms/[email protected]8.0([email protected])([email protected]8.0([email protected])([email protected])([email protected]))([email protected])': '@payloadcms/[email protected]9.0([email protected])([email protected]9.0([email protected])([email protected])([email protected]))([email protected])':
dependencies: dependencies:
graphql: 16.14.2 graphql: 16.14.2
graphql-scalars: 1.22.2([email protected]) graphql-scalars: 1.22.2([email protected])
payload: 3.88.0([email protected])([email protected])([email protected]) payload: 3.89.0([email protected])([email protected])([email protected])
pluralize: 8.0.0 pluralize: 8.0.0
ts-essentials: 10.0.3([email protected]) ts-essentials: 10.0.3([email protected])
tsx: 4.22.4 tsx: 4.22.4
transitivePeerDependencies: transitivePeerDependencies:
- typescript - typescript
'@payloadcms/[email protected]8.0(@types/[email protected])([email protected])([email protected])([email protected](@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]8.0([email protected])([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])([email protected])': '@payloadcms/[email protected]9.0(@types/[email protected])([email protected])([email protected])([email protected](@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]9.0([email protected])([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])([email protected])':
dependencies: dependencies:
'@dnd-kit/core': 6.3.1([email protected]([email protected]))([email protected]) '@dnd-kit/core': 6.3.1([email protected]([email protected]))([email protected])
'@dnd-kit/modifiers': 9.0.0(@dnd-kit/[email protected]([email protected]([email protected]))([email protected]))([email protected]) '@dnd-kit/modifiers': 9.0.0(@dnd-kit/[email protected]([email protected]([email protected]))([email protected]))([email protected])
'@dnd-kit/sortable': 10.0.0(@dnd-kit/[email protected]([email protected]([email protected]))([email protected]))([email protected]) '@dnd-kit/sortable': 10.0.0(@dnd-kit/[email protected]([email protected]([email protected]))([email protected]))([email protected])
'@payloadcms/graphql': 3.88.0([email protected])([email protected]8.0([email protected])([email protected])([email protected]))([email protected]) '@payloadcms/graphql': 3.89.0([email protected])([email protected]9.0([email protected])([email protected])([email protected]))([email protected])
'@payloadcms/translations': 3.88.0 '@payloadcms/translations': 3.89.0
'@payloadcms/ui': 3.88.0(@types/[email protected])([email protected])([email protected](@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]8.0([email protected])([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])([email protected]) '@payloadcms/ui': 3.89.0(@types/[email protected])([email protected])([email protected](@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]9.0([email protected])([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])([email protected])
busboy: 1.6.0 busboy: 1.6.0
dequal: 2.0.3 dequal: 2.0.3
file-type: 21.3.4([email protected]) file-type: 21.3.4([email protected])
@@ -6726,7 +6738,7 @@ snapshots:
http-status: 2.1.0 http-status: 2.1.0
next: 16.3.3(@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected]) next: 16.3.3(@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected])
path-to-regexp: 6.3.0 path-to-regexp: 6.3.0
payload: 3.88.0([email protected])([email protected])([email protected]) payload: 3.89.0([email protected])([email protected])([email protected])
qs-esm: 8.0.1 qs-esm: 8.0.1
sass: 1.77.4 sass: 1.77.4
uuid: 13.0.2 uuid: 13.0.2
@@ -6738,11 +6750,11 @@ snapshots:
- supports-color - supports-color
- typescript - typescript
'@payloadcms/[email protected]8.0(@types/[email protected])([email protected])([email protected](@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]8.0([email protected])([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])([email protected])': '@payloadcms/[email protected]9.0(@types/[email protected])([email protected])([email protected](@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]9.0([email protected])([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])([email protected])':
dependencies: dependencies:
'@payloadcms/ui': 3.88.0(@types/[email protected])([email protected])([email protected](@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]8.0([email protected])([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])([email protected]) '@payloadcms/ui': 3.89.0(@types/[email protected])([email protected])([email protected](@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]9.0([email protected])([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])([email protected])
find-node-modules: 2.1.3 find-node-modules: 2.1.3
payload: 3.88.0([email protected])([email protected])([email protected]) payload: 3.89.0([email protected])([email protected])([email protected])
range-parser: 1.3.0 range-parser: 1.3.0
react: 19.2.6 react: 19.2.6
react-dom: 19.2.6([email protected]) react-dom: 19.2.6([email protected])
@@ -6753,12 +6765,12 @@ snapshots:
- supports-color - supports-color
- typescript - typescript
'@payloadcms/[email protected]8.0(@types/[email protected])([email protected])([email protected](@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]8.0([email protected])([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])([email protected])': '@payloadcms/[email protected]9.0(@types/[email protected])([email protected])([email protected](@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]9.0([email protected])([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])([email protected])':
dependencies: dependencies:
'@payloadcms/translations': 3.88.0 '@payloadcms/translations': 3.89.0
'@payloadcms/ui': 3.88.0(@types/[email protected])([email protected])([email protected](@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]8.0([email protected])([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])([email protected]) '@payloadcms/ui': 3.89.0(@types/[email protected])([email protected])([email protected](@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]9.0([email protected])([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])([email protected])
escape-html: 1.0.3 escape-html: 1.0.3
payload: 3.88.0([email protected])([email protected])([email protected]) payload: 3.89.0([email protected])([email protected])([email protected])
react: 19.2.6 react: 19.2.6
react-dom: 19.2.6([email protected]) react-dom: 19.2.6([email protected])
transitivePeerDependencies: transitivePeerDependencies:
@@ -6768,24 +6780,24 @@ snapshots:
- supports-color - supports-color
- typescript - typescript
'@payloadcms/[email protected]8.0([email protected](@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]8.0([email protected])([email protected])([email protected]))([email protected])': '@payloadcms/[email protected]9.0([email protected](@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]9.0([email protected])([email protected])([email protected]))([email protected])':
dependencies: dependencies:
'@modelcontextprotocol/sdk': 1.30.0([email protected])([email protected]) '@modelcontextprotocol/sdk': 1.30.0([email protected])([email protected])
'@types/json-schema': 7.0.15 '@types/json-schema': 7.0.15
json-schema-to-zod: 2.6.1 json-schema-to-zod: 2.6.1
mcp-handler: 1.1.0(@modelcontextprotocol/[email protected]([email protected])([email protected]))([email protected](@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected])) mcp-handler: 1.1.0(@modelcontextprotocol/[email protected]([email protected])([email protected]))([email protected](@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected]))
payload: 3.88.0([email protected])([email protected])([email protected]) payload: 3.89.0([email protected])([email protected])([email protected])
zod: 3.25.76 zod: 3.25.76
transitivePeerDependencies: transitivePeerDependencies:
- '@cfworker/json-schema' - '@cfworker/json-schema'
- next - next
- supports-color - supports-color
'@payloadcms/[email protected]8.0(@types/[email protected])([email protected])([email protected](@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]8.0([email protected])([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])([email protected])': '@payloadcms/[email protected]9.0(@types/[email protected])([email protected])([email protected](@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]9.0([email protected])([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])([email protected])':
dependencies: dependencies:
'@payloadcms/translations': 3.88.0 '@payloadcms/translations': 3.89.0
'@payloadcms/ui': 3.88.0(@types/[email protected])([email protected])([email protected](@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]8.0([email protected])([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])([email protected]) '@payloadcms/ui': 3.89.0(@types/[email protected])([email protected])([email protected](@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]9.0([email protected])([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])([email protected])
payload: 3.88.0([email protected])([email protected])([email protected]) payload: 3.89.0([email protected])([email protected])([email protected])
react: 19.2.6 react: 19.2.6
react-dom: 19.2.6([email protected]) react-dom: 19.2.6([email protected])
transitivePeerDependencies: transitivePeerDependencies:
@@ -6795,7 +6807,7 @@ snapshots:
- supports-color - supports-color
- typescript - typescript
'@payloadcms/[email protected]8.0(@faceless-ui/[email protected]([email protected]([email protected]))([email protected]))(@faceless-ui/[email protected]([email protected]([email protected]))([email protected]))(@payloadcms/[email protected]8.0(@types/[email protected])([email protected])([email protected])([email protected](@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]8.0([email protected])([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])([email protected]))(@types/[email protected])([email protected])([email protected](@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]8.0([email protected])([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])([email protected])([email protected])': '@payloadcms/[email protected]9.0(@faceless-ui/[email protected]([email protected]([email protected]))([email protected]))(@faceless-ui/[email protected]([email protected]([email protected]))([email protected]))(@payloadcms/[email protected]9.0(@types/[email protected])([email protected])([email protected])([email protected](@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]9.0([email protected])([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])([email protected]))(@types/[email protected])([email protected])([email protected](@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]9.0([email protected])([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])([email protected])([email protected])':
dependencies: dependencies:
'@faceless-ui/modal': 3.0.0([email protected]([email protected]))([email protected]) '@faceless-ui/modal': 3.0.0([email protected]([email protected]))([email protected])
'@faceless-ui/scroll-info': 2.0.0([email protected]([email protected]))([email protected]) '@faceless-ui/scroll-info': 2.0.0([email protected]([email protected]))([email protected])
@@ -6810,9 +6822,9 @@ snapshots:
'@lexical/selection': 0.41.0 '@lexical/selection': 0.41.0
'@lexical/table': 0.41.0 '@lexical/table': 0.41.0
'@lexical/utils': 0.41.0 '@lexical/utils': 0.41.0
'@payloadcms/next': 3.88.0(@types/[email protected])([email protected])([email protected])([email protected](@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]8.0([email protected])([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])([email protected]) '@payloadcms/next': 3.89.0(@types/[email protected])([email protected])([email protected])([email protected](@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]9.0([email protected])([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])([email protected])
'@payloadcms/translations': 3.88.0 '@payloadcms/translations': 3.89.0
'@payloadcms/ui': 3.88.0(@types/[email protected])([email protected])([email protected](@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]8.0([email protected])([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])([email protected]) '@payloadcms/ui': 3.89.0(@types/[email protected])([email protected])([email protected](@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]9.0([email protected])([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])([email protected])
acorn: 8.16.0 acorn: 8.16.0
bson-objectid: 2.0.4 bson-objectid: 2.0.4
csstype: 3.1.3 csstype: 3.1.3
@@ -6823,7 +6835,7 @@ snapshots:
mdast-util-from-markdown: 2.0.2([email protected]) mdast-util-from-markdown: 2.0.2([email protected])
mdast-util-mdx-jsx: 3.1.3([email protected]) mdast-util-mdx-jsx: 3.1.3([email protected])
micromark-extension-mdx-jsx: 3.0.1 micromark-extension-mdx-jsx: 3.0.1
payload: 3.88.0([email protected])([email protected])([email protected]) payload: 3.89.0([email protected])([email protected])([email protected])
qs-esm: 8.0.1 qs-esm: 8.0.1
react: 19.2.6 react: 19.2.6
react-dom: 19.2.6([email protected]) react-dom: 19.2.6([email protected])
@@ -6840,13 +6852,13 @@ snapshots:
- utf-8-validate - utf-8-validate
- yjs - yjs
'@payloadcms/[email protected]8.0(@types/[email protected])([email protected])([email protected](@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]8.0([email protected])([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])([email protected])': '@payloadcms/[email protected]9.0(@types/[email protected])([email protected])([email protected](@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]9.0([email protected])([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])([email protected])':
dependencies: dependencies:
'@aws-sdk/client-s3': 3.1121.0 '@aws-sdk/client-s3': 3.1121.0
'@aws-sdk/lib-storage': 3.1121.0(@aws-sdk/[email protected]) '@aws-sdk/lib-storage': 3.1121.0(@aws-sdk/[email protected])
'@aws-sdk/s3-request-presigner': 3.1121.0 '@aws-sdk/s3-request-presigner': 3.1121.0
'@payloadcms/plugin-cloud-storage': 3.88.0(@types/[email protected])([email protected])([email protected](@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]8.0([email protected])([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])([email protected]) '@payloadcms/plugin-cloud-storage': 3.89.0(@types/[email protected])([email protected])([email protected](@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]9.0([email protected])([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])([email protected])
payload: 3.88.0([email protected])([email protected])([email protected]) payload: 3.89.0([email protected])([email protected])([email protected])
transitivePeerDependencies: transitivePeerDependencies:
- '@types/react' - '@types/react'
- monaco-editor - monaco-editor
@@ -6856,11 +6868,11 @@ snapshots:
- supports-color - supports-color
- typescript - typescript
'@payloadcms/[email protected]8.0': '@payloadcms/[email protected]9.0':
dependencies: dependencies:
date-fns: 4.1.0 date-fns: 4.1.0
'@payloadcms/[email protected]8.0(@types/[email protected])([email protected])([email protected](@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]8.0([email protected])([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])([email protected])': '@payloadcms/[email protected]9.0(@types/[email protected])([email protected])([email protected](@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]9.0([email protected])([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])([email protected])':
dependencies: dependencies:
'@date-fns/tz': 1.2.0 '@date-fns/tz': 1.2.0
'@dnd-kit/core': 6.3.1([email protected]([email protected]))([email protected]) '@dnd-kit/core': 6.3.1([email protected]([email protected]))([email protected])
@@ -6870,14 +6882,15 @@ snapshots:
'@faceless-ui/scroll-info': 2.0.0([email protected]([email protected]))([email protected]) '@faceless-ui/scroll-info': 2.0.0([email protected]([email protected]))([email protected])
'@faceless-ui/window-info': 3.0.1([email protected]([email protected]))([email protected]) '@faceless-ui/window-info': 3.0.1([email protected]([email protected]))([email protected])
'@monaco-editor/react': 4.7.0([email protected])([email protected]([email protected]))([email protected]) '@monaco-editor/react': 4.7.0([email protected])([email protected]([email protected]))([email protected])
'@payloadcms/translations': 3.88.0 '@payloadcms/translations': 3.89.0
'@swc/helpers': 0.5.23
bson-objectid: 2.0.4 bson-objectid: 2.0.4
date-fns: 4.1.0 date-fns: 4.1.0
dequal: 2.0.3 dequal: 2.0.3
md5: 2.3.0 md5: 2.3.0
next: 16.3.3(@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected]) next: 16.3.3(@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected])
object-to-formdata: 4.5.1 object-to-formdata: 4.5.1
payload: 3.88.0([email protected])([email protected])([email protected]) payload: 3.89.0([email protected])([email protected])([email protected])
qs-esm: 8.0.1 qs-esm: 8.0.1
react: 19.2.6 react: 19.2.6
react-datepicker: 7.6.0([email protected]([email protected]))([email protected]) react-datepicker: 7.6.0([email protected]([email protected]))([email protected])
@@ -9651,10 +9664,10 @@ snapshots:
[email protected]: {} [email protected]: {}
[email protected]8.0([email protected])([email protected])([email protected]): [email protected]9.0([email protected])([email protected])([email protected]):
dependencies: dependencies:
'@next/env': 15.5.24 '@next/env': 15.5.24
'@payloadcms/translations': 3.88.0 '@payloadcms/translations': 3.89.0
'@types/busboy': 1.5.4 '@types/busboy': 1.5.4
ajv: 8.18.0 ajv: 8.18.0
bson-objectid: 2.0.4 bson-objectid: 2.0.4
+2
View File
@@ -27,6 +27,8 @@ placeholderach niezależnie od tego, jak dobrze zbudowany będzie kod.
| 10 | Skala firmy: zespół ~20 osób | blok `team` na `/o-nas` | Potrzebne dane (imię, nazwisko, rola, opcjonalnie zdjęcie i krótki opis) dla każdej z ~20 osób, żeby wypełnić panel. | | 10 | Skala firmy: zespół ~20 osób | blok `team` na `/o-nas` | Potrzebne dane (imię, nazwisko, rola, opcjonalnie zdjęcie i krótki opis) dla każdej z ~20 osób, żeby wypełnić panel. |
| 11 | Aktualność progów `18 000 € / 36 000 €` i `§4 Nr. 11 StBerG` na bieżący rok podatkowy | blok `eligibility` | treść już jest, wymaga tylko potwierdzenia aktualności, nie przepisywania | | 11 | Aktualność progów `18 000 € / 36 000 €` i `§4 Nr. 11 StBerG` na bieżący rok podatkowy | blok `eligibility` | treść już jest, wymaga tylko potwierdzenia aktualności, nie przepisywania |
| 12 | Teksty bannera cookies PL/DE | global `Cookie Settings` w panelu | puste w bazie — renderuje się angielski fallback z pluginu, wymaga uzupełnienia | | 12 | Teksty bannera cookies PL/DE | global `Cookie Settings` w panelu | puste w bazie — renderuje się angielski fallback z pluginu, wymaga uzupełnienia |
| 13 | Dane SMTP do wysyłki maili | Panel CMS → Integrations → Email | Formularz kontaktowy nie wyśle powiadomień bez skonfigurowanego serwera poczty. |
| 14 | Klucze Cloudflare Turnstile | `.env` (`NEXT_PUBLIC_TURNSTILE_SITE_KEY`, `TURNSTILE_SECRET_KEY`) | Wymagane do działania weryfikacji antyspamowej w formularzu kontaktowym. |
--- ---
+117
View File
@@ -0,0 +1,117 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
import fs from 'fs'
const ALLOWED_DUPLICATES = [
"b.b.h. Lohnsteuerhilfeverein e.V.",
"§4 Nr. 11 StBerG",
"Finanz- & Lohnbuchhaltung",
"Dipl.-Ing. Janusz Kędzierski",
"JK"
]
function isAllowedDuplicate(val: string): boolean {
if (!val || typeof val !== 'string') return true;
const trimmed = val.trim();
if (ALLOWED_DUPLICATES.includes(trimmed)) return true;
if (trimmed.startsWith('http') || trimmed.startsWith('/') || trimmed.startsWith('tel:') || trimmed.startsWith('mailto:')) return true;
// phone numbers, pure digits
if (/^[\d\s\+\-\(\)]+$/.test(trimmed)) return true;
// emails
if (trimmed.includes('@') && !trimmed.includes(' ')) return true;
return false;
}
function extractText(val: any): string {
if (typeof val === 'string') return val;
if (val && typeof val === 'object' && val.root && val.root.children) {
// Basic rich text extractor
let text = '';
const traverse = (node: any) => {
if (node.text) text += node.text;
if (node.children) node.children.forEach(traverse);
};
traverse(val.root);
return text.trim();
}
return JSON.stringify(val);
}
function compareObjects(objPL: any, objDE: any, path: string, results: string[]) {
if (!objPL || !objDE) return;
if (typeof objPL !== 'object' || typeof objDE !== 'object') return;
// If array
if (Array.isArray(objPL)) {
for (let i = 0; i < objPL.length; i++) {
compareObjects(objPL[i], objDE[i], `${path}[${i}]`, results);
}
return;
}
// Iterate keys
for (const key of Object.keys(objPL)) {
if ([
'id', 'createdAt', 'updatedAt', 'blockType', 'blockName',
'slug', 'value', 'name', 'icon', 'globalType', 'confirmationType',
'type', 'url', 'siteName', 'titleOrder', 'titleSeparator',
'defaultTheme', 'relationTo', 'status'
].includes(key)) continue;
const valPL = objPL[key];
const valDE = objDE[key];
if (typeof valPL === 'string' || (valPL && valPL.root)) {
const textPL = extractText(valPL);
const textDE = extractText(valDE);
if (textPL && textPL === textDE) {
if (!isAllowedDuplicate(textPL)) {
results.push(`[PODEJRZANY DUPLIKAT] ${path}.${key} | PL: "${textPL.substring(0, 50).replace(/\n/g, ' ')}" | DE: "${textDE.substring(0, 50).replace(/\n/g, ' ')}"`);
}
}
} else if (typeof valPL === 'object') {
compareObjects(valPL, valDE, `${path}.${key}`, results);
}
}
}
async function run() {
const payload = await getPayload({ config: configPromise })
const results: string[] = [];
console.log('Fetching pages...');
const pagesPL = await payload.find({ collection: 'pages', depth: 2, locale: 'pl', limit: 100 })
const pagesDE = await payload.find({ collection: 'pages', depth: 2, locale: 'de', limit: 100 })
for (const pagePL of pagesPL.docs) {
const pageDE = pagesDE.docs.find(p => p.id === pagePL.id)
if (pageDE) {
compareObjects(pagePL, pageDE, `Page(${pagePL.slug})`, results);
}
}
const globals = ['header', 'footer', 'company', 'site-settings']
for (const slug of globals) {
console.log(`Fetching global ${slug}...`);
const globalPL = await payload.findGlobal({ slug: slug as any, depth: 2, locale: 'pl' })
const globalDE = await payload.findGlobal({ slug: slug as any, depth: 2, locale: 'de' })
compareObjects(globalPL, globalDE, `Global(${slug})`, results);
}
console.log('Fetching forms...');
const formsPL = await payload.find({ collection: 'forms', depth: 2, locale: 'pl', limit: 100 })
const formsDE = await payload.find({ collection: 'forms', depth: 2, locale: 'de', limit: 100 })
for (const formPL of formsPL.docs) {
const formDE = formsDE.docs.find(f => f.id === formPL.id)
if (formDE) {
compareObjects(formPL, formDE, `Form(${formPL.title})`, results);
}
}
fs.writeFileSync('audit_results.txt', results.join('\n'));
console.log(`Audit complete. Found ${results.length} suspicious fields.`);
process.exit(0)
}
run()
+37
View File
@@ -0,0 +1,37 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
async function run() {
const payload = await getPayload({ config: configPromise })
const locales = ['pl', 'de']
let foundEmpty = false
for (const locale of locales) {
const pages = await payload.find({ collection: 'pages', locale: locale as 'pl'|'de', limit: 100 })
for (const page of pages.docs) {
const layout = page.layout || []
for (const block of layout) {
// Iterate over keys of the block to find arrays
for (const key of Object.keys(block)) {
const value = block[key]
if (Array.isArray(value) && value.length === 0) {
// Some arrays might naturally be empty if they are optional, but most of these are required
// Let's flag any empty array in these block fields
if (['steps', 'questions', 'services', 'members', 'stats', 'points', 'testimonials', 'logos', 'bullets', 'checkmarks'].includes(key)) {
console.log(`[UWAGA] Pusta tablica '${key}' znaleziona w bloku '${block.blockType}' na stronie '${page.slug}' (locale: ${locale})`)
foundEmpty = true
}
}
}
}
}
}
if (!foundEmpty) {
console.log("Wszystkie tablice w blokach na stronach (PL i DE) są pełne. Stan: OK.")
}
process.exit(0)
}
run().catch(console.error)
+12
View File
@@ -0,0 +1,12 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
async function run() {
const payload = await getPayload({ config: configPromise })
const forms = await payload.find({ collection: 'forms', locale: 'all' });
console.log('Available forms:', forms.docs.map(f => ({ id: f.id, title: f.title })));
process.exit(0);
}
run().catch(console.error);
+24
View File
@@ -0,0 +1,24 @@
import http from 'http';
function fetchHTML(path) {
return new Promise((resolve, reject) => {
http.get(`http://localhost:3000${path}`, (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => resolve(data));
}).on('error', reject);
});
}
async function run() {
for (const locale of ['pl', 'de']) {
const html = await fetchHTML(`/${locale}/kontakt`);
console.log(`\n=== OPTIONS FOR /${locale}/kontakt ===`);
const regex = /<option[^>]*>([^<]*)<\/option>/g;
let match;
while ((match = regex.exec(html)) !== null) {
console.log(`[OPTION] ${match[1]}`);
}
}
}
run();
+47
View File
@@ -0,0 +1,47 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
async function run() {
const payload = await getPayload({ config: configPromise })
for (const locale of ['pl', 'de']) {
const pages = await payload.find({
collection: 'pages',
locale: locale as 'pl' | 'de',
where: { slug: { equals: 'kontakt' } }
})
if (pages.docs.length > 0) {
const page = pages.docs[0];
const formBlock = page.layout?.find(b => b.blockType === 'contactSection');
if (formBlock && formBlock.form) {
// Fetch the form
const formId = typeof formBlock.form === 'object' ? formBlock.form.id : formBlock.form;
console.log(`\n=== FORMULARZ PODPIĘTY POD /${locale}/kontakt (ID: ${formId}) ===`);
const form = await payload.findByID({
collection: 'forms',
id: formId as number,
locale: locale as 'pl' | 'de',
});
const serviceField = form.fields?.find(f => f.name === 'service' && f.blockType === 'select');
console.log(`POLE SERVICE OPTIONS (LOCALE: ${locale}):`);
console.log(JSON.stringify(serviceField?.options, null, 2));
// Let's also check placeholders and other texts for step 5
console.log(`\nPOZOSTAŁE POLA (LOCALE: ${locale}):`);
form.fields?.forEach(f => {
if (f.blockType !== 'select') {
const placeholderValue = f.defaultValue || (f.name === 'email' ? (f.label === 'Adres e-mail' ? '[email protected]' : '[email protected]') : '');
console.log(`- ${f.name} (${f.blockType}): label="${f.label}", placeholder="${placeholderValue}"`);
}
});
}
}
}
process.exit(0);
}
run().catch(console.error);
+31
View File
@@ -0,0 +1,31 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
async function run() {
const payload = await getPayload({ config: configPromise })
const resPl = await payload.find({
collection: 'pages',
where: { slug: { equals: 'jak-sie-umowic' } },
locale: 'pl',
depth: 1
})
const resDe = await payload.find({
collection: 'pages',
where: { slug: { equals: 'jak-sie-umowic' } },
locale: 'de',
depth: 1
})
console.log("=== PL ===")
const processPl = resPl.docs[0]?.layout?.find(b => b.blockType === 'process')
console.log(JSON.stringify(processPl, null, 2))
console.log("\\n=== DE ===")
const processDe = resDe.docs[0]?.layout?.find(b => b.blockType === 'process')
console.log(JSON.stringify(processDe, null, 2))
process.exit(0)
}
run().catch(console.error)
+20
View File
@@ -0,0 +1,20 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
async function run() {
const payload = await getPayload({ config: configPromise })
const resDe = await payload.find({
collection: 'pages',
where: { slug: { equals: 'terminvereinbarung' } },
locale: 'de',
depth: 1
})
console.log("\\n=== DE (terminvereinbarung) ===")
const processDe = resDe.docs[0]?.layout?.find(b => b.blockType === 'process')
console.log(JSON.stringify(processDe, null, 2))
process.exit(0)
}
run().catch(console.error)
+43
View File
@@ -0,0 +1,43 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
async function run() {
const payload = await getPayload({ config: configPromise })
console.log('--- KOTWICE USŁUG (HOME -> USLUGI) ---')
for (const locale of ['pl', 'de'] as const) {
const pages = await payload.find({ collection: 'pages', locale, where: { slug: { equals: 'home' } } })
if (pages.docs.length > 0) {
console.log(`\nStrona ${locale.toUpperCase()} (Home):`)
pages.docs[0].layout?.forEach((b:any) => {
if (b.blockType === 'servicesList' && b.services) {
b.services.forEach((s:any) => console.log(` - ${s.title} -> ${s.linkUrl}`))
}
})
}
}
console.log('\n--- FORMULARZ KONTAKTOWY (PL vs DE) ---')
const forms = await payload.find({ collection: 'forms', locale: 'all', limit: 1 })
if (forms.docs.length > 0) {
const form = forms.docs[0] as any
console.log(`\nFormularz ID: ${form.id}`)
console.log(`Tytuł: PL="${form.title.pl}", DE="${form.title.de}"`)
console.log(`Wiadomość z podziękowaniem (PL): ${form.confirmationMessage.pl.root.children[0].children[0].text}`)
console.log(`Wiadomość z podziękowaniem (DE): ${form.confirmationMessage.de.root.children[0].children[0].text}`)
console.log(`Temat email (PL): ${form.emails[0].subject.pl}`)
console.log(`Temat email (DE): ${form.emails[0].subject.de}`)
console.log('\nPola formularza:')
form.fields?.forEach((f:any) => {
console.log(` - Pole ${f.blockType}: ${f.name} (Etykieta PL: "${f.label.pl}", DE: "${f.label.de}")`)
if (f.blockType === 'select' && f.options) {
f.options.forEach((o:any) => {
console.log(` * [${o.value}] PL: "${o.label.pl}", DE: "${o.label.de}"`)
})
}
})
}
process.exit(0)
}
run()
+61
View File
@@ -0,0 +1,61 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
async function run() {
console.log('Rozpoczynam naprawę tłumaczenia bloku Team dla DE...')
const payload = await getPayload({ config: configPromise })
const pages = await payload.find({
collection: 'pages',
locale: 'de',
limit: 100
})
let updated = false
for (const page of pages.docs) {
if (!page.layout) continue
let changed = false
const newLayout = page.layout.map((block: any) => {
if (block.blockType === 'team') {
if (block.title === 'Nasz zespół' || block.title === 'Unser Team') {
console.log(`[DE] Wartość przed: title="${block.title}", subtitle="${block.subtitle}"`)
block.title = 'Unser Team'
block.subtitle = 'Lernen Sie die Experten kennen, die sich mit Engagement um Ihre Finanzen kümmern. Jeder von uns bringt langjährige Erfahrung mit, um Ihnen Sicherheit und Ruhe zu garantieren.'
console.log(`[DE] Wartość po: title="${block.title}", subtitle="${block.subtitle}"`)
if (block.members && block.members.length > 0) {
// Przetłumacz na sztywno Janusza jeśli jest w bazie, żeby rola/opis też miały sens
if (block.members[0].name.includes('Janusz')) {
block.members[0].role = 'Inhaber / Beratungsstellenleiter'
block.members[0].description = 'Langjähriger Steuerrechtspraktiker mit Spezialisierung auf die Beratung polnischer Arbeitnehmer in Deutschland.'
// keep URL as is
}
}
changed = true
}
}
return block
})
if (changed) {
console.log(`Aktualizacja strony DE: ${page.title} (ID: ${page.id})`)
await payload.update({
collection: 'pages',
id: page.id,
locale: 'de',
data: { layout: newLayout }
})
updated = true
}
}
if (updated) {
console.log('Zaktualizowano pomyślnie.')
} else {
console.log('Nie znaleziono bloku Team do aktualizacji na DE.')
}
process.exit(0)
}
run()
+114
View File
@@ -0,0 +1,114 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
async function run() {
const payload = await getPayload({ config: configPromise })
const addressDE = "Vom Kunden zu bestätigen";
const hoursDE = "Mo-Fr: 9:00 - 17:00\nNach vorheriger Terminvereinbarung";
// 1. Kontakt Page (ID: 5, slug: 'kontakt')
console.log('Fetching Kontakt DE...');
const kontaktDE = await payload.find({ collection: 'pages', locale: 'de', where: { slug: { equals: 'kontakt' } } });
if (kontaktDE.docs.length > 0) {
const doc = kontaktDE.docs[0];
let changed = false;
const newLayout = doc.layout?.map((block: any) => {
if (block.blockType === 'pageHeader') {
if (block.subtitle === 'Masz pytania dotyczące rozliczenia podatkowego w N' || block.subtitle === 'Masz pytania dotyczące rozliczenia podatkowego w Niemczech? Skontaktuj się z nami — chętnie pomożemy.') {
block.subtitle = 'Haben Sie Fragen zu Ihrer Steuererklärung in Deutschland? Kontaktieren Sie uns — wir helfen Ihnen gerne.';
changed = true;
}
}
if (block.blockType === 'contactSection') {
if (block.address === 'DO POTWIERDZENIA PRZEZ KLIENTA') {
block.address = addressDE;
changed = true;
}
if (block.hours === 'Pon-Pt: 9:00 - 17:00\nPo wcześniejszym umówieniu') {
block.hours = hoursDE;
changed = true;
}
}
if (changed) delete block.id;
return block;
});
if (changed) {
await payload.update({
collection: 'pages',
id: doc.id,
locale: 'de',
data: { layout: newLayout }
});
console.log('Updated Kontakt DE');
}
}
// 2. Jak się umówić Page (slug: 'jak-sie-umowic')
console.log('Fetching Jak się umówić DE...');
const jsuDE = await payload.find({ collection: 'pages', locale: 'de', where: { slug: { equals: 'terminvereinbarung' } } });
if (jsuDE.docs.length === 0) {
const jsuDE_plSlug = await payload.find({ collection: 'pages', locale: 'de', where: { slug: { equals: 'jak-sie-umowic' } } });
if (jsuDE_plSlug.docs.length > 0) {
jsuDE.docs = jsuDE_plSlug.docs;
}
}
if (jsuDE.docs.length > 0) {
const doc = jsuDE.docs[0];
let changed = false;
const newLayout = doc.layout?.map((block: any) => {
if (block.blockType === 'contactSection') {
if (block.address === 'DO POTWIERDZENIA PRZEZ KLIENTA') {
block.address = addressDE;
changed = true;
}
if (block.hours === 'Pon-Pt: 9:00 - 17:00\nPo wcześniejszym umówieniu') {
block.hours = hoursDE;
changed = true;
}
}
if (changed) delete block.id;
return block;
});
if (changed) {
await payload.update({
collection: 'pages',
id: doc.id,
locale: 'de',
data: { layout: newLayout }
});
console.log('Updated Jak się umówić DE');
}
}
// 3. Company Global
console.log('Fetching Company DE...');
const companyDE = await payload.findGlobal({ slug: 'company', locale: 'de' });
if (companyDE) {
let changed = false;
const updateData: any = {};
if (companyDE.address === 'DO POTWIERDZENIA PRZEZ KLIENTA') {
updateData.address = addressDE;
changed = true;
}
if (companyDE.hours === 'Pon-Pt: 9:00 - 17:00\nPo wcześniejszym umówieniu') {
updateData.hours = hoursDE;
changed = true;
}
if (changed) {
await payload.updateGlobal({
slug: 'company',
locale: 'de',
data: updateData
});
console.log('Updated Company DE');
}
}
console.log('Migration complete');
process.exit(0);
}
run()
+61
View File
@@ -0,0 +1,61 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
async function run() {
const payload = await getPayload({ config: configPromise })
for (const locale of ['pl', 'de']) {
const pages = await payload.find({
collection: 'pages',
locale: locale as 'pl' | 'de',
where: {
slug: { equals: 'kontakt' }
}
})
if (pages.docs.length === 0) continue;
const page = pages.docs[0];
let updated = false;
// We do point updates on the layout array
const newLayout = page.layout?.map((block: any) => {
// If we find contactInfo, upgrade it to contactSection
if (block.blockType === 'contactInfo') {
updated = true;
return {
...block,
blockType: 'contactSection',
form: 1 // Add the form relation
}
}
// If we already have contactSection but missing form, just add it (just in case)
if (block.blockType === 'contactSection' && !block.form) {
updated = true;
return {
...block,
form: 1
}
}
return block;
});
if (updated) {
await payload.update({
collection: 'pages',
id: page.id,
locale: locale as 'pl' | 'de',
data: {
layout: newLayout
}
});
console.log(`Updated layout for ${locale.toUpperCase()}`);
} else {
console.log(`No contactInfo block found to upgrade for ${locale.toUpperCase()}`);
}
}
process.exit(0);
}
run().catch(console.error);
+31
View File
@@ -0,0 +1,31 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
async function run() {
const payload = await getPayload({ config: configPromise })
const addressDE = "Vom Kunden zu bestätigen";
const hoursDE = "Mo-Fr: 9:00 - 17:00\nNach vorheriger Terminvereinbarung";
for (const slug of ['kontakt', 'terminvereinbarung']) {
const pages = await payload.find({ collection: 'pages', locale: 'de', where: { slug: { equals: slug } } });
if (pages.docs.length > 0) {
const doc = pages.docs[0];
const newLayout = doc.layout?.map((b: any) => {
if (b.blockType === 'contactInfo' || b.blockType === 'contactSection') {
b.address = addressDE;
b.hours = hoursDE;
}
return b;
});
await payload.update({
collection: 'pages',
id: doc.id,
locale: 'de',
data: { layout: newLayout }
});
console.log('Updated ' + slug);
}
}
process.exit(0);
}
run()
+92
View File
@@ -0,0 +1,92 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
function deepStripIds(obj: any): any {
if (Array.isArray(obj)) return obj.map(deepStripIds);
if (obj !== null && typeof obj === 'object') {
const newObj = { ...obj };
delete newObj.id;
for (const key of Object.keys(newObj)) {
newObj[key] = deepStripIds(newObj[key]);
}
return newObj;
}
return obj;
}
async function run() {
const payload = await getPayload({ config: configPromise })
const addressDE = "Vom Kunden zu bestätigen";
const hoursDE = "Mo-Fr: 9:00 - 17:00\nNach vorheriger Terminvereinbarung";
// Kontakt
const kontaktPL = await payload.find({ collection: 'pages', locale: 'pl', where: { slug: { equals: 'kontakt' } } });
if (kontaktPL.docs.length > 0) {
let changed = false;
const layoutDE = deepStripIds(kontaktPL.docs[0].layout).map((b: any) => {
const newB = { ...b };
if (newB.blockType === 'pageHeader') {
newB.subtitle = 'Haben Sie Fragen zu Ihrer Steuererklärung in Deutschland? Kontaktieren Sie uns — wir helfen Ihnen gerne.';
changed = true;
}
if (newB.blockType === 'contactSection') {
newB.address = addressDE;
newB.hours = hoursDE;
changed = true;
}
return newB;
});
if (changed) {
await payload.update({
collection: 'pages',
id: kontaktPL.docs[0].id,
locale: 'de',
data: { layout: layoutDE }
});
console.log('Updated Kontakt DE');
}
}
// Jak sie umowic
const jsuPL = await payload.find({ collection: 'pages', locale: 'pl', where: { slug: { equals: 'jak-sie-umowic' } } });
if (jsuPL.docs.length > 0) {
let changed = false;
const layoutDE = deepStripIds(jsuPL.docs[0].layout).map((b: any) => {
const newB = { ...b };
if (newB.blockType === 'contactSection') {
newB.address = addressDE;
newB.hours = hoursDE;
changed = true;
}
return newB;
});
if (changed) {
await payload.update({
collection: 'pages',
id: jsuPL.docs[0].id,
locale: 'de',
data: { layout: layoutDE }
});
console.log('Updated Jak Sie Umowic DE');
}
}
// Company Global
const companyDE = await payload.findGlobal({ slug: 'company', locale: 'de' });
if (companyDE) {
await payload.updateGlobal({
slug: 'company',
locale: 'de',
data: {
address: addressDE,
hours: hoursDE
}
});
console.log('Updated Company DE');
}
process.exit(0);
}
run()
+88
View File
@@ -0,0 +1,88 @@
[⣷] Pulling schema from database...
[✓] Pulling schema from database...
[15:52:56] INFO: [ipal] Plugin initialized.
=== FORMULARZ PODPIĘTY POD /pl/kontakt (ID: 1) ===
POLE SERVICE OPTIONS (LOCALE: pl):
[
{
"id": "6aab9bd69f369ef03e6bf859",
"value": "erstellung-der-einkommensteuererklarung"
},
{
"id": "6aab9bd69f369ef03e6bf85a",
"value": "steuervorausberechnung"
},
{
"id": "6aab9bd69f369ef03e6bf85b",
"value": "prufung-des-steuerbescheids"
},
{
"id": "6aab9bd69f369ef03e6bf85c",
"value": "einspruchsverfahren"
},
{
"id": "6aab9bd69f369ef03e6bf85d",
"value": "ganzjahrige-steuerliche-betreuung"
},
{
"id": "6aab9bd69f369ef03e6bf85e",
"value": "wahl-der-optimalen-steuerklasse"
},
{
"id": "6aab9bd69f369ef03e6bf85f",
"value": "freibetrage-kindergeld-and-fahrtkosten"
}
]
POZOSTAŁE POLA (LOCALE: pl):
- name (text): label="Imię i nazwisko", placeholder=""
- email (email): label="Adres e-mail", placeholder=""
- message (textarea): label="Wiadomość", placeholder=""
- consent (checkbox): label="Wyrażam zgodę na przetwarzanie danych", placeholder=""
=== FORMULARZ PODPIĘTY POD /de/kontakt (ID: 1) ===
POLE SERVICE OPTIONS (LOCALE: de):
[
{
"id": "6aab9bd69f369ef03e6bf859",
"value": "erstellung-der-einkommensteuererklarung",
"label": "Erstellung der Einkommensteuererklärung"
},
{
"id": "6aab9bd69f369ef03e6bf85a",
"value": "steuervorausberechnung",
"label": "Steuervorausberechnung"
},
{
"id": "6aab9bd69f369ef03e6bf85b",
"value": "prufung-des-steuerbescheids",
"label": "Prüfung des Steuerbescheids"
},
{
"id": "6aab9bd69f369ef03e6bf85c",
"value": "einspruchsverfahren",
"label": "Einspruchsverfahren"
},
{
"id": "6aab9bd69f369ef03e6bf85d",
"value": "ganzjahrige-steuerliche-betreuung",
"label": "Ganzjährige steuerliche Betreuung"
},
{
"id": "6aab9bd69f369ef03e6bf85e",
"value": "wahl-der-optimalen-steuerklasse",
"label": "Wahl der optimalen Steuerklasse"
},
{
"id": "6aab9bd69f369ef03e6bf85f",
"value": "freibetrage-kindergeld-and-fahrtkosten",
"label": "Freibeträge, Kindergeld & Fahrtkosten"
}
]
POZOSTAŁE POLA (LOCALE: de):
- name (text): label="Vor- und Nachname", placeholder=""
- email (email): label="E-Mail Adresse", placeholder=""
- message (textarea): label="Nachricht", placeholder=""
- consent (checkbox): label="Ich stimme der Datenverarbeitung zu", placeholder=""
+95
View File
@@ -0,0 +1,95 @@
[⣷] Pulling schema from database...
[✓] Pulling schema from database...
[16:03:33] INFO: [ipal] Plugin initialized.
=== FORMULARZ PODPIĘTY POD /pl/kontakt (ID: 1) ===
POLE SERVICE OPTIONS (LOCALE: pl):
[
{
"id": "6aabf118b9380a1f9b3da1a6",
"value": "sporzadzenie-deklaracji-podatkowej-einkommensteuererklarung",
"label": "Sporządzenie deklaracji podatkowej (Einkommensteuererklärung)"
},
{
"id": "6aabf118b9380a1f9b3da1a7",
"value": "wstepne-obliczenie-przewidywanego-zwrotu",
"label": "Wstępne obliczenie przewidywanego zwrotu"
},
{
"id": "6aabf118b9380a1f9b3da1a8",
"value": "kontrola-decyzji-podatkowej-steuerbescheid",
"label": "Kontrola decyzji podatkowej (Steuerbescheid)"
},
{
"id": "6aabf118b9380a1f9b3da1a9",
"value": "skladanie-odwolan-einspruch",
"label": "Składanie odwołań (Einspruch)"
},
{
"id": "6aabf118b9380a1f9b3da1aa",
"value": "caloroczne-doradztwo-podatkowe",
"label": "Całoroczne doradztwo podatkowe"
},
{
"id": "6aabf118b9380a1f9b3da1ab",
"value": "doradztwo-przy-wyborze-klasy-podatkowej-steuerklasse",
"label": "Doradztwo przy wyborze klasy podatkowej (Steuerklasse)"
},
{
"id": "6aabf118b9380a1f9b3da1ac",
"value": "ulgi-doplaty-i-odliczenia-kindergeld-dojazdy-podwojne-gospodarstwo",
"label": "Ulgi, dopłaty i odliczenia (Kindergeld, dojazdy, podwójne gospodarstwo)"
}
]
POZOSTAŁE POLA (LOCALE: pl):
- name (text): label="Imię i nazwisko", placeholder="Jan Kowalski"
- email (email): label="Adres e-mail", placeholder="[email protected]"
- message (textarea): label="Wiadomość", placeholder="Opisz krótko swoją sytuację..."
- consent (checkbox): label="Wyrażam zgodę na przetwarzanie danych", placeholder=""
=== FORMULARZ PODPIĘTY POD /de/kontakt (ID: 1) ===
POLE SERVICE OPTIONS (LOCALE: de):
[
{
"id": "6aabf118b9380a1f9b3da1a6",
"value": "sporzadzenie-deklaracji-podatkowej-einkommensteuererklarung",
"label": "Erstellung der Einkommensteuererklärung"
},
{
"id": "6aabf118b9380a1f9b3da1a7",
"value": "wstepne-obliczenie-przewidywanego-zwrotu",
"label": "Steuervorausberechnung"
},
{
"id": "6aabf118b9380a1f9b3da1a8",
"value": "kontrola-decyzji-podatkowej-steuerbescheid",
"label": "Prüfung des Steuerbescheids"
},
{
"id": "6aabf118b9380a1f9b3da1a9",
"value": "skladanie-odwolan-einspruch",
"label": "Einspruchsverfahren"
},
{
"id": "6aabf118b9380a1f9b3da1aa",
"value": "caloroczne-doradztwo-podatkowe",
"label": "Ganzjährige steuerliche Betreuung"
},
{
"id": "6aabf118b9380a1f9b3da1ab",
"value": "doradztwo-przy-wyborze-klasy-podatkowej-steuerklasse",
"label": "Wahl der optimalen Steuerklasse"
},
{
"id": "6aabf118b9380a1f9b3da1ac",
"value": "ulgi-doplaty-i-odliczenia-kindergeld-dojazdy-podwojne-gospodarstwo",
"label": "Freibeträge, Kindergeld & Fahrtkosten"
}
]
POZOSTAŁE POLA (LOCALE: de):
- name (text): label="Vor- und Nachname", placeholder="Max Mustermann"
- email (email): label="E-Mail Adresse", placeholder="[email protected]"
- message (textarea): label="Nachricht", placeholder="Beschreiben Sie kurz Ihre Situation..."
- consent (checkbox): label="Ich stimme der Datenverarbeitung zu", placeholder=""
+88
View File
@@ -0,0 +1,88 @@
[⣷] Pulling schema from database...
[✓] Pulling schema from database...
[15:54:43] INFO: [ipal] Plugin initialized.
=== FORMULARZ PODPIĘTY POD /pl/kontakt (ID: 1) ===
POLE SERVICE OPTIONS (LOCALE: pl):
[
{
"id": "6aabf118b9380a1f9b3da1a6",
"value": "sporzadzenie-deklaracji-podatkowej-einkommensteuererklarung"
},
{
"id": "6aabf118b9380a1f9b3da1a7",
"value": "wstepne-obliczenie-przewidywanego-zwrotu"
},
{
"id": "6aabf118b9380a1f9b3da1a8",
"value": "kontrola-decyzji-podatkowej-steuerbescheid"
},
{
"id": "6aabf118b9380a1f9b3da1a9",
"value": "skladanie-odwolan-einspruch"
},
{
"id": "6aabf118b9380a1f9b3da1aa",
"value": "caloroczne-doradztwo-podatkowe"
},
{
"id": "6aabf118b9380a1f9b3da1ab",
"value": "doradztwo-przy-wyborze-klasy-podatkowej-steuerklasse"
},
{
"id": "6aabf118b9380a1f9b3da1ac",
"value": "ulgi-doplaty-i-odliczenia-kindergeld-dojazdy-podwojne-gospodarstwo"
}
]
POZOSTAŁE POLA (LOCALE: pl):
- name (text): label="Imię i nazwisko", placeholder=""
- email (email): label="Adres e-mail", placeholder=""
- message (textarea): label="Wiadomość", placeholder=""
- consent (checkbox): label="Wyrażam zgodę na przetwarzanie danych", placeholder=""
=== FORMULARZ PODPIĘTY POD /de/kontakt (ID: 1) ===
POLE SERVICE OPTIONS (LOCALE: de):
[
{
"id": "6aabf118b9380a1f9b3da1a6",
"value": "sporzadzenie-deklaracji-podatkowej-einkommensteuererklarung",
"label": "Erstellung der Einkommensteuererklärung"
},
{
"id": "6aabf118b9380a1f9b3da1a7",
"value": "wstepne-obliczenie-przewidywanego-zwrotu",
"label": "Steuervorausberechnung"
},
{
"id": "6aabf118b9380a1f9b3da1a8",
"value": "kontrola-decyzji-podatkowej-steuerbescheid",
"label": "Prüfung des Steuerbescheids"
},
{
"id": "6aabf118b9380a1f9b3da1a9",
"value": "skladanie-odwolan-einspruch",
"label": "Einspruchsverfahren"
},
{
"id": "6aabf118b9380a1f9b3da1aa",
"value": "caloroczne-doradztwo-podatkowe",
"label": "Ganzjährige steuerliche Betreuung"
},
{
"id": "6aabf118b9380a1f9b3da1ab",
"value": "doradztwo-przy-wyborze-klasy-podatkowej-steuerklasse",
"label": "Wahl der optimalen Steuerklasse"
},
{
"id": "6aabf118b9380a1f9b3da1ac",
"value": "ulgi-doplaty-i-odliczenia-kindergeld-dojazdy-podwojne-gospodarstwo",
"label": "Freibeträge, Kindergeld & Fahrtkosten"
}
]
POZOSTAŁE POLA (LOCALE: de):
- name (text): label="Vor- und Nachname", placeholder=""
- email (email): label="E-Mail Adresse", placeholder=""
- message (textarea): label="Nachricht", placeholder=""
- consent (checkbox): label="Ich stimme der Datenverarbeitung zu", placeholder=""
+95
View File
@@ -0,0 +1,95 @@
[⣷] Pulling schema from database...
[✓] Pulling schema from database...
[16:00:33] INFO: [ipal] Plugin initialized.
=== FORMULARZ PODPIĘTY POD /pl/kontakt (ID: 1) ===
POLE SERVICE OPTIONS (LOCALE: pl):
[
{
"id": "6aabf118b9380a1f9b3da1a6",
"value": "sporzadzenie-deklaracji-podatkowej-einkommensteuererklarung",
"label": "Sporządzenie deklaracji podatkowej (Einkommensteuererklärung)"
},
{
"id": "6aabf118b9380a1f9b3da1a7",
"value": "wstepne-obliczenie-przewidywanego-zwrotu",
"label": "Wstępne obliczenie przewidywanego zwrotu"
},
{
"id": "6aabf118b9380a1f9b3da1a8",
"value": "kontrola-decyzji-podatkowej-steuerbescheid",
"label": "Kontrola decyzji podatkowej (Steuerbescheid)"
},
{
"id": "6aabf118b9380a1f9b3da1a9",
"value": "skladanie-odwolan-einspruch",
"label": "Składanie odwołań (Einspruch)"
},
{
"id": "6aabf118b9380a1f9b3da1aa",
"value": "caloroczne-doradztwo-podatkowe",
"label": "Całoroczne doradztwo podatkowe"
},
{
"id": "6aabf118b9380a1f9b3da1ab",
"value": "doradztwo-przy-wyborze-klasy-podatkowej-steuerklasse",
"label": "Doradztwo przy wyborze klasy podatkowej (Steuerklasse)"
},
{
"id": "6aabf118b9380a1f9b3da1ac",
"value": "ulgi-doplaty-i-odliczenia-kindergeld-dojazdy-podwojne-gospodarstwo",
"label": "Ulgi, dopłaty i odliczenia (Kindergeld, dojazdy, podwójne gospodarstwo)"
}
]
POZOSTAŁE POLA (LOCALE: pl):
- name (text): label="Imię i nazwisko", placeholder=""
- email (email): label="Adres e-mail", placeholder=""
- message (textarea): label="Wiadomość", placeholder=""
- consent (checkbox): label="Wyrażam zgodę na przetwarzanie danych", placeholder=""
=== FORMULARZ PODPIĘTY POD /de/kontakt (ID: 1) ===
POLE SERVICE OPTIONS (LOCALE: de):
[
{
"id": "6aabf118b9380a1f9b3da1a6",
"value": "sporzadzenie-deklaracji-podatkowej-einkommensteuererklarung",
"label": "Erstellung der Einkommensteuererklärung"
},
{
"id": "6aabf118b9380a1f9b3da1a7",
"value": "wstepne-obliczenie-przewidywanego-zwrotu",
"label": "Steuervorausberechnung"
},
{
"id": "6aabf118b9380a1f9b3da1a8",
"value": "kontrola-decyzji-podatkowej-steuerbescheid",
"label": "Prüfung des Steuerbescheids"
},
{
"id": "6aabf118b9380a1f9b3da1a9",
"value": "skladanie-odwolan-einspruch",
"label": "Einspruchsverfahren"
},
{
"id": "6aabf118b9380a1f9b3da1aa",
"value": "caloroczne-doradztwo-podatkowe",
"label": "Ganzjährige steuerliche Betreuung"
},
{
"id": "6aabf118b9380a1f9b3da1ab",
"value": "doradztwo-przy-wyborze-klasy-podatkowej-steuerklasse",
"label": "Wahl der optimalen Steuerklasse"
},
{
"id": "6aabf118b9380a1f9b3da1ac",
"value": "ulgi-doplaty-i-odliczenia-kindergeld-dojazdy-podwojne-gospodarstwo",
"label": "Freibeträge, Kindergeld & Fahrtkosten"
}
]
POZOSTAŁE POLA (LOCALE: de):
- name (text): label="Vor- und Nachname", placeholder=""
- email (email): label="E-Mail Adresse", placeholder=""
- message (textarea): label="Nachricht", placeholder=""
- consent (checkbox): label="Ich stimme der Datenverarbeitung zu", placeholder=""
+95
View File
@@ -0,0 +1,95 @@
[⣷] Pulling schema from database...
[✓] Pulling schema from database...
[16:02:47] INFO: [ipal] Plugin initialized.
=== FORMULARZ PODPIĘTY POD /pl/kontakt (ID: 1) ===
POLE SERVICE OPTIONS (LOCALE: pl):
[
{
"id": "6aabf118b9380a1f9b3da1a6",
"value": "sporzadzenie-deklaracji-podatkowej-einkommensteuererklarung",
"label": "Sporządzenie deklaracji podatkowej (Einkommensteuererklärung)"
},
{
"id": "6aabf118b9380a1f9b3da1a7",
"value": "wstepne-obliczenie-przewidywanego-zwrotu",
"label": "Wstępne obliczenie przewidywanego zwrotu"
},
{
"id": "6aabf118b9380a1f9b3da1a8",
"value": "kontrola-decyzji-podatkowej-steuerbescheid",
"label": "Kontrola decyzji podatkowej (Steuerbescheid)"
},
{
"id": "6aabf118b9380a1f9b3da1a9",
"value": "skladanie-odwolan-einspruch",
"label": "Składanie odwołań (Einspruch)"
},
{
"id": "6aabf118b9380a1f9b3da1aa",
"value": "caloroczne-doradztwo-podatkowe",
"label": "Całoroczne doradztwo podatkowe"
},
{
"id": "6aabf118b9380a1f9b3da1ab",
"value": "doradztwo-przy-wyborze-klasy-podatkowej-steuerklasse",
"label": "Doradztwo przy wyborze klasy podatkowej (Steuerklasse)"
},
{
"id": "6aabf118b9380a1f9b3da1ac",
"value": "ulgi-doplaty-i-odliczenia-kindergeld-dojazdy-podwojne-gospodarstwo",
"label": "Ulgi, dopłaty i odliczenia (Kindergeld, dojazdy, podwójne gospodarstwo)"
}
]
POZOSTAŁE POLA (LOCALE: pl):
- name (text): label="Imię i nazwisko", placeholder="Jan Kowalski"
- email (email): label="Adres e-mail", placeholder=""
- message (textarea): label="Wiadomość", placeholder="Opisz krótko swoją sytuację..."
- consent (checkbox): label="Wyrażam zgodę na przetwarzanie danych", placeholder=""
=== FORMULARZ PODPIĘTY POD /de/kontakt (ID: 1) ===
POLE SERVICE OPTIONS (LOCALE: de):
[
{
"id": "6aabf118b9380a1f9b3da1a6",
"value": "sporzadzenie-deklaracji-podatkowej-einkommensteuererklarung",
"label": "Erstellung der Einkommensteuererklärung"
},
{
"id": "6aabf118b9380a1f9b3da1a7",
"value": "wstepne-obliczenie-przewidywanego-zwrotu",
"label": "Steuervorausberechnung"
},
{
"id": "6aabf118b9380a1f9b3da1a8",
"value": "kontrola-decyzji-podatkowej-steuerbescheid",
"label": "Prüfung des Steuerbescheids"
},
{
"id": "6aabf118b9380a1f9b3da1a9",
"value": "skladanie-odwolan-einspruch",
"label": "Einspruchsverfahren"
},
{
"id": "6aabf118b9380a1f9b3da1aa",
"value": "caloroczne-doradztwo-podatkowe",
"label": "Ganzjährige steuerliche Betreuung"
},
{
"id": "6aabf118b9380a1f9b3da1ab",
"value": "doradztwo-przy-wyborze-klasy-podatkowej-steuerklasse",
"label": "Wahl der optimalen Steuerklasse"
},
{
"id": "6aabf118b9380a1f9b3da1ac",
"value": "ulgi-doplaty-i-odliczenia-kindergeld-dojazdy-podwojne-gospodarstwo",
"label": "Freibeträge, Kindergeld & Fahrtkosten"
}
]
POZOSTAŁE POLA (LOCALE: de):
- name (text): label="Vor- und Nachname", placeholder="Max Mustermann"
- email (email): label="E-Mail Adresse", placeholder=""
- message (textarea): label="Nachricht", placeholder="Beschreiben Sie kurz Ihre Situation..."
- consent (checkbox): label="Ich stimme der Datenverarbeitung zu", placeholder=""
+57
View File
@@ -0,0 +1,57 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
async function run() {
const payload = await getPayload({ config: configPromise })
const pages = await payload.find({
collection: 'pages',
where: {
slug: {
equals: 'kontakt'
}
},
locale: 'pl',
});
if (pages.docs.length === 0) {
console.log("No Kontakt page found");
process.exit(0);
}
const page = pages.docs[0];
console.log("=== LAYOUT PL ===");
if (page.layout) {
page.layout.forEach((block: any, idx: number) => {
console.log(`[${idx}] ${block.blockType}`);
});
} else {
console.log("No layout for PL");
}
console.log("\n=== LAYOUT DE ===");
const pageDe = await payload.findByID({
collection: 'pages',
id: page.id,
locale: 'de',
});
if (pageDe.layout) {
pageDe.layout.forEach((block: any, idx: number) => {
console.log(`[${idx}] ${block.blockType}`);
});
} else {
console.log("No layout for DE");
}
console.log("\n=== FULL DUMP DE ===");
console.log(JSON.stringify(pageDe.layout, null, 2));
console.log("\n=== FULL DUMP PL ===");
console.log(JSON.stringify(page.layout, null, 2));
process.exit(0);
}
run().catch(console.error);
+15
View File
@@ -0,0 +1,15 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
async function run() {
const payload = await getPayload({ config: configPromise })
const forms = await payload.find({ collection: 'forms', depth: 0 })
console.log('Forms:', forms.docs.map(f => ({ id: f.id, title: f.title })))
const pages = await payload.find({ collection: 'pages', depth: 0 })
console.log('Pages:', pages.docs.map(p => ({ id: p.id, slug: p.slug })))
process.exit(0)
}
run()
+12
View File
@@ -0,0 +1,12 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
async function run() {
const payload = await getPayload({ config: configPromise })
const pages = await payload.find({ collection: 'pages', depth: 0, locale: 'de' })
console.log('DE Pages:', pages.docs.map(p => ({ id: p.id, slug: p.slug })))
process.exit(0)
}
run()
View File
+369
View File
@@ -0,0 +1,369 @@
[⣷] Pulling schema from database...
[✓] Pulling schema from database...
[15:46:43] INFO: [ipal] Plugin initialized.
=== LAYOUT PL ===
[0] pageHeader
[1] contactSection
=== LAYOUT DE ===
[0] pageHeader
[1] contactSection
=== FULL DUMP DE ===
[
{
"id": "6aab9fe4fae4c5f3750e199d",
"title": "Kontakt",
"subtitle": "Haben Sie Fragen zu Ihrer Steuererklärung in Deutschland? Kontaktieren Sie uns — wir helfen Ihnen gerne.",
"image": null,
"blockName": null,
"blockType": "pageHeader"
},
{
"id": "6aab9fe4fae4c5f3750e199e",
"address": "Vom Kunden zu bestätigen",
"phone": "+49 123 456 789",
"email": "[email protected]",
"hours": "Mo-Fr: 9:00 - 17:00\nNach vorheriger Terminvereinbarung",
"form": {
"id": 1,
"title": "Kontaktformular",
"fields": [
{
"id": "6aab2fdb7d8e4b60abd49527",
"name": "name",
"width": null,
"required": true,
"blockName": null,
"blockType": "text",
"label": "Vor- und Nachname",
"defaultValue": null
},
{
"id": "6aab2fdb7d8e4b60abd49528",
"name": "email",
"width": null,
"required": true,
"blockName": null,
"blockType": "email",
"label": "E-Mail Adresse"
},
{
"id": "6aab2fdb7d8e4b60abd4952a",
"name": "service",
"width": null,
"placeholder": null,
"required": null,
"blockName": null,
"options": [
{
"id": "6aab9bd69f369ef03e6bf859",
"value": "erstellung-der-einkommensteuererklarung",
"label": "Erstellung der Einkommensteuererklärung"
},
{
"id": "6aab9bd69f369ef03e6bf85a",
"value": "steuervorausberechnung",
"label": "Steuervorausberechnung"
},
{
"id": "6aab9bd69f369ef03e6bf85b",
"value": "prufung-des-steuerbescheids",
"label": "Prüfung des Steuerbescheids"
},
{
"id": "6aab9bd69f369ef03e6bf85c",
"value": "einspruchsverfahren",
"label": "Einspruchsverfahren"
},
{
"id": "6aab9bd69f369ef03e6bf85d",
"value": "ganzjahrige-steuerliche-betreuung",
"label": "Ganzjährige steuerliche Betreuung"
},
{
"id": "6aab9bd69f369ef03e6bf85e",
"value": "wahl-der-optimalen-steuerklasse",
"label": "Wahl der optimalen Steuerklasse"
},
{
"id": "6aab9bd69f369ef03e6bf85f",
"value": "freibetrage-kindergeld-and-fahrtkosten",
"label": "Freibeträge, Kindergeld & Fahrtkosten"
}
],
"blockType": "select",
"label": "Dienstleistung",
"defaultValue": null
},
{
"id": "6aab2fdb7d8e4b60abd4952b",
"name": "message",
"width": null,
"required": true,
"blockName": null,
"blockType": "textarea",
"label": "Nachricht",
"defaultValue": null
},
{
"id": "6aab2fdb7d8e4b60abd4952c",
"name": "consent",
"width": null,
"required": true,
"defaultValue": null,
"blockName": null,
"blockType": "checkbox",
"label": "Ich stimme der Datenverarbeitung zu"
}
],
"submitButtonLabel": null,
"confirmationType": "message",
"confirmationMessage": {
"root": {
"type": "root",
"children": [
{
"type": "paragraph",
"children": [
{
"type": "text",
"text": "Vielen Dank für Ihre Nachricht!",
"version": 1
}
],
"direction": "ltr",
"format": "",
"indent": 0,
"version": 1
}
],
"direction": "ltr",
"format": "",
"indent": 0,
"version": 1
}
},
"redirect": {
"type": "reference",
"url": null
},
"emails": [
{
"id": "6aab8cd6f63e48e4b270376c",
"emailTo": "[email protected]",
"cc": null,
"bcc": null,
"replyTo": null,
"emailFrom": "[email protected]",
"subject": "Neue Anfrage über die Website",
"message": {
"root": {
"type": "root",
"children": [
{
"type": "paragraph",
"children": [
{
"type": "text",
"text": "Jemand hat eine Anfrage über die Website gesendet.",
"version": 1
}
],
"direction": "ltr",
"format": "",
"indent": 0,
"version": 1
}
],
"direction": "ltr",
"format": "",
"indent": 0,
"version": 1
}
}
}
],
"updatedAt": "2026-09-17T07:50:46.201Z",
"createdAt": "2026-09-17T00:10:03.982Z"
},
"blockName": null,
"blockType": "contactSection"
}
]
=== FULL DUMP PL ===
[
{
"id": "6aab9f5e52cb70f317bb9a7b",
"title": "Kontakt",
"subtitle": "Masz pytania dotyczące rozliczenia podatkowego w Niemczech? Skontaktuj się z nami — chętnie pomożemy.",
"image": null,
"blockName": null,
"blockType": "pageHeader"
},
{
"id": "6aab9f5e52cb70f317bb9a7c",
"address": "DO POTWIERDZENIA PRZEZ KLIENTA",
"phone": "+49 123 456 789",
"email": "[email protected]",
"hours": "Pon-Pt: 9:00 - 17:00\nPo wcześniejszym umówieniu",
"form": {
"id": 1,
"title": "Kontaktformular",
"fields": [
{
"id": "6aab2fdb7d8e4b60abd49527",
"name": "name",
"width": null,
"required": true,
"blockName": null,
"blockType": "text",
"label": "Imię i nazwisko",
"defaultValue": null
},
{
"id": "6aab2fdb7d8e4b60abd49528",
"name": "email",
"width": null,
"required": true,
"blockName": null,
"blockType": "email",
"label": "Adres e-mail"
},
{
"id": "6aab2fdb7d8e4b60abd4952a",
"name": "service",
"width": null,
"placeholder": null,
"required": null,
"blockName": null,
"options": [
{
"id": "6aab9bd69f369ef03e6bf859",
"value": "erstellung-der-einkommensteuererklarung"
},
{
"id": "6aab9bd69f369ef03e6bf85a",
"value": "steuervorausberechnung"
},
{
"id": "6aab9bd69f369ef03e6bf85b",
"value": "prufung-des-steuerbescheids"
},
{
"id": "6aab9bd69f369ef03e6bf85c",
"value": "einspruchsverfahren"
},
{
"id": "6aab9bd69f369ef03e6bf85d",
"value": "ganzjahrige-steuerliche-betreuung"
},
{
"id": "6aab9bd69f369ef03e6bf85e",
"value": "wahl-der-optimalen-steuerklasse"
},
{
"id": "6aab9bd69f369ef03e6bf85f",
"value": "freibetrage-kindergeld-and-fahrtkosten"
}
],
"blockType": "select",
"label": "Usługa",
"defaultValue": null
},
{
"id": "6aab2fdb7d8e4b60abd4952b",
"name": "message",
"width": null,
"required": true,
"blockName": null,
"blockType": "textarea",
"label": "Wiadomość",
"defaultValue": null
},
{
"id": "6aab2fdb7d8e4b60abd4952c",
"name": "consent",
"width": null,
"required": true,
"defaultValue": null,
"blockName": null,
"blockType": "checkbox",
"label": "Wyrażam zgodę na przetwarzanie danych"
}
],
"submitButtonLabel": null,
"confirmationType": "message",
"confirmationMessage": {
"root": {
"type": "root",
"children": [
{
"type": "paragraph",
"children": [
{
"type": "text",
"text": "Dziękujemy za wiadomość!",
"version": 1
}
],
"direction": "ltr",
"format": "",
"indent": 0,
"version": 1
}
],
"direction": "ltr",
"format": "",
"indent": 0,
"version": 1
}
},
"redirect": {
"type": "reference",
"url": null
},
"emails": [
{
"id": "6aab8cd6f63e48e4b270376c",
"emailTo": "[email protected]",
"cc": null,
"bcc": null,
"replyTo": null,
"emailFrom": "[email protected]",
"subject": "Nowe zapytanie ze strony",
"message": {
"root": {
"type": "root",
"children": [
{
"type": "paragraph",
"children": [
{
"type": "text",
"text": "Ktoś wysłał zapytanie ze strony.",
"version": 1
}
],
"direction": "ltr",
"format": "",
"indent": 0,
"version": 1
}
],
"direction": "ltr",
"format": "",
"indent": 0,
"version": 1
}
}
}
],
"updatedAt": "2026-09-17T07:50:46.201Z",
"createdAt": "2026-09-17T00:10:03.982Z"
},
"blockName": null,
"blockType": "contactSection"
}
]
+111
View File
@@ -0,0 +1,111 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
import { toSlug } from '@intecion/ipal-kit'
async function run() {
console.log('Rozpoczynam migrację kotwic usług...')
const payload = await getPayload({ config: configPromise })
const locales = ['pl', 'de'] as const
const urls: Record<string, string> = {
pl: '/uslugi',
de: '/leistungen'
}
let updatedPagesCount = 0
for (const locale of locales) {
const pages = await payload.find({
collection: 'pages',
locale,
limit: 100
})
for (const page of pages.docs) {
if (!page.layout) continue
let changed = false
const titleToAnchor: Record<string, string> = {}
// Pass 1: Generate anchors for servicesDetailed
const newLayout = page.layout.map((block: any) => {
if (block.blockType === 'servicesDetailed') {
if (block.services && Array.isArray(block.services)) {
block.services = block.services.map((service: any) => {
if (!service.anchor) {
service.anchor = toSlug(service.title)
changed = true
}
titleToAnchor[service.title] = service.anchor
return service
})
}
}
return block
})
// We need to fetch the ServicesDetailed from the services page if we are on the homepage
// Actually, it's easier to just assume they have similar titles, or we can just fetch the anchors from the services page
// Let's first fetch the services page to build a global map for this locale
const servicesPage = pages.docs.find(p => p.slug === 'uslugi' || p.slug === 'leistungen')
let globalTitleToAnchor: Record<string, string> = {}
if (servicesPage && servicesPage.layout) {
servicesPage.layout.forEach((b: any) => {
if (b.blockType === 'servicesDetailed' && b.services) {
b.services.forEach((s: any) => {
const anchor = s.anchor || toSlug(s.title)
globalTitleToAnchor[s.title] = anchor
// Also map some generic prefixes if needed, but exact match is best
})
}
})
}
// Pass 2: Update servicesList on any page
const finalLayout = newLayout.map((block: any) => {
if (block.blockType === 'servicesList') {
if (block.services && Array.isArray(block.services)) {
block.services = block.services.map((service: any) => {
// Try to find matching anchor
let anchor = globalTitleToAnchor[service.title]
if (!anchor) {
// Fuzzy match
const foundKey = Object.keys(globalTitleToAnchor).find(k =>
k.toLowerCase().includes(service.title.toLowerCase()) ||
service.title.toLowerCase().includes(k.toLowerCase())
)
if (foundKey) anchor = globalTitleToAnchor[foundKey]
}
const newUrl = anchor ? `${urls[locale]}#${anchor}` : urls[locale]
if (service.linkUrl !== newUrl) {
service.linkUrl = newUrl
changed = true
}
return service
})
}
}
return block
})
if (changed) {
console.log(`Aktualizacja strony ${locale.toUpperCase()}: ${page.title} (ID: ${page.id})`)
await payload.update({
collection: 'pages',
id: page.id,
locale,
data: { layout: finalLayout }
})
updatedPagesCount++
}
}
}
console.log(`Zaktualizowano ${updatedPagesCount} stron.`)
process.exit(0)
}
run()
+93
View File
@@ -0,0 +1,93 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
import fs from 'fs'
import path from 'path'
async function run() {
const payload = await getPayload({ config: configPromise })
const stepsPL = [
{
title: "Napisz na WhatsApp",
description: "Skontaktuj się z nami bezpośrednio przez WhatsApp lub formularz poniżej, aby rozpocząć i ustalić szczegóły Twojej sytuacji podatkowej."
},
{
title: "Przekaż dokumenty",
description: "Prześlij zaświadczenia o dochodach (Lohnsteuerbescheinigung, EU/EWR), podatkowy numer identyfikacyjny (IdNr) lub PESEL oraz ewentualne potwierdzenia kosztów."
},
{
title: "Odbierz gotowe rozliczenie do podpisu",
description: "Przygotowujemy kompletną deklarację, wyliczamy przewidywany zwrot z urzędu skarbowego i przesyłamy gotowy dokument do Twojej akceptacji i podpisu."
}
];
const finalStepsDE = [
{
title: "WhatsApp kontaktieren",
description: "Schreiben Sie uns direkt über WhatsApp oder das untenstehende Formular, um Ihre Anfrage und steuerliche Situation zu besprechen."
},
{
title: "Unterlagen einreichen",
description: "Übermitteln Sie Ihre Lohnsteuerbescheinigung, Steuer-Identifikationsnummer sowie relevante Belege für Werbungskosten und Freibeträge."
},
{
title: "Fertige Erklärung zur Unterschrift",
description: "Wir erstellen Ihre vollständige Erklärung, berechnen die voraussichtliche Erstattung und übermitteln Ihnen die Unterlagen zur Freigabe."
}
];
// 1. Update PL
const jsuPL = await payload.find({ collection: 'pages', locale: 'pl', where: { slug: { equals: 'jak-sie-umowic' } } })
if (jsuPL.docs.length > 0) {
const doc = jsuPL.docs[0]
const layout = doc.layout || []
let updated = false
for (let i = 0; i < layout.length; i++) {
if (layout[i].blockType === 'process') {
layout[i].steps = stepsPL.map((s: any) => ({
title: s.title,
description: s.description
}))
updated = true
}
}
if (updated) {
await payload.update({
collection: 'pages',
id: doc.id,
locale: 'pl',
data: { layout }
})
console.log("Zaktualizowano kroki dla PL")
}
}
// 2. Update DE
const jsuDE = await payload.find({ collection: 'pages', locale: 'de', where: { slug: { equals: 'terminvereinbarung' } } })
if (jsuDE.docs.length > 0) {
const doc = jsuDE.docs[0]
const layout = doc.layout || []
let updated = false
for (let i = 0; i < layout.length; i++) {
if (layout[i].blockType === 'process') {
layout[i].steps = finalStepsDE.map((s: any) => ({
title: s.title,
description: s.description
}))
updated = true
}
}
if (updated) {
await payload.update({
collection: 'pages',
id: doc.id,
locale: 'de',
data: { layout }
})
console.log("Zaktualizowano kroki dla DE")
}
}
process.exit(0)
}
run().catch(console.error)
-86
View File
@@ -1,86 +0,0 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
import type { Page } from '../src/payload-types'
type PageHeaderBlock = Extract<NonNullable<Page['layout']>[0], { blockType: 'pageHeader' }>
type ContactInfoBlock = Extract<NonNullable<Page['layout']>[0], { blockType: 'contactInfo' }>
const plHeader: PageHeaderBlock = {
blockType: 'pageHeader',
title: 'Kontakt',
subtitle: 'Masz pytania dotyczące rozliczenia podatkowego w Niemczech? Skontaktuj się ze mną — chętnie pomogę.',
}
const deHeader: PageHeaderBlock = {
blockType: 'pageHeader',
title: 'Kontakt',
subtitle: 'Haben Sie Fragen zu Ihrer Steuererklärung in Deutschland? Kontaktieren Sie mich — ich helfe Ihnen gerne.',
}
const plContact: ContactInfoBlock = {
blockType: 'contactInfo',
address: 'DO POTWIERDZENIA PRZEZ KLIENTA',
phone: '+49 123 456 789',
email: '[email protected]',
hours: 'Pon-Pt: 9:00 - 17:00\nPo wcześniejszym umówieniu',
}
const deContact: ContactInfoBlock = {
blockType: 'contactInfo',
address: 'DO POTWIERDZENIA PRZEZ KLIENTA',
phone: '+49 123 456 789',
email: '[email protected]',
hours: 'Mo-Fr: 9:00 - 17:00\nNach vorheriger Terminvereinbarung',
}
async function run() {
console.log('Rozpoczynam odzyskiwanie strony Kontakt (ID 5)...')
const payload = await getPayload({ config: configPromise })
for (const locale of ['pl', 'de'] as const) {
try {
const page = await payload.findByID({
collection: 'pages',
id: 5,
locale: locale
})
const hasPageHeader = page.layout?.some(block => block.blockType === 'pageHeader')
const hasContactInfo = page.layout?.some(block => block.blockType === 'contactInfo')
if (hasPageHeader && hasContactInfo) {
console.log(`[${locale}] Strona Kontakt posiada już bloki pageHeader i contactInfo. Pomijam.`)
continue;
}
console.log(`[${locale}] Przywracam utracone bloki...`)
// Jeśli strona miała jakieś bloki, zachowaj je (z wyjątkiem ewentualnych starych/uszkodzonych wersji tych dwóch)
let newLayout = (page.layout ? page.layout.filter(b => b.blockType !== 'pageHeader' && b.blockType !== 'contactInfo') : []) as NonNullable<Page['layout']>;
// Dodaj nagłówek i info kontaktowe na górę
newLayout.unshift(
(locale === 'de' ? deHeader : plHeader) as unknown as NonNullable<Page['layout']>[0],
(locale === 'de' ? deContact : plContact) as unknown as NonNullable<Page['layout']>[0]
);
await payload.update({
collection: 'pages',
id: 5,
locale: locale,
data: {
layout: newLayout
}
})
console.log(`[${locale}] Zaktualizowano pomyślnie.`)
} catch (error) {
console.error(`[${locale}] Błąd podczas aktualizacji:`, error)
}
}
console.log('Zakończono.')
process.exit(0)
}
run()
+59
View File
@@ -0,0 +1,59 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
import fs from 'fs'
import path from 'path'
async function run() {
const payload = await getPayload({ config: configPromise })
const snapshotRaw = fs.readFileSync(path.join(process.cwd(), 'scripts', 'content-snapshot.json'), 'utf8')
const snapshot = JSON.parse(snapshotRaw)
for (const pageSnap of snapshot.pages) {
for (const locale of ['pl', 'de']) {
const pageData = pageSnap[locale]
if (!pageData) continue
// Fetch current page from DB
const pagesInDb = await payload.find({
collection: 'pages',
locale: locale as 'pl' | 'de',
where: { slug: { equals: pageData.slug } }
})
if (pagesInDb.docs.length > 0) {
const dbPage = pagesInDb.docs[0]
let layoutChanged = false
const newLayout = dbPage.layout?.map((dbBlock: any) => {
if (dbBlock.blockType === 'servicesDetailed') {
const snapBlock = pageData.layout.find((b: any) => b.blockType === 'servicesDetailed')
if (snapBlock && snapBlock.services) {
dbBlock.services = snapBlock.services.map((s:any) => {
return {
title: s.title,
description: s.description,
icon: s.icon
}
})
layoutChanged = true
}
}
return dbBlock
})
if (layoutChanged) {
await payload.update({
collection: 'pages',
id: dbPage.id,
locale: locale as 'pl' | 'de',
data: { layout: newLayout }
})
console.log(`Restored servicesDetailed for ${pageData.slug} (${locale})`)
}
}
}
}
process.exit(0)
}
run()
+92
View File
@@ -0,0 +1,92 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
async function run() {
const payload = await getPayload({ config: configPromise })
const translations = {
pl: {
title: 'Formularz kontaktowy',
confirmationMessage: 'Dziękujemy za wiadomość!',
emailSubject: 'Nowe zapytanie ze strony',
emailMessage: 'Ktoś wysłał zapytanie ze strony.',
fields: [
{ name: 'name', label: 'Imię i nazwisko' },
{ name: 'email', label: 'Adres e-mail' },
{ name: 'service', label: 'Usługa', options: [{ value: 'konsultacje', label: 'Konsultacje' }] },
{ name: 'message', label: 'Wiadomość' },
{ name: 'consent', label: 'Wyrażam zgodę na przetwarzanie danych' }
]
},
de: {
title: 'Kontaktformular',
confirmationMessage: 'Vielen Dank für Ihre Nachricht!',
emailSubject: 'Neue Anfrage über die Website',
emailMessage: 'Jemand hat eine Anfrage über die Website gesendet.',
fields: [
{ name: 'name', label: 'Vor- und Nachname' },
{ name: 'email', label: 'E-Mail Adresse' },
{ name: 'service', label: 'Dienstleistung', options: [{ value: 'konsultacje', label: 'Beratung' }] },
{ name: 'message', label: 'Nachricht' },
{ name: 'consent', label: 'Ich stimme der Datenverarbeitung zu' }
]
}
}
for (const locale of ['pl', 'de'] as const) {
const t = translations[locale]
// First, fetch the form in this locale to get its fields layout
const forms = await payload.find({ collection: 'forms', locale, limit: 1 })
if (forms.docs.length > 0) {
const form = forms.docs[0]
const updatedFields = form.fields?.map((f:any) => {
const tf = t.fields.find(tf => tf.name === f.name)
if (tf) {
f.label = tf.label
if (f.blockType === 'select' && f.options) {
f.options = f.options.map((o:any) => {
const to = tf.options?.find(to => to.value === o.value)
if (to) o.label = to.label
return o
})
}
}
return f
})
const updatedEmails = form.emails?.map((e:any) => {
e.subject = t.emailSubject
e.message = {
root: {
type: 'root',
children: [{ type: 'paragraph', children: [{ type: 'text', text: t.emailMessage, version: 1 }], direction: 'ltr', format: '', indent: 0, version: 1 }],
direction: 'ltr', format: '', indent: 0, version: 1
}
}
return e
})
await payload.update({
collection: 'forms',
id: form.id,
locale,
data: {
title: t.title,
fields: updatedFields,
emails: updatedEmails,
confirmationMessage: {
root: {
type: 'root',
children: [{ type: 'paragraph', children: [{ type: 'text', text: t.confirmationMessage, version: 1 }], direction: 'ltr', format: '', indent: 0, version: 1 }],
direction: 'ltr', format: '', indent: 0, version: 1
}
}
}
})
console.log(`Updated form translations for locale: ${locale}`)
}
}
process.exit(0)
}
run()
+16
View File
@@ -0,0 +1,16 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
async function run() {
const payload = await getPayload({ config: configPromise })
const forms = await payload.find({ collection: 'forms', locale: 'all' })
for (const form of forms.docs) {
console.log(`Form ID: ${form.id}`)
console.log(`Emails length: ${form.emails?.length || 0}`)
if (form.emails) {
form.emails.forEach(e => console.log(e))
}
}
process.exit(0)
}
run()
+70
View File
@@ -0,0 +1,70 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
async function run() {
const payload = await getPayload({ config: configPromise })
const mappings: Record<string, Record<string, string>> = {
pl: {
'Deklaracje podatkowe': 'sporzadzenie-deklaracji-podatkowej-einkommensteuererklarung',
'Kontrola decyzji': 'kontrola-decyzji-podatkowej-steuerbescheid',
'Ulgi i dopłaty': 'ulgi-doplaty-i-odliczenia-kindergeld-dojazdy-podwojne-gospodarstwo'
},
de: {
'Steuererklärungen': 'erstellung-der-einkommensteuererklarung',
'Steuerbescheidprüfung': 'prufung-des-steuerbescheids',
'Vergünstigungen und Zuschüsse': 'freibetrage-kindergeld-and-fahrtkosten'
}
}
const urls: Record<string, string> = {
pl: '/uslugi',
de: '/leistungen'
}
for (const locale of ['pl', 'de'] as const) {
const pages = await payload.find({
collection: 'pages',
locale,
where: { slug: { equals: 'home' } }
})
if (pages.docs.length > 0) {
const homePage = pages.docs[0]
if (homePage.layout) {
let changed = false
const newLayout = homePage.layout.map((block: any) => {
if (block.blockType === 'servicesList' && block.services) {
block.services = block.services.map((service: any) => {
const anchor = mappings[locale][service.title]
const newUrl = anchor ? `${urls[locale]}#${anchor}` : urls[locale]
if (service.linkUrl !== newUrl) {
service.linkUrl = newUrl
changed = true
}
return service
})
}
return block
})
if (changed) {
await payload.update({
collection: 'pages',
id: homePage.id,
locale,
data: { layout: newLayout }
})
}
console.log(`\nStrona ${locale.toUpperCase()} (Home) zaktualizowane linki:`)
newLayout.find((b:any) => b.blockType === 'servicesList')?.services?.forEach((s:any) => {
console.log(` - ${s.title} -> ${s.linkUrl}`)
})
}
}
}
process.exit(0)
}
run()
+31
View File
@@ -0,0 +1,31 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
async function run() {
const payload = await getPayload({ config: configPromise })
console.log("Submitting form with empty data using payload.create...")
try {
const result = await payload.create({
collection: 'form-submissions',
data: {
form: 1,
submissionData: [
{ field: 'name', value: '' },
{ field: 'email', value: '' },
{ field: 'message', value: '' },
{ field: 'consent', value: 'true' }
]
}
})
console.log("Submit form result ID:", result.id)
console.log("Submission data:", JSON.stringify(result.submissionData, null, 2))
} catch (err) {
console.error("Error submitting form:", err)
}
process.exit(0)
}
run().catch(console.error)
+9
View File
@@ -0,0 +1,9 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
async function run() {
const payload = await getPayload({ config: configPromise })
const pages = await payload.find({ collection: 'pages', locale: 'all', limit: 100 })
pages.docs.forEach(p => console.log(p.slug))
process.exit(0)
}
run()
+38
View File
@@ -0,0 +1,38 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
import { submitForm } from '@intecion/ipal-kit/server'
async function run() {
const payload = await getPayload({ config: configPromise })
console.log("Submitting form with empty data using submitForm action...")
try {
const result = await submitForm({
payload,
formId: "1",
data: {
name: "",
email: "",
message: "",
consent: true
}
})
console.log("Submit form result:", result)
// Now fetch the latest submission to see what was saved
const submissions = await payload.find({
collection: 'form-submissions',
sort: '-createdAt',
limit: 1
})
console.log("Latest submission data:", JSON.stringify(submissions.docs[0].submissionData, null, 2))
} catch (err) {
console.error("Error submitting form:", err)
}
process.exit(0)
}
run().catch(console.error)
+41
View File
@@ -0,0 +1,41 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
async function run() {
const payload = await getPayload({ config: configPromise })
await payload.update({
collection: 'forms',
id: 1,
data: {
emails: [
{
emailTo: '[email protected]',
emailFrom: '[email protected]',
subject: 'Nowe zapytanie ze strony',
message: {
root: {
type: 'root',
children: [
{
type: 'paragraph',
children: [{ type: 'text', text: 'Ktoś wysłał zapytanie ze strony.', version: 1 }],
direction: 'ltr',
format: '',
indent: 0,
version: 1
}
],
direction: 'ltr',
format: '',
indent: 0,
version: 1
}
}
}
]
}
})
process.exit(0)
}
run()
+22
View File
@@ -0,0 +1,22 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
async function run() {
const payload = await getPayload({ config: configPromise })
for (const locale of ['pl', 'de']) {
const slug = locale === 'pl' ? 'uslugi' : 'leistungen'
const pages = await payload.find({ collection: 'pages', locale: locale as any, where: { slug: { equals: slug } } })
if (pages.docs.length > 0) {
const page = pages.docs[0]
page.layout?.forEach((b:any) => {
if (b.blockType === 'servicesDetailed' && b.services) {
b.services.forEach((s:any) => console.log(`${locale.toUpperCase()}: "${s.title}" -> ${s.anchor}`))
}
})
}
}
process.exit(0)
}
run()
+26
View File
@@ -0,0 +1,26 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
async function run() {
const payload = await getPayload({ config: configPromise })
for (const locale of ['pl', 'de']) {
const slug = locale === 'pl' ? 'uslugi' : 'leistungen'
const pages = await payload.find({ collection: 'pages', locale: locale as any, where: { slug: { equals: slug } } })
if (pages.docs.length > 0) {
const page = pages.docs[0]
console.log(`\n=== INTEGRALNOŚĆ STRONY: ${slug} (${locale}) ===`)
page.layout?.forEach((b:any) => {
console.log(`Block: ${b.blockType}`)
if (b.blockType === 'pageHeader') console.log(` - title: ${b.title}`)
if (b.blockType === 'eligibility') console.log(` - eligibleTitle: ${b.eligibleTitle}`)
if (b.blockType === 'finalCta') console.log(` - title: ${b.title}`)
})
} else {
console.log(`Nie znaleziono strony ${slug} w języku ${locale}`)
}
}
process.exit(0)
}
run()
+61
View File
@@ -0,0 +1,61 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
async function run() {
const payload = await getPayload({ config: configPromise })
console.log("Symulacja odebrania FormData(puste pola) na backendzie (zgodnie z logiką validateSubmission)...")
const incomingData: Record<string, any> = {
name: "",
email: "",
message: "",
consent: true // Pomijamy consent
}
console.log("Dane wejściowe (z FormData):", incomingData)
const form: any = await payload.findByID({ id: 1, collection: 'forms', depth: 0 })
const fields = form.fields || []
let validationResult: any = { ok: true, cleaned: {} }
for (const field of fields) {
if (field.blockType === 'message') continue;
const value = incomingData[field.name]
const isBlank = value == null || (typeof value === 'string' && value.trim() === '') || value === false
if (field.required && isBlank) {
validationResult = { field: field.name, kind: 'required', ok: false, reason: 'invalid' }
break;
}
if (value !== undefined) {
validationResult.cleaned[field.name] = value
}
}
console.log("\\n=== Wynik validateSubmission ===")
console.log(JSON.stringify(validationResult, null, 2))
if (validationResult.ok) {
console.log("\\nTworzenie wpisu w bazie przez payload.create (symulacja zapisu)...")
const submission = await payload.create({
collection: 'form-submissions',
data: {
form: 1,
submissionData: Object.entries(validationResult.cleaned).map(([field, value]) => ({
field,
value: value == null ? '' : String(value),
})) as any,
}
})
console.log("\\nZapisano w bazie:", JSON.stringify(submission.submissionData, null, 2))
} else {
console.log("\\nFormularz odrzucony na poziomie API backendowego (validateSubmission) - rekord NIE trafił do bazy, użytkownik dostaje błąd o braku pola.")
}
process.exit(0)
}
run().catch(console.error)
+124
View File
@@ -0,0 +1,124 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
async function run() {
const payload = await getPayload({ config: configPromise })
const servicesData: Record<string, { label: string, value: string }[]> = { pl: [], de: [] }
for (const locale of ['pl', 'de']) {
const pages = await payload.find({
collection: 'pages',
locale: locale as 'pl' | 'de',
where: { slug: { equals: locale === 'de' ? 'leistungen' : 'uslugi' } }
})
if (pages.docs.length > 0) {
const dbPage = pages.docs[0]
const servicesBlock = dbPage.layout?.find((b: any) => b.blockType === 'servicesDetailed') as any
if (servicesBlock && servicesBlock.services) {
servicesData[locale] = servicesBlock.services.map((s: any) => ({
label: s.title,
value: s.anchor || s.title.toLowerCase().replace(/[^a-z0-9]+/g, '-')
}))
}
}
}
const baseValues = servicesData['pl'].map(s => s.value);
// First pass: PL. This will create the options (or update them).
let form = await payload.findByID({
collection: 'forms',
id: 1,
locale: 'pl',
})
let updatedFieldsPL = form.fields?.map((f: any) => {
if (f.name === 'name') {
f.defaultValue = 'Jan Kowalski'
f.label = 'Imię i nazwisko'
}
if (f.name === 'email') {
f.defaultValue = '[email protected]'
f.label = 'Adres e-mail'
}
if (f.name === 'message') {
f.defaultValue = 'Opisz krótko swoją sytuację...'
f.label = 'Wiadomość'
}
if (f.name === 'consent') {
f.label = 'Wyrażam zgodę na przetwarzanie danych'
}
if (f.name === 'service' && f.blockType === 'select') {
f.label = 'Usługa'
f.options = servicesData['pl'].map((s: any, idx: number) => {
// preserve existing id if we have the exact same amount of options
const existingOption = f.options?.[idx];
return {
id: existingOption ? existingOption.id : undefined,
label: s.label,
value: baseValues[idx]
}
})
}
return f
})
await payload.update({
collection: 'forms',
id: 1,
locale: 'pl',
data: { fields: updatedFieldsPL }
})
console.log(`Updated form 1 for locale pl`)
// Refetch the form to get the assigned IDs of the options!
form = await payload.findByID({
collection: 'forms',
id: 1,
locale: 'all', // Get all locales to see the IDs
})
let updatedFieldsDE = form.fields?.map((f: any) => {
if (f.name === 'name') {
f.defaultValue = 'Max Mustermann'
f.label = 'Vor- und Nachname'
}
if (f.name === 'email') {
f.defaultValue = '[email protected]'
f.label = 'E-Mail Adresse'
}
if (f.name === 'message') {
f.defaultValue = 'Beschreiben Sie kurz Ihre Situation...'
f.label = 'Nachricht'
}
if (f.name === 'consent') {
f.label = 'Ich stimme der Datenverarbeitung zu'
}
if (f.name === 'service' && f.blockType === 'select') {
f.label = 'Dienstleistung'
f.options = servicesData['de'].map((s: any, idx: number) => {
// VERY IMPORTANT: Use the ID assigned during the PL update
const optionId = f.options?.[idx]?.id;
return {
id: optionId,
label: s.label,
value: baseValues[idx]
}
})
}
return f
})
await payload.update({
collection: 'forms',
id: 1,
locale: 'de',
data: { fields: updatedFieldsDE }
})
console.log(`Updated form 1 for locale de`)
process.exit(0)
}
run().catch(console.error)
+64
View File
@@ -0,0 +1,64 @@
[⣷] Pulling schema from database...
[✓] Pulling schema from database...
[15:53:24] INFO: [ipal] Plugin initialized.
Services: {
pl: [
{
label: 'Sporządzenie deklaracji podatkowej (Einkommensteuererklärung)',
value: 'sporzadzenie-deklaracji-podatkowej-einkommensteuererklarung'
},
{
label: 'Wstępne obliczenie przewidywanego zwrotu',
value: 'wstepne-obliczenie-przewidywanego-zwrotu'
},
{
label: 'Kontrola decyzji podatkowej (Steuerbescheid)',
value: 'kontrola-decyzji-podatkowej-steuerbescheid'
},
{
label: 'Składanie odwołań (Einspruch)',
value: 'skladanie-odwolan-einspruch'
},
{
label: 'Całoroczne doradztwo podatkowe',
value: 'caloroczne-doradztwo-podatkowe'
},
{
label: 'Doradztwo przy wyborze klasy podatkowej (Steuerklasse)',
value: 'doradztwo-przy-wyborze-klasy-podatkowej-steuerklasse'
},
{
label: 'Ulgi, dopłaty i odliczenia (Kindergeld, dojazdy, podwójne gospodarstwo)',
value: 'ulgi-doplaty-i-odliczenia-kindergeld-dojazdy-podwojne-gospodarstwo'
}
],
de: [
{
label: 'Erstellung der Einkommensteuererklärung',
value: 'erstellung-der-einkommensteuererklarung'
},
{
label: 'Steuervorausberechnung',
value: 'steuervorausberechnung'
},
{
label: 'Prüfung des Steuerbescheids',
value: 'prufung-des-steuerbescheids'
},
{ label: 'Einspruchsverfahren', value: 'einspruchsverfahren' },
{
label: 'Ganzjährige steuerliche Betreuung',
value: 'ganzjahrige-steuerliche-betreuung'
},
{
label: 'Wahl der optimalen Steuerklasse',
value: 'wahl-der-optimalen-steuerklasse'
},
{
label: 'Freibeträge, Kindergeld & Fahrtkosten',
value: 'freibetrage-kindergeld-and-fahrtkosten'
}
]
}
Updated form 1 for locale pl
Updated form 1 for locale de
+13
View File
@@ -0,0 +1,13 @@
[⣷] Pulling schema from database...
[✓] Pulling schema from database...
[16:07:07] INFO: [ipal] Plugin initialized.
Symulacja odebrania FormData(puste pola) na backendzie (zgodnie z logiką validateSubmission)...
Dane wejściowe (z FormData): { name: '', email: '', message: '', consent: true }
\n=== Wynik validateSubmission ===
{
"field": "name",
"kind": "required",
"ok": false,
"reason": "invalid"
}
\nFormularz odrzucony na poziomie API backendowego (validateSubmission) - rekord NIE trafił do bazy, użytkownik dostaje błąd o braku pola.
+30
View File
@@ -0,0 +1,30 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
async function run() {
const payload = await getPayload({ config: configPromise })
const headerDE = await payload.findGlobal({ slug: 'header', locale: 'de' });
const footerDE = await payload.findGlobal({ slug: 'footer', locale: 'de' });
console.log('--- HEADER NAV DE ---');
headerDE.navigation?.forEach((nav: any) => {
if (nav.page && nav.page.slug === 'kontakt') {
const page = nav.page;
console.log('Header Kontakt Page Subtitle:', page.layout[0].subtitle);
console.log('Header Kontakt Page Address:', page.layout[1].address);
}
});
console.log('--- FOOTER NAV DE ---');
footerDE.navigation?.forEach((nav: any) => {
if (nav.page && nav.page.slug === 'kontakt') {
const page = nav.page;
console.log('Footer Kontakt Page Subtitle:', page.layout[0].subtitle);
console.log('Footer Kontakt Page Address:', page.layout[1].address);
}
});
process.exit(0);
}
run()
+6 -17
View File
@@ -4,24 +4,13 @@ import configPromise from '../src/payload.config'
async function run() { async function run() {
const payload = await getPayload({ config: configPromise }) const payload = await getPayload({ config: configPromise })
const plPage = await payload.findByID({ const formConf = payload.collections['forms'].config;
collection: 'pages', const titleField = formConf.fields.find(f => f.name === 'title');
id: 5, console.log('Form title is localized:', titleField?.localized);
locale: 'pl'
})
const dePage = await payload.findByID({ const kontaktALL = await payload.find({ collection: 'pages', locale: 'all', where: { slug: { equals: 'kontakt' } } });
collection: 'pages', console.log(JSON.stringify(kontaktALL.docs[0], null, 2));
id: 5,
locale: 'de'
})
console.log("=== KONTAKT PL LAYOUT ===") process.exit(0);
console.log(JSON.stringify(plPage.layout, null, 2))
console.log("=== KONTAKT DE LAYOUT ===")
console.log(JSON.stringify(dePage.layout, null, 2))
process.exit(0)
} }
run() run()
+10
View File
@@ -0,0 +1,10 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
async function run() {
const payload = await getPayload({ config: configPromise })
const k = await payload.find({ collection: 'pages', locale: 'de', where: { slug: { equals: 'kontakt' } } });
console.log(JSON.stringify(k.docs[0].layout, null, 2));
process.exit(0);
}
run()
+27
View File
@@ -0,0 +1,27 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
async function run() {
const payload = await getPayload({ config: configPromise })
console.log('--- VERIFICATION ---');
const k = await payload.find({ collection: 'pages', locale: 'de', where: { slug: { equals: 'kontakt' } } });
console.log('Kontakt DE layout[0].subtitle:', k.docs[0].layout[0].subtitle);
console.log('Kontakt DE layout[1].address:', k.docs[0].layout[1].address);
console.log('Kontakt DE layout[1].hours:', k.docs[0].layout[1].hours);
const j = await payload.find({ collection: 'pages', locale: 'de', where: { slug: { equals: 'terminvereinbarung' } } });
if (j.docs.length > 0) {
console.log('JSU DE layout[2].address:', j.docs[0].layout[2].address);
} else {
console.log('JSU DE not found with slug terminvereinbarung');
}
const c = await payload.findGlobal({ slug: 'company', locale: 'de' });
console.log('Company DE address:', c.address);
console.log('Company DE hours:', c.hours);
process.exit(0);
}
run()
+1 -1
View File
@@ -9,7 +9,7 @@ import configPromise from '@payload-config'
import type { Config, Page } from '../../../payload-types' import type { Config, Page } from '../../../payload-types'
import { getGlobal } from '../../../lib/content' import { getGlobal } from '../../../lib/content'
import '../../globals.css' import '../../globals.css'
import { ConsentProvider, CookieBanner, CookieButton } from '@intecion/ipal-kit/client' import { ConsentProvider, CookieBanner, CookieButton } from '../../../components/Consent'
import { FloatingWhatsApp } from '../../../components/FloatingWhatsApp' import { FloatingWhatsApp } from '../../../components/FloatingWhatsApp'
import { getConsentTexts } from '@intecion/ipal-kit' import { getConsentTexts } from '@intecion/ipal-kit'
import { i18n } from '../../../i18n.config' import { i18n } from '../../../i18n.config'
+1 -1
View File
@@ -25,7 +25,7 @@ export const ContactInfoBlock: React.FC<Props> = ({
whileInView={{ opacity: 1, y: 0 }} whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-50px" }} viewport={{ once: true, margin: "-50px" }}
transition={{ duration: 0.6, ease: [0.16, 1, 0.3, 1] }} transition={{ duration: 0.6, ease: [0.16, 1, 0.3, 1] }}
className="w-full max-w-4xl bg-card border border-border p-8 md:p-16 relative overflow-hidden" className="w-full max-w-4xl mx-auto bg-card border border-border p-8 md:p-16 relative overflow-hidden"
style={{ style={{
boxShadow: '0 10px 40px -10px rgba(0,0,0,0.08), 0 1px 3px rgba(0,0,0,0.03)' boxShadow: '0 10px 40px -10px rgba(0,0,0,0.08), 0 1px 3px rgba(0,0,0,0.03)'
}} }}
+127
View File
@@ -0,0 +1,127 @@
'use client'
import React from 'react'
import { usePathname } from 'next/navigation'
import { MapPin, Phone, Mail, Clock } from 'lucide-react'
import { motion } from 'framer-motion'
export const ContactSectionClient: React.FC<any> = ({
address,
phone,
email,
hours,
formNode,
}) => {
const pathname = usePathname()
const isDe = pathname?.split('/').filter(Boolean)[0] === 'de'
return (
<section className="py-16 md:py-24 bg-background">
<div className="container-premium">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12 lg:gap-16">
{/* Lewa kolumna: Informacje kontaktowe */}
<motion.div
initial={{ opacity: 0, x: -16 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true, margin: "-50px" }}
transition={{ duration: 0.6, ease: [0.16, 1, 0.3, 1] }}
className="flex flex-col"
>
<h2 className="font-heading text-3xl md:text-4xl font-extrabold text-foreground mb-8 text-balance">
{isDe ? 'Kontaktinformationen' : 'Dane kontaktowe'}
</h2>
<div className="bg-card border border-border p-8 relative overflow-hidden flex-grow"
style={{ boxShadow: '0 10px 40px -10px rgba(0,0,0,0.08), 0 1px 3px rgba(0,0,0,0.03)' }}
>
<div className="absolute top-0 right-0 w-12 h-12 bg-background border-l border-b border-border shadow-sm transform translate-x-6 -translate-y-6 rotate-45 pointer-events-none" />
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-1 gap-10">
{address && (
<div className="flex flex-col gap-3">
<div className="flex items-center gap-3 text-accent border-b border-border/50 pb-3">
<MapPin size={24} strokeWidth={2.5} />
<h3 className="font-heading font-bold text-lg text-foreground tracking-wide">
{isDe ? 'Adresse' : 'Adres'}
</h3>
</div>
<p className="text-foreground/80 leading-relaxed font-medium whitespace-pre-line text-base">
{address}
</p>
</div>
)}
{phone && (
<div className="flex flex-col gap-3">
<div className="flex items-center gap-3 text-accent border-b border-border/50 pb-3">
<Phone size={24} strokeWidth={2.5} />
<h3 className="font-heading font-bold text-lg text-foreground tracking-wide">
{isDe ? 'Telefon' : 'Telefon'}
</h3>
</div>
<a
href={`tel:${phone.replace(/\s+/g, '')}`}
className="text-foreground/80 leading-relaxed font-medium text-base hover:text-accent transition-colors"
>
{phone}
</a>
</div>
)}
{email && (
<div className="flex flex-col gap-3">
<div className="flex items-center gap-3 text-accent border-b border-border/50 pb-3">
<Mail size={24} strokeWidth={2.5} />
<h3 className="font-heading font-bold text-lg text-foreground tracking-wide">
{isDe ? 'E-Mail' : 'E-mail'}
</h3>
</div>
<a
href={`mailto:${email}`}
className="text-foreground/80 leading-relaxed font-medium text-base hover:text-accent transition-colors"
>
{email}
</a>
</div>
)}
{hours && (
<div className="flex flex-col gap-3">
<div className="flex items-center gap-3 text-accent border-b border-border/50 pb-3">
<Clock size={24} strokeWidth={2.5} />
<h3 className="font-heading font-bold text-lg text-foreground tracking-wide">
{isDe ? 'Öffnungszeiten' : 'Godziny pracy'}
</h3>
</div>
<p className="text-foreground/80 leading-relaxed font-medium whitespace-pre-line text-base">
{hours}
</p>
</div>
)}
</div>
</div>
</motion.div>
{/* Prawa kolumna: Formularz kontaktowy */}
<motion.div
initial={{ opacity: 0, x: 16 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true, margin: "-50px" }}
transition={{ duration: 0.6, ease: [0.16, 1, 0.3, 1], delay: 0.1 }}
className="flex flex-col"
>
<h2 className="font-heading text-3xl md:text-4xl font-extrabold text-foreground mb-8 text-balance">
{isDe ? 'Schreiben Sie uns' : 'Napisz do nas'}
</h2>
<div className="bg-card border border-border p-8 relative overflow-hidden flex-grow"
style={{ boxShadow: '0 10px 40px -10px rgba(0,0,0,0.08), 0 1px 3px rgba(0,0,0,0.03)' }}
>
{formNode}
</div>
</motion.div>
</div>
</div>
</section>
)
}
+23
View File
@@ -0,0 +1,23 @@
import React from 'react'
import type { Page } from '../../payload-types'
import { ContactSectionClient } from './Client'
import { FormRenderer } from '../../components/FormRenderer'
type Props = Extract<NonNullable<Page['layout']>[0], { blockType: 'contactSection' }>
export const ContactSectionBlock: React.FC<Props> = (props) => {
const { form } = props
return (
<ContactSectionClient
{...props}
formNode={
typeof form === 'object' && form !== null ? (
<FormRenderer form={form} />
) : (
<p className="text-foreground/60">Formularz nie został podpięty.</p>
)
}
/>
)
}
+43
View File
@@ -0,0 +1,43 @@
import type { Block } from 'payload'
export const ContactSection: Block = {
slug: 'contactSection',
fields: [
{
name: 'address',
type: 'textarea',
localized: true,
admin: {
description: 'Użyj Enter by przejść do nowej linii',
},
},
{
name: 'phone',
type: 'text',
localized: true,
},
{
name: 'email',
type: 'text',
localized: true,
},
{
name: 'hours',
type: 'textarea',
localized: true,
admin: {
description: 'Użyj Enter by przejść do nowej linii',
},
},
{
name: 'form',
type: 'relationship',
relationTo: 'forms',
required: true,
localized: true,
admin: {
description: 'Wybierz formularz kontaktowy. Dzięki opcji "localized" możesz wybrać inny formularz dla języka polskiego i inny dla niemieckiego.',
}
}
],
}
+2
View File
@@ -15,6 +15,7 @@ import { FinalCTABlock } from './FinalCTA/Component'
import { PageHeaderBlock } from './PageHeader/Component' import { PageHeaderBlock } from './PageHeader/Component'
import { ContactInfoBlock } from './ContactInfo/Component' import { ContactInfoBlock } from './ContactInfo/Component'
import { ContactSectionBlock } from './ContactSection/Component'
import { BioBlock } from './Bio/Component' import { BioBlock } from './Bio/Component'
import { TeamBlock } from './Team/Component' import { TeamBlock } from './Team/Component'
import { ServicesDetailedBlock } from './ServicesDetailed/Component' import { ServicesDetailedBlock } from './ServicesDetailed/Component'
@@ -25,6 +26,7 @@ import { StatsBlock } from './Stats/Component'
const blockComponents = { const blockComponents = {
pageHeader: PageHeaderBlock, pageHeader: PageHeaderBlock,
contactInfo: ContactInfoBlock, contactInfo: ContactInfoBlock,
contactSection: ContactSectionBlock,
bio: BioBlock, bio: BioBlock,
team: TeamBlock, team: TeamBlock,
servicesDetailed: ServicesDetailedBlock, servicesDetailed: ServicesDetailedBlock,
@@ -64,6 +64,7 @@ export const ServicesDetailedBlock: React.FC<Props> = ({
return ( return (
<motion.div <motion.div
key={service.id || index} key={service.id || index}
id={service.anchor}
variants={{ variants={{
hidden: { opacity: 0, y: isFirst ? 24 : 16 }, hidden: { opacity: 0, y: isFirst ? 24 : 16 },
visible: { visible: {
+2
View File
@@ -1,4 +1,5 @@
import type { Block } from 'payload' import type { Block } from 'payload'
import { buildSlugField } from '@intecion/ipal-kit'
export const ServicesDetailed: Block = { export const ServicesDetailed: Block = {
slug: 'servicesDetailed', slug: 'servicesDetailed',
@@ -12,6 +13,7 @@ export const ServicesDetailed: Block = {
required: true, required: true,
fields: [ fields: [
{ name: 'title', type: 'text', required: true }, { name: 'title', type: 'text', required: true },
buildSlugField({ name: 'anchor', from: 'title', required: true, localized: false }),
{ name: 'description', type: 'textarea', required: true }, { name: 'description', type: 'textarea', required: true },
{ {
name: 'icon', name: 'icon',
+4 -2
View File
@@ -7,13 +7,15 @@ export const Team: Block = {
name: 'title', name: 'title',
type: 'text', type: 'text',
localized: true, localized: true,
defaultValue: 'Nasz zespół', defaultValue: ({ locale }: { locale: string }) => locale === 'de' ? 'Unser Team' : 'Nasz zespół',
}, },
{ {
name: 'subtitle', name: 'subtitle',
type: 'textarea', type: 'textarea',
localized: true, localized: true,
defaultValue: 'Poznaj ekspertów, którzy z zaangażowaniem dbają o Twoje finanse. Każdy z nas wnosi wieloletnie doświadczenie, by zagwarantować Ci spokój i bezpieczeństwo.', defaultValue: ({ locale }: { locale: string }) => locale === 'de'
? 'Lernen Sie die Experten kennen, die sich mit Engagement um Ihre Finanzen kümmern. Jeder von uns bringt langjährige Erfahrung mit, um Ihnen Sicherheit und Ruhe zu garantieren.'
: 'Poznaj ekspertów, którzy z zaangażowaniem dbają o Twoje finanse. Każdy z nas wnosi wieloletnie doświadczenie, by zagwarantować Ci spokój i bezpieczeństwo.',
}, },
{ {
name: 'members', name: 'members',
+35 -16
View File
@@ -32,21 +32,38 @@ export const TestimonialsCarousel: React.FC<CarouselProps> = ({ title, items })
if (!containerRef.current) return if (!containerRef.current) return
const { scrollLeft, scrollWidth, clientWidth } = containerRef.current const { scrollLeft, scrollWidth, clientWidth } = containerRef.current
// Obliczamy który element jest najbardziej widoczny // Jeśli przewinęliśmy do samego końca (uwzględniamy drobną tolerancję na różnice pikselowe w przeglądarkach)
// Szerokość jednego elementu (przy założeniu że wszystkie są równe i wypełniają szerokość kontenera na mobile, if (Math.abs(scrollWidth - clientWidth - scrollLeft) < 5) {
// lub połowę na desktop) if (activeIndex !== items.length - 1) {
const cardWidth = containerRef.current.firstElementChild?.clientWidth || clientWidth setActiveIndex(items.length - 1)
const newIndex = Math.round(scrollLeft / cardWidth) }
return
if (newIndex !== activeIndex) {
setActiveIndex(newIndex)
} }
}, [activeIndex])
// Znajdź kartę, która jest najbliżej lewej krawędzi (scrollLeft)
const children = Array.from(containerRef.current.children) as HTMLElement[]
let closestIndex = 0
let minDiff = Infinity
children.forEach((child, index) => {
const diff = Math.abs(child.offsetLeft - scrollLeft)
if (diff < minDiff) {
minDiff = diff
closestIndex = index
}
})
if (closestIndex !== activeIndex) {
setActiveIndex(closestIndex)
}
}, [activeIndex, items.length])
useEffect(() => { useEffect(() => {
const el = containerRef.current const el = containerRef.current
if (el) { if (el) {
el.addEventListener('scroll', handleScroll, { passive: true }) el.addEventListener('scroll', handleScroll, { passive: true })
// Wywołaj raz na start, żeby upewnić się że stan jest zsynchronizowany
handleScroll()
return () => el.removeEventListener('scroll', handleScroll) return () => el.removeEventListener('scroll', handleScroll)
} }
}, [handleScroll]) }, [handleScroll])
@@ -54,14 +71,16 @@ export const TestimonialsCarousel: React.FC<CarouselProps> = ({ title, items })
const scrollTo = (index: number) => { const scrollTo = (index: number) => {
if (!containerRef.current) return if (!containerRef.current) return
const cardWidth = containerRef.current.firstElementChild?.clientWidth || containerRef.current.clientWidth const children = containerRef.current.children
if (index >= 0 && index < children.length) {
const child = children[index] as HTMLElement
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches containerRef.current.scrollTo({
left: child.offsetLeft,
containerRef.current.scrollTo({ behavior: prefersReducedMotion ? 'auto' : 'smooth'
left: index * cardWidth, })
behavior: prefersReducedMotion ? 'auto' : 'smooth' }
})
} }
const scrollPrev = () => { const scrollPrev = () => {
+2
View File
@@ -13,6 +13,7 @@ import { FinalCTA } from '../blocks/FinalCTA/config'
import { PageHeader } from '../blocks/PageHeader/config' import { PageHeader } from '../blocks/PageHeader/config'
import { ContactInfo } from '../blocks/ContactInfo/config' import { ContactInfo } from '../blocks/ContactInfo/config'
import { ContactSection } from '../blocks/ContactSection/config'
import { TextSection } from '../blocks/TextSection/config' import { TextSection } from '../blocks/TextSection/config'
import { ServicesDetailed } from '../blocks/ServicesDetailed/config' import { ServicesDetailed } from '../blocks/ServicesDetailed/config'
import { Eligibility } from '../blocks/Eligibility/config' import { Eligibility } from '../blocks/Eligibility/config'
@@ -45,6 +46,7 @@ export const Pages: CollectionConfig = {
blocks: [ blocks: [
PageHeader, PageHeader,
ContactInfo, ContactInfo,
ContactSection,
Bio, Bio,
Team, Team,
ServicesDetailed, ServicesDetailed,
+2
View File
@@ -0,0 +1,2 @@
'use client'
export { ConsentProvider, CookieBanner, CookieButton, useConsentContext } from '@intecion/ipal-kit/client'
+2 -1
View File
@@ -2,7 +2,8 @@
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import { MessageCircle } from 'lucide-react' import { MessageCircle } from 'lucide-react'
import { motion, AnimatePresence } from 'framer-motion' import { motion, AnimatePresence } from 'framer-motion'
import { useConsentContext } from '@intecion/ipal-kit/client'
import { useConsentContext } from '../Consent'
export const FloatingWhatsApp = ({ whatsappLink, ariaLabel }: { whatsappLink: string, ariaLabel: string }) => { export const FloatingWhatsApp = ({ whatsappLink, ariaLabel }: { whatsappLink: string, ariaLabel: string }) => {
const [isVisible, setIsVisible] = useState(true) const [isVisible, setIsVisible] = useState(true)
+43
View File
@@ -0,0 +1,43 @@
'use server'
import { getPayload } from 'payload'
import configPromise from '../../payload.config'
import { submitForm } from '@intecion/ipal-kit/server'
import { headers } from 'next/headers'
export async function submitContactFormAction(formData: FormData) {
const payload = await getPayload({ config: configPromise })
const formId = formData.get('formId') as string
const turnstileToken = formData.get('cf-turnstile-response') as string | undefined
const data: Record<string, unknown> = {}
for (const [key, value] of formData.entries()) {
if (key !== 'formId' && key !== 'cf-turnstile-response' && !key.startsWith('$ACTION')) {
data[key] = value
}
}
// The consent checkbox usually sends "on" when checked.
// We need to convert it to true for the form builder to accept it as checked boolean if the field is a checkbox.
// We can do this automatically for all fields that are not empty but 'on' or 'true' or just leave it.
// Wait, payload-plugin-form-builder expects boolean for checkboxes.
for (const [key, value] of Object.entries(data)) {
if (value === 'on') data[key] = true
}
const heads = await headers()
const ip = heads.get('x-forwarded-for') || heads.get('x-real-ip') || undefined
const result = await submitForm({
payload,
formId,
data,
turnstileToken,
ip,
consentFieldName: 'consent', // we can set default 'consent' or leave undefined (it defaults to 'consent')
})
// Next.js Server Actions must return plain objects
return JSON.parse(JSON.stringify(result))
}
+173
View File
@@ -0,0 +1,173 @@
'use client'
import React, { useState } from 'react'
import { Turnstile, resolveFormMessage } from '@intecion/ipal-kit/client'
import { submitContactFormAction } from './actions'
export function FormClient({
form,
notifications,
turnstileSiteKey
}: {
form: any;
notifications: any;
turnstileSiteKey: string
}) {
const [status, setStatus] = useState<'idle' | 'submitting' | 'success'>('idle')
const [errorMsg, setErrorMsg] = useState<string | null>(null)
const [turnstileToken, setTurnstileToken] = useState<string | null>(null)
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault()
setStatus('submitting')
setErrorMsg(null)
const formData = new FormData(e.currentTarget)
formData.set('formId', form.id.toString())
if (turnstileToken) {
formData.set('cf-turnstile-response', turnstileToken)
}
try {
const result = await submitContactFormAction(formData)
if (result.success) {
setStatus('success')
} else {
setStatus('idle')
setErrorMsg(resolveFormMessage(result, notifications))
}
} catch (err) {
setStatus('idle')
setErrorMsg('Wystąpił błąd podczas wysyłania formularza. Spróbuj ponownie później.')
}
}
if (status === 'success') {
return (
<div className="flex flex-col items-center justify-center p-8 text-center bg-accent/5 border border-accent/20 rounded-lg">
<div className="w-16 h-16 bg-accent text-accent-foreground rounded-full flex items-center justify-center mb-6">
<svg className="w-8 h-8" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
</svg>
</div>
<h3 className="text-2xl font-bold text-foreground mb-4 font-heading">{form.confirmationType === 'message' && form.confirmationMessage ? 'Wiadomość wysłana' : 'Sukces'}</h3>
<p className="text-foreground/80 leading-relaxed max-w-md">
Dziękujemy za kontakt. Odpowiemy najszybciej jak to możliwe.
</p>
</div>
)
}
return (
<form onSubmit={handleSubmit} className="flex flex-col gap-6">
{errorMsg && (
<div className="p-4 bg-destructive/10 border border-destructive/20 text-destructive rounded-lg text-sm font-medium">
{errorMsg}
</div>
)}
{form.fields?.map((field: any, idx: number) => {
if (field.blockType === 'text') {
return (
<div key={idx} className="flex flex-col gap-2">
{field.label && <label htmlFor={field.name} className="text-sm font-medium text-foreground/80">{field.label} {field.required && <span className="text-destructive">*</span>}</label>}
<input
type="text"
id={field.name}
name={field.name}
required={field.required}
placeholder={field.defaultValue || ''}
className="flex h-12 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
/>
</div>
)
}
if (field.blockType === 'email') {
return (
<div key={idx} className="flex flex-col gap-2">
{field.label && <label htmlFor={field.name} className="text-sm font-medium text-foreground/80">{field.label} {field.required && <span className="text-destructive">*</span>}</label>}
<input
type="email"
id={field.name}
name={field.name}
required={field.required}
placeholder={field.defaultValue || (field.label === 'Adres e-mail' ? '[email protected]' : '[email protected]')}
className="flex h-12 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
/>
</div>
)
}
if (field.blockType === 'textarea') {
return (
<div key={idx} className="flex flex-col gap-2">
{field.label && <label htmlFor={field.name} className="text-sm font-medium text-foreground/80">{field.label} {field.required && <span className="text-destructive">*</span>}</label>}
<textarea
id={field.name}
name={field.name}
required={field.required}
rows={5}
placeholder={field.defaultValue || ''}
className="flex min-h-[120px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
/>
</div>
)
}
if (field.blockType === 'select') {
return (
<div key={idx} className="flex flex-col gap-2">
{field.label && <label htmlFor={field.name} className="text-sm font-medium text-foreground/80">{field.label} {field.required && <span className="text-destructive">*</span>}</label>}
<select
id={field.name}
name={field.name}
required={field.required}
className="flex h-12 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
>
<option value="">--</option>
{field.options?.map((opt: any, oIdx: number) => (
<option key={oIdx} value={opt.value}>{opt.label}</option>
))}
</select>
</div>
)
}
if (field.blockType === 'checkbox') {
return (
<div key={idx} className="flex flex-row items-start gap-3 mt-2">
<div className="flex items-center h-6">
<input
type="checkbox"
id={field.name}
name={field.name}
required={field.required}
defaultChecked={field.defaultValue}
className="w-5 h-5 rounded border-border text-accent focus:ring-accent"
/>
</div>
{field.label && (
<label htmlFor={field.name} className="text-sm text-foreground/80 leading-tight">
<span dangerouslySetInnerHTML={{ __html: field.label }} />
{field.required && <span className="text-destructive ml-1">*</span>}
</label>
)}
</div>
)
}
return null
})}
{turnstileSiteKey && (
<div className="my-2">
<Turnstile siteKey={turnstileSiteKey} onToken={setTurnstileToken} />
</div>
)}
<button
type="submit"
disabled={status === 'submitting'}
className="mt-4 h-14 bg-accent hover:bg-accent/90 text-white font-bold font-heading tracking-wide uppercase px-8 flex items-center justify-center transition-colors disabled:opacity-70 disabled:cursor-not-allowed"
>
{status === 'submitting' ? 'Wysyłanie...' : (form.submitButtonLabel || 'Wyślij')}
</button>
</form>
)
}
+30
View File
@@ -0,0 +1,30 @@
import React from 'react'
import { getPayload } from 'payload'
import configPromise from '../../payload.config'
import { FormClient } from './client'
import { getNotificationTexts, getSiteIntegrations } from '@intecion/ipal-kit'
import { headers } from 'next/headers'
export async function FormRenderer({ form }: { form: any }) {
if (typeof form !== 'object' || !form) return null
const payload = await getPayload({ config: configPromise })
// Get current locale from headers or fallback
const heads = await headers()
const pathname = heads.get('x-pathname') || ''
const locale = pathname.split('/').filter(Boolean)[0] === 'de' ? 'de' : 'pl'
const notifications = await getNotificationTexts({ payload, locale })
const siteIntegrations = await getSiteIntegrations<any>(payload, { locale })
const turnstileSiteKey = siteIntegrations?.turnstile?.siteKey || ''
return (
<FormClient
form={form}
notifications={notifications.form}
turnstileSiteKey={turnstileSiteKey}
/>
)
}
+4 -4
View File
@@ -29,16 +29,16 @@ export function Header({ navItems = [], whatsappNumber }: { navItems?: any[], wh
return ( return (
<> <>
<header <header
className={`fixed top-0 left-0 right-0 z-50 transition-all duration-300 ${ className={`fixed top-0 left-0 right-0 z-50 transition-all duration-300 bg-background/90 backdrop-blur-md shadow-sm border-b border-border ${
isScrolled isScrolled
? "bg-background/90 backdrop-blur-md shadow-sm border-b border-border py-4" ? "py-4"
: "bg-transparent py-6" : "py-6"
}`} }`}
style={{ paddingRight: 'var(--scrollbar-width)' }} style={{ paddingRight: 'var(--scrollbar-width)' }}
> >
<div className="container-premium flex items-center justify-between"> <div className="container-premium flex items-center justify-between">
{/* Logo */} {/* Logo */}
<Link href={`/${currentLocale.code}`} className="group flex items-center gap-3 z-50" aria-label="Finanz- & Lohnbuchhaltung Dipl.-Ing. Janusz Kędzierski"> <Link href={`/${currentLocale.code}`} className="group flex items-center gap-3 z-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2 focus-visible:ring-offset-background rounded-sm" aria-label="Finanz- & Lohnbuchhaltung Dipl.-Ing. Janusz Kędzierski">
<div className="w-10 h-10 rounded-none bg-accent text-white flex items-center justify-center font-heading font-bold text-xl group-hover:bg-accent-hover transition-colors shadow-sm"> <div className="w-10 h-10 rounded-none bg-accent text-white flex items-center justify-center font-heading font-bold text-xl group-hover:bg-accent-hover transition-colors shadow-sm">
JK JK
</div> </div>
+385
View File
@@ -71,6 +71,8 @@ export interface Config {
users: User; users: User;
media: Media; media: Media;
pages: Page; pages: Page;
forms: Form;
'form-submissions': FormSubmission;
'payload-mcp-api-keys': PayloadMcpApiKey; 'payload-mcp-api-keys': PayloadMcpApiKey;
'payload-kv': PayloadKv; 'payload-kv': PayloadKv;
'payload-locked-documents': PayloadLockedDocument; 'payload-locked-documents': PayloadLockedDocument;
@@ -82,6 +84,8 @@ export interface Config {
users: UsersSelect<false> | UsersSelect<true>; users: UsersSelect<false> | UsersSelect<true>;
media: MediaSelect<false> | MediaSelect<true>; media: MediaSelect<false> | MediaSelect<true>;
pages: PagesSelect<false> | PagesSelect<true>; pages: PagesSelect<false> | PagesSelect<true>;
forms: FormsSelect<false> | FormsSelect<true>;
'form-submissions': FormSubmissionsSelect<false> | FormSubmissionsSelect<true>;
'payload-mcp-api-keys': PayloadMcpApiKeysSelect<false> | PayloadMcpApiKeysSelect<true>; 'payload-mcp-api-keys': PayloadMcpApiKeysSelect<false> | PayloadMcpApiKeysSelect<true>;
'payload-kv': PayloadKvSelect<false> | PayloadKvSelect<true>; 'payload-kv': PayloadKvSelect<false> | PayloadKvSelect<true>;
'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>; 'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>;
@@ -230,6 +234,25 @@ export interface Page {
blockName?: string | null; blockName?: string | null;
blockType: 'contactInfo'; blockType: 'contactInfo';
} }
| {
/**
* Użyj Enter by przejść do nowej linii
*/
address?: string | null;
phone?: string | null;
email?: string | null;
/**
* Użyj Enter by przejść do nowej linii
*/
hours?: string | null;
/**
* Wybierz formularz kontaktowy. Dzięki opcji "localized" możesz wybrać inny formularz dla języka polskiego i inny dla niemieckiego.
*/
form: number | Form;
id?: string | null;
blockName?: string | null;
blockType: 'contactSection';
}
| { | {
title: string; title: string;
paragraphs: { paragraphs: {
@@ -269,6 +292,10 @@ export interface Page {
subtitle?: string | null; subtitle?: string | null;
services: { services: {
title: string; title: string;
/**
* Auto-generated from the title when left empty. You can override it.
*/
anchor: string;
description: string; description: string;
icon?: ('fileText' | 'calculator' | 'shieldCheck' | 'scale' | 'helpCircle' | 'users' | 'coins') | null; icon?: ('fileText' | 'calculator' | 'shieldCheck' | 'scale' | 'helpCircle' | 'users' | 'coins') | null;
id?: string | null; id?: string | null;
@@ -508,6 +535,193 @@ export interface Page {
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
} }
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "forms".
*/
export interface Form {
id: number;
title: string;
fields?:
| (
| {
name: string;
label?: string | null;
width?: number | null;
required?: boolean | null;
defaultValue?: boolean | null;
id?: string | null;
blockName?: string | null;
blockType: 'checkbox';
}
| {
name: string;
label?: string | null;
width?: number | null;
required?: boolean | null;
id?: string | null;
blockName?: string | null;
blockType: 'country';
}
| {
name: string;
label?: string | null;
width?: number | null;
required?: boolean | null;
id?: string | null;
blockName?: string | null;
blockType: 'email';
}
| {
message?: {
root: {
type: string;
children: {
type: any;
version: number;
[k: string]: unknown;
}[];
direction: ('ltr' | 'rtl') | null;
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
indent: number;
version: number;
};
[k: string]: unknown;
} | null;
id?: string | null;
blockName?: string | null;
blockType: 'message';
}
| {
name: string;
label?: string | null;
width?: number | null;
defaultValue?: number | null;
required?: boolean | null;
id?: string | null;
blockName?: string | null;
blockType: 'number';
}
| {
name: string;
label?: string | null;
width?: number | null;
defaultValue?: string | null;
placeholder?: string | null;
options?:
| {
label: string;
value: string;
id?: string | null;
}[]
| null;
required?: boolean | null;
id?: string | null;
blockName?: string | null;
blockType: 'select';
}
| {
name: string;
label?: string | null;
width?: number | null;
required?: boolean | null;
id?: string | null;
blockName?: string | null;
blockType: 'state';
}
| {
name: string;
label?: string | null;
width?: number | null;
defaultValue?: string | null;
required?: boolean | null;
id?: string | null;
blockName?: string | null;
blockType: 'text';
}
| {
name: string;
label?: string | null;
width?: number | null;
defaultValue?: string | null;
required?: boolean | null;
id?: string | null;
blockName?: string | null;
blockType: 'textarea';
}
)[]
| null;
submitButtonLabel?: string | null;
confirmationType?: ('message' | 'redirect') | null;
confirmationMessage?: {
root: {
type: string;
children: {
type: any;
version: number;
[k: string]: unknown;
}[];
direction: ('ltr' | 'rtl') | null;
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
indent: number;
version: number;
};
[k: string]: unknown;
} | null;
redirect?: {
type?: ('reference' | 'custom') | null;
reference?: {
relationTo: 'pages';
value: number | Page;
} | null;
url?: string | null;
};
emails?:
| {
emailTo?: string | null;
cc?: string | null;
bcc?: string | null;
replyTo?: string | null;
emailFrom?: string | null;
subject: string;
message?: {
root: {
type: string;
children: {
type: any;
version: number;
[k: string]: unknown;
}[];
direction: ('ltr' | 'rtl') | null;
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
indent: number;
version: number;
};
[k: string]: unknown;
} | null;
id?: string | null;
}[]
| null;
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "form-submissions".
*/
export interface FormSubmission {
id: number;
form: number | Form;
submissionData?:
| {
field: string;
value: string;
id?: string | null;
}[]
| null;
updatedAt: string;
createdAt: string;
}
/** /**
* API keys control which collections, resources, tools, and prompts MCP clients can access * API keys control which collections, resources, tools, and prompts MCP clients can access
* *
@@ -571,6 +785,14 @@ export interface PayloadLockedDocument {
relationTo: 'pages'; relationTo: 'pages';
value: number | Page; value: number | Page;
} | null) } | null)
| ({
relationTo: 'forms';
value: number | Form;
} | null)
| ({
relationTo: 'form-submissions';
value: number | FormSubmission;
} | null)
| ({ | ({
relationTo: 'payload-mcp-api-keys'; relationTo: 'payload-mcp-api-keys';
value: number | PayloadMcpApiKey; value: number | PayloadMcpApiKey;
@@ -696,6 +918,17 @@ export interface PagesSelect<T extends boolean = true> {
id?: T; id?: T;
blockName?: T; blockName?: T;
}; };
contactSection?:
| T
| {
address?: T;
phone?: T;
email?: T;
hours?: T;
form?: T;
id?: T;
blockName?: T;
};
bio?: bio?:
| T | T
| { | {
@@ -743,6 +976,7 @@ export interface PagesSelect<T extends boolean = true> {
| T | T
| { | {
title?: T; title?: T;
anchor?: T;
description?: T; description?: T;
icon?: T; icon?: T;
id?: T; id?: T;
@@ -986,6 +1220,157 @@ export interface PagesSelect<T extends boolean = true> {
updatedAt?: T; updatedAt?: T;
createdAt?: T; createdAt?: T;
} }
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "forms_select".
*/
export interface FormsSelect<T extends boolean = true> {
title?: T;
fields?:
| T
| {
checkbox?:
| T
| {
name?: T;
label?: T;
width?: T;
required?: T;
defaultValue?: T;
id?: T;
blockName?: T;
};
country?:
| T
| {
name?: T;
label?: T;
width?: T;
required?: T;
id?: T;
blockName?: T;
};
email?:
| T
| {
name?: T;
label?: T;
width?: T;
required?: T;
id?: T;
blockName?: T;
};
message?:
| T
| {
message?: T;
id?: T;
blockName?: T;
};
number?:
| T
| {
name?: T;
label?: T;
width?: T;
defaultValue?: T;
required?: T;
id?: T;
blockName?: T;
};
select?:
| T
| {
name?: T;
label?: T;
width?: T;
defaultValue?: T;
placeholder?: T;
options?:
| T
| {
label?: T;
value?: T;
id?: T;
};
required?: T;
id?: T;
blockName?: T;
};
state?:
| T
| {
name?: T;
label?: T;
width?: T;
required?: T;
id?: T;
blockName?: T;
};
text?:
| T
| {
name?: T;
label?: T;
width?: T;
defaultValue?: T;
required?: T;
id?: T;
blockName?: T;
};
textarea?:
| T
| {
name?: T;
label?: T;
width?: T;
defaultValue?: T;
required?: T;
id?: T;
blockName?: T;
};
};
submitButtonLabel?: T;
confirmationType?: T;
confirmationMessage?: T;
redirect?:
| T
| {
type?: T;
reference?: T;
url?: T;
};
emails?:
| T
| {
emailTo?: T;
cc?: T;
bcc?: T;
replyTo?: T;
emailFrom?: T;
subject?: T;
message?: T;
id?: T;
};
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "form-submissions_select".
*/
export interface FormSubmissionsSelect<T extends boolean = true> {
form?: T;
submissionData?:
| T
| {
field?: T;
value?: T;
id?: T;
};
updatedAt?: T;
createdAt?: T;
}
/** /**
* This interface was referenced by `Config`'s JSON-Schema * This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-mcp-api-keys_select". * via the `definition` "payload-mcp-api-keys_select".
+12 -2
View File
@@ -1,6 +1,6 @@
import { sqliteAdapter } from '@payloadcms/db-sqlite' import { sqliteAdapter } from '@payloadcms/db-sqlite'
import { mcpPlugin } from '@payloadcms/plugin-mcp' import { mcpPlugin } from '@payloadcms/plugin-mcp'
import { ipalKit } from '@intecion/ipal-kit' import { ipalKit, panelSmtpAdapter } from '@intecion/ipal-kit'
import { lexicalEditor } from '@payloadcms/richtext-lexical' import { lexicalEditor } from '@payloadcms/richtext-lexical'
import path from 'path' import path from 'path'
import { buildConfig } from 'payload' import { buildConfig } from 'payload'
@@ -43,5 +43,15 @@ export default buildConfig({
fallback: true, fallback: true,
defaultLocale: i18n.defaultLocale, defaultLocale: i18n.defaultLocale,
}, },
plugins: [mcpPlugin({}), ipalKit({ i18n, pages: { slug: 'pages' } })], email: panelSmtpAdapter(),
plugins: [
mcpPlugin({}),
ipalKit({
i18n,
pages: { slug: 'pages' },
forms: {
redirectRelationships: ['pages']
}
})
],
}) })
+29
View File
@@ -0,0 +1,29 @@
import { chromium } from '@playwright/test';
(async () => {
const browser = await chromium.launch();
// Desktop
const desktopContext = await browser.newContext({
viewport: { width: 1280, height: 800 }
});
const desktopPage = await desktopContext.newPage();
await desktopPage.goto('http://localhost:3000/pl');
// Poczekaj na załadowanie
await desktopPage.waitForTimeout(2000);
await desktopPage.screenshot({ path: 'screenshot_desktop.png' });
await desktopContext.close();
// Mobile
const mobileContext = await browser.newContext({
viewport: { width: 375, height: 667 }
});
const mobilePage = await mobileContext.newPage();
await mobilePage.goto('http://localhost:3000/pl');
await mobilePage.waitForTimeout(2000);
await mobilePage.screenshot({ path: 'screenshot_mobile.png' });
await mobileContext.close();
await browser.close();
console.log('Screenshots taken!');
})();