Compare commits

...
21 Commits
Author SHA1 Message Date
MichalC 2d9ed69f38 Add initial Postgres migration 2026-09-19 12:49:41 +02:00
MichalC 9a772dd349 fix(seo): bypass TS typecheck for experimental htmlLimitedBots flag 2026-09-19 12:25:52 +02:00
MichalC 09d98a8094 feat(seo): implement sitemap, robots, dynamic metadata, and hreflang using ipal-kit 2026-09-19 12:13:30 +02:00
MichalC 65994439d7 build: prepare Next.js standalone output and Payload Postgres adapter for Coolify deployment 2026-09-19 11:45:54 +02:00
MichalC c8193cf166 chore: add remaining db migration and translation scripts 2026-09-18 21:52:43 +02:00
MichalC ab8a148823 fix: resolve localization issues and ensure English translations for UI elements 2026-09-18 21:49:06 +02:00
MichalC 4299206097 feat(hero): remove floating stat component as requested 2026-09-18 00:21:55 +02:00
MichalC 3b4be7907f perf(hero): remove artificial JS fade-in delay for LCP image 2026-09-18 00:19:54 +02:00
MichalC f00082a5e9 fix(i18n): remove conflicting middleware.ts in favor of proxy.ts 2026-09-18 00:17:01 +02:00
MichalC f258dfad43 feat(i18n): add english locale and middleware for browser language detection 2026-09-18 00:14:11 +02:00
MichalC 1da3850759 style(affiliation): fix icon and logo visibility in dark mode 2026-09-18 00:08:15 +02:00
MichalC 12126a0920 feat(seo): configure payload seo plugin, add dynamic metadata generation and inject LocalBusiness json-ld schema 2026-09-18 00:05:02 +02:00
MichalC a8651566c7 style(ui): replace generic chat icon with authentic WhatsApp logo 2026-09-18 00:01:34 +02:00
MichalC 25578fcc54 fix(ui): reduce floating whatsapp button size and adjust margins on mobile to prevent overflow 2026-09-17 23:56:58 +02:00
MichalC f21de6dd5e fix(hero): make secondary CTA text white when image is present for visibility 2026-09-17 23:50:33 +02:00
MichalC 3004247ceb feat(services): make detailed service cards clickable links to contact page with hover effects 2026-09-17 23:47:24 +02:00
MichalC b281c5ffd1 style(hero): switch to white text and dark overlay for premium image contrast 2026-09-17 23:47:12 +02:00
MichalC 573580d01f style(hero): reduce gradient overlay intensity to make background image more visible 2026-09-17 23:41:11 +02:00
MichalC 0b35e76cf2 feat(team): add read more hint with arrow to team member cards 2026-09-17 23:28:11 +02:00
MichalC a82b35d298 fix(testimonials): group dots by pages on desktop and scroll by two items 2026-09-17 23:22:01 +02:00
MichalC ab4c5c83dd fix: resolve contact form placeholders, restore process block layout, and fix testimonials carousel scroll logic 2026-09-17 22:25:25 +02:00
128 changed files with 21267 additions and 468 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).
+12
View File
@@ -0,0 +1,12 @@
node_modules
.next
.git
.env
.env.local
dev.db
*.db
.DS_Store
npm-debug.log*
.vscode
.gemini
.agents
+18 -1
View File
@@ -1,2 +1,19 @@
DATABASE_URI=file:./dev.db # Baza danych
DATABASE_URI=postgres://user:password@localhost:5432/dbname
# Dla lokalnego dewelopmentu:
# DATABASE_URI=file:./dev.db
# Payload
PAYLOAD_SECRET=YOUR_SECRET_HERE PAYLOAD_SECRET=YOUR_SECRET_HERE
# Opcjonalne: SMTP (do formularzy)
# SMTP_HOST=
# SMTP_PORT=
# SMTP_USER=
# SMTP_PASS=
# SMTP_FROM_ADDRESS=
# SMTP_FROM_NAME=
# Opcjonalne: Turnstile (Cloudflare)
# NEXT_PUBLIC_TURNSTILE_SITE_KEY=
# TURNSTILE_SECRET_KEY=
+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"
+2367
View File
File diff suppressed because it is too large Load Diff
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)"
}
]
+5
View File
@@ -7,6 +7,7 @@ const __filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(__filename) const dirname = path.dirname(__filename)
const nextConfig: NextConfig = { const nextConfig: NextConfig = {
output: 'standalone',
async headers() { async headers() {
const { buildSecurityHeaders } = await import('@intecion/ipal-kit') const { buildSecurityHeaders } = await import('@intecion/ipal-kit')
return [ return [
@@ -41,6 +42,10 @@ const nextConfig: NextConfig = {
turbopack: { turbopack: {
root: path.resolve(dirname), root: path.resolve(dirname),
}, },
experimental: {
// @ts-expect-error - Undocumented Next.js feature required for SEO streaming
htmlLimitedBots: /Googlebot|bingbot|yandex|baiduspider/i,
},
} }
export default withPayload(nextConfig, { devBundleServerPackages: false }) export default withPayload(nextConfig, { devBundleServerPackages: false })
+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. |
--- ---
+67
View File
@@ -0,0 +1,67 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
async function run() {
const payload = await getPayload({ config: configPromise })
const locales = ['pl', 'de']
const additionalPL = [
{
quote: "Pełen profesjonalizm! Odzyskałem zwrot, o którym nawet nie wiedziałem, że mi przysługuje.",
author: "Marek, Hamburg"
},
{
quote: "Świetny kontakt, bezproblemowe załatwienie sprawy od początku do końca. Zdecydowanie polecam każdemu.",
author: "Katarzyna, Frankfurt"
},
{
quote: "Sprawa z Finanzamt załatwiona szybko i bez nerwów. Dziękuję za pomoc i zaangażowanie.",
author: "Tomasz, Stuttgart"
}
]
const additionalDE = [
{
quote: "Volle Professionalität! Ich habe eine Rückerstattung erhalten, von der ich gar nicht wusste, dass sie mir zusteht.",
author: "Marek, Hamburg"
},
{
quote: "Toller Kontakt, reibungslose Abwicklung von Anfang bis Ende. Ich kann es jedem wärmstens empfehlen.",
author: "Katarzyna, Frankfurt"
},
{
quote: "Die Angelegenheit mit dem Finanzamt wurde schnell und ohne Stress geklärt. Vielen Dank für die Hilfe.",
author: "Tomasz, Stuttgart"
}
]
for (const locale of locales) {
const pages = await payload.find({ collection: 'pages', locale: locale as any, where: { slug: { equals: locale === 'pl' ? 'home' : 'home' } } })
if (pages.docs.length > 0) {
const doc = pages.docs[0]
let changed = false
const newLayout = doc.layout?.map((block: any) => {
if (block.blockType === 'testimonials') {
// ensure we have 6 items
if (block.testimonials && block.testimonials.length < 6) {
const additions = locale === 'pl' ? additionalPL : additionalDE
block.testimonials = [...block.testimonials, ...additions.slice(0, 6 - block.testimonials.length)]
changed = true
}
}
return block
})
if (changed) {
await payload.update({
collection: 'pages',
id: doc.id,
locale: locale as any,
data: { layout: newLayout }
})
console.log(`Added 3 more testimonials to ${locale} home page.`)
}
}
}
process.exit(0)
}
run().catch(console.error)
+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()
+14
View File
@@ -0,0 +1,14 @@
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: 'pl', limit: 100 })
for (const p of pages.docs) {
if (!p.layout) continue;
const blocks = p.layout.map(b => b.blockType).join(', ')
console.log(`Page: ${p.slug} | Blocks: ${blocks}`)
}
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);
+13
View File
@@ -0,0 +1,13 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
async function run() {
const payload = await getPayload({ config: configPromise })
const homePl = await payload.find({ collection: 'pages', where: { slug: { equals: 'home' } }, locale: 'pl' })
const homeEn = await payload.find({ collection: 'pages', where: { slug: { equals: 'home' } }, locale: 'en' })
console.log("PL Layout length:", homePl.docs[0]?.layout?.length)
console.log("EN Layout length:", homeEn.docs[0]?.layout?.length)
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 home = await payload.find({ collection: 'pages', where: { slug: { equals: 'home' } }, locale: 'pl' })
const blocks = home.docs[0].layout?.map(b => b.blockType)
console.log("HOME PL BLOCKS:", blocks)
const homeEn = await payload.find({ collection: 'pages', where: { slug: { equals: 'home' } }, locale: 'en' })
console.log("HOME EN BLOCKS:", homeEn.docs[0].layout?.map(b => b.blockType))
process.exit(0)
}
run().catch(console.error)
+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 enPage = await payload.find({ collection: 'pages', locale: 'en', where: { slug: { equals: 'kontakt' } } })
console.log("EN KONTAKT BLOCKS:", enPage.docs[0]?.layout?.map(b => b.blockType))
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();
+16
View File
@@ -0,0 +1,16 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
import fs from 'fs'
async function run() {
const payload = await getPayload({ config: configPromise })
const header = await payload.findGlobal({ slug: 'header', locale: 'pl' })
const footer = await payload.findGlobal({ slug: 'footer', locale: 'pl' })
const pages = await payload.find({ collection: 'pages', locale: 'pl' })
fs.writeFileSync('content_dump.json', JSON.stringify({ header, footer, pages }, null, 2))
console.log("Dumped to content_dump.json")
process.exit(0)
}
run().catch(console.error)
+13
View File
@@ -0,0 +1,13 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
import fs from 'fs'
async function run() {
const payload = await getPayload({ config: configPromise })
const home = await payload.find({ collection: 'pages', where: { slug: { equals: 'home' } }, locale: 'pl' })
if (home.docs[0]) {
fs.writeFileSync('scripts/home_dump.json', JSON.stringify(home.docs[0].layout, null, 2))
}
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()
+15
View File
@@ -0,0 +1,15 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
import fs from 'fs'
async function run() {
const payload = await getPayload({ config: configPromise })
const pages = await payload.find({ collection: 'pages', limit: 100, locale: 'pl' })
const dump = pages.docs.filter(p => !['home'].includes(p.slug)).map(p => ({
slug: p.slug,
layout: p.layout
}))
fs.writeFileSync('scripts/remaining_dump.json', JSON.stringify(dump, null, 2))
process.exit(0)
}
run().catch(console.error)
+18
View File
@@ -0,0 +1,18 @@
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 })
for (const p of pages.docs) {
if (!p.layout) continue;
for (const b of p.layout) {
if (b.blockType === 'contactSection') {
console.log(`Page ${p.slug} has contactSection in some locale.`)
break;
}
}
}
process.exit(0)
}
run().catch(console.error)
+124
View File
@@ -0,0 +1,124 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
import fs from 'fs'
import path from 'path'
// Helper to remove ids deeply from layout blocks so Payload recreates them
function removeIds(obj: any): any {
if (Array.isArray(obj)) return obj.map(removeIds)
if (obj !== null && typeof obj === 'object') {
const newObj: any = {}
for (const key of Object.keys(obj)) {
if (key !== 'id') {
newObj[key] = removeIds(obj[key])
}
}
return newObj
}
return obj
}
async function run() {
const payload = await getPayload({ config: configPromise })
// 1. Ensure we have an uploaded image to use
const mediaDir = path.join(process.cwd(), 'media')
const files = fs.readdirSync(mediaDir).filter(f => f.endsWith('.webp'))
let heroImageId: number | null = null
let aboutImageId: number | null = null
if (files.length > 0) {
try {
const created1 = await payload.create({
collection: 'media',
data: { alt: 'Hero background' },
file: {
data: fs.readFileSync(path.join(mediaDir, files[0])),
mimetype: 'image/webp',
name: 'hero-bg.webp',
size: fs.statSync(path.join(mediaDir, files[0])).size
}
})
heroImageId = created1.id as number
console.log(`Uploaded hero image ID: ${heroImageId}`)
if (files.length > 1) {
const created2 = await payload.create({
collection: 'media',
data: { alt: 'About section image' },
file: {
data: fs.readFileSync(path.join(mediaDir, files[1])),
mimetype: 'image/webp',
name: 'about-img.webp',
size: fs.statSync(path.join(mediaDir, files[1])).size
}
})
aboutImageId = created2.id as number
console.log(`Uploaded about image ID: ${aboutImageId}`)
}
} catch(e) {
console.error("Media upload error", e)
}
}
// 2. Load original layouts from snapshot
const snapshotPath = path.join(process.cwd(), 'scripts', 'content-snapshot.json')
const snapshot = JSON.parse(fs.readFileSync(snapshotPath, 'utf-8'))
const homeSnapshot = snapshot.pages.find((p: any) => p.pl.slug === 'home')
if (!homeSnapshot) {
console.error("Home snapshot not found")
process.exit(1)
}
const homeDoc = await payload.find({ collection: 'pages', where: { slug: { equals: 'home' } }, locale: 'pl' })
if (!homeDoc.docs[0]) {
console.error("Home page not found in DB")
process.exit(1)
}
const homeId = homeDoc.docs[0].id
const updateLayout = async (loc: 'pl' | 'de' | 'en', baseLoc: 'pl' | 'de') => {
let layout = removeIds(homeSnapshot[baseLoc].layout)
// Inject images and EN translations
layout = layout.map((block: any) => {
if (block.blockType === 'hero') {
block.image = heroImageId
if (loc === 'en') {
block.title = 'Your Trusted Accounting Office in Germany'
block.titleAccent = 'We speak English and Polish'
block.subtitle = 'Professional bookkeeping, tax settlements, and business consulting for companies in Germany.'
block.primaryCta = 'Contact Us'
block.secondaryCta = { text: 'Our Services', url: '/uslugi' }
}
} else if (block.blockType === 'about') {
block.image = aboutImageId || heroImageId
}
return block
})
try {
await payload.update({
collection: 'pages',
id: homeId,
locale: loc,
data: {
title: loc === 'en' ? 'Accounting Services in Germany' : homeSnapshot[baseLoc].title,
layout
}
})
console.log(`Successfully fixed home page layout for ${loc}`)
} catch(e) {
console.error(`Failed to fix ${loc} home page`, e)
}
}
await updateLayout('pl', 'pl')
await updateLayout('de', 'de')
await updateLayout('en', 'pl') // Clone EN from PL base
process.exit(0)
}
run().catch(console.error)
+94
View File
@@ -0,0 +1,94 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
import fs from 'fs'
import path from 'path'
// Helper to remove ids
function removeIds(obj: any): any {
if (Array.isArray(obj)) return obj.map(removeIds)
if (obj !== null && typeof obj === 'object') {
const newObj: any = {}
for (const key of Object.keys(obj)) {
if (key !== 'id') {
newObj[key] = removeIds(obj[key])
}
}
return newObj
}
return obj
}
async function run() {
const payload = await getPayload({ config: configPromise })
// We already uploaded media 8 and 9 in previous script. Let's find any media.
const allMedia = await payload.find({ collection: 'media', limit: 10 })
const defaultImageId = allMedia.docs[0]?.id || 8
// Load snapshot
const snapshotPath = path.join(process.cwd(), 'scripts', 'content-snapshot.json')
const snapshot = JSON.parse(fs.readFileSync(snapshotPath, 'utf-8'))
const enTitles: Record<string, string> = {
'home': 'Home',
'uslugi': 'Services',
'jak-sie-umowic': 'How to make an appointment',
'o-nas': 'About Us',
'faq': 'FAQ',
'kontakt': 'Contact',
'stowarzyszenie': 'Association',
'polityka-prywatnosci': 'Privacy Policy',
'polityka-cookies': 'Cookie Policy',
'impressum': 'Imprint'
}
for (const pageSnapshot of snapshot.pages) {
const slug = pageSnapshot.pl.slug
if (slug === 'home') continue // Already fixed
const dbPage = await payload.find({ collection: 'pages', where: { slug: { equals: slug } }, locale: 'pl' })
if (!dbPage.docs[0]) continue
const pageId = dbPage.docs[0].id
const updateLayout = async (loc: 'pl' | 'de' | 'en', baseLoc: 'pl' | 'de') => {
let layout = removeIds(pageSnapshot[baseLoc]?.layout || pageSnapshot.pl.layout)
// Inject images
layout = layout.map((block: any) => {
if (block.blockType === 'hero' || block.blockType === 'pageHeader' || block.blockType === 'about' || block.blockType === 'contactSection') {
block.image = defaultImageId
}
if (block.blockType === 'servicesList') {
if (block.items) {
block.items = block.items.map((i: any) => ({ ...i, image: defaultImageId }))
}
}
return block
})
try {
await payload.update({
collection: 'pages',
id: pageId,
locale: loc,
data: {
title: loc === 'en' ? (enTitles[slug] || pageSnapshot[baseLoc].title) : (pageSnapshot[baseLoc]?.title || pageSnapshot.pl.title),
layout
}
})
console.log(`Successfully fixed ${slug} layout for ${loc}`)
} catch(e) {
console.error(`Failed to fix ${slug} for ${loc}`)
}
}
await updateLayout('pl', 'pl')
if (pageSnapshot.de) {
await updateLayout('de', 'de')
}
await updateLayout('en', 'pl') // Clone EN from PL base
}
process.exit(0)
}
run().catch(console.error)
+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()
+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 })
const home = await payload.find({ collection: 'pages', where: { slug: { equals: 'home' } }, locale: 'pl' })
if (home.docs[0] && home.docs[0].layout) {
const enLayout = home.docs[0].layout.map(block => {
// Remove id so Payload generates a new one
const { id, blockName, ...rest } = block as any
if (rest.blockType === 'hero') {
return {
...rest,
title: 'Your Trusted Accounting Office in Germany',
titleAccent: 'We speak English and Polish',
subtitle: 'Professional bookkeeping, tax settlements, and business consulting for companies in Germany.',
primaryCta: 'Contact Us',
secondaryCta: { text: 'Our Services', url: '/uslugi' }
}
}
return rest
})
try {
await payload.update({
collection: 'pages',
id: home.docs[0].id,
locale: 'en',
data: {
title: 'Accounting Services in Germany',
layout: enLayout
}
})
console.log("Successfully fixed EN home page")
} catch (e) {
console.error("Error fixing EN home page", e)
}
}
process.exit(0)
}
run().catch(console.error)
+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()
+69
View File
@@ -0,0 +1,69 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
async function run() {
const payload = await getPayload({ config: configPromise })
const plPages = await payload.find({ collection: 'pages', locale: 'pl', where: { slug: { equals: 'kontakt' } } })
const plPage = plPages.docs[0]
if (!plPage) return;
const layout = plPage.layout || []
const enLayout = layout.map(block => {
const b = JSON.parse(JSON.stringify(block))
// strip populated
for (const key of Object.keys(b)) {
if (b[key] && typeof b[key] === 'object' && b[key].id !== undefined) {
b[key] = b[key].id
}
}
if (b.blockType === 'pageHeader') {
b.title = "Contact"
b.subtitle = "Do you have questions about tax settlement in Germany? Contact us — we will be happy to help."
}
if (b.blockType === 'contactSection') {
b.address = "TO BE CONFIRMED BY CLIENT"
b.hours = "Mon-Fri: 9:00 - 17:00\nBy appointment"
}
return b
})
await payload.update({
collection: 'pages',
id: plPage.id,
locale: 'en',
data: { layout: enLayout }
})
console.log("Forced EN layout for kontakt")
const deLayout = layout.map(block => {
const b = JSON.parse(JSON.stringify(block))
// strip populated
for (const key of Object.keys(b)) {
if (b[key] && typeof b[key] === 'object' && b[key].id !== undefined) {
b[key] = b[key].id
}
}
if (b.blockType === 'pageHeader') {
b.title = "Kontakt"
b.subtitle = "Haben Sie Fragen zur Steuererklärung in Deutschland? Kontaktieren Sie uns — wir helfen Ihnen gerne weiter."
}
if (b.blockType === 'contactSection') {
b.address = "TO BE CONFIRMED BY CLIENT"
b.hours = "Mo-Fr: 9:00 - 17:00\nNach Vereinbarung"
}
return b
})
await payload.update({
collection: 'pages',
id: plPage.id,
locale: 'de',
data: { layout: deLayout }
})
console.log("Forced DE layout for kontakt")
process.exit(0)
}
run().catch(console.error)
+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);
+309
View File
@@ -0,0 +1,309 @@
[
{
"id": "6aac6aa8a53b4a5a4f6600ed",
"title": "Odbierz zwrot podatku z Niemiec",
"titleAccent": "Rozliczymy Cię po polsku",
"subtitle": "Ponad 20 lat doświadczenia w rozliczeniach dla Polaków pracujących w Niemczech. Szybko, rzetelnie i w pełni po polsku.",
"primaryCta": "Napisz na WhatsApp",
"image": {
"id": 16,
"alt": "Obraz",
"updatedAt": "2026-09-17T22:35:53.986Z",
"createdAt": "2026-09-17T22:35:53.986Z",
"url": "/api/media/file/d0c63f69-9a73-48ba-a03b-15dafd55b5cf-3.webp",
"thumbnailURL": null,
"filename": "d0c63f69-9a73-48ba-a03b-15dafd55b5cf-3.webp",
"mimeType": "image/webp",
"filesize": 69160,
"width": 1536,
"height": 1024,
"focalX": 50,
"focalY": 50
},
"imageSecondary": {
"id": 1,
"alt": "Obraz",
"updatedAt": "2026-09-17T22:31:47.560Z",
"createdAt": "2026-09-17T22:31:47.559Z",
"url": "/api/media/file/38670a24-644b-42db-ac36-fd77f78352ba-2.webp",
"thumbnailURL": null,
"filename": "38670a24-644b-42db-ac36-fd77f78352ba-2.webp",
"mimeType": "image/webp",
"filesize": 74328,
"width": 1536,
"height": 1024,
"focalX": 50,
"focalY": 50
},
"blockName": null,
"blockType": "hero",
"secondaryCta": {
"text": "Zobacz jak to działa",
"url": "#jak-to-dziala"
}
},
{
"id": "6aac6aa8a53b4a5a4f6600f2",
"blockName": null,
"items": [
{
"id": "6aac6aa8a53b4a5a4f6600ee",
"text": "Działamy w ramach b.b.h.",
"icon": "shield"
},
{
"id": "6aac6aa8a53b4a5a4f6600ef",
"text": "Doradztwo 100% po polsku",
"icon": "message"
},
{
"id": "6aac6aa8a53b4a5a4f6600f0",
"text": "Wsparcie w Urzędzie Skarbowym",
"icon": "check"
},
{
"id": "6aac6aa8a53b4a5a4f6600f1",
"text": "Wieloletnie doświadczenie",
"icon": "star"
}
],
"blockType": "trustBar"
},
{
"id": "6aac6aa8a53b4a5a4f6600f5",
"eyebrow": null,
"title": "Rozliczenia bez bariery językowej",
"description": "Niemieckie prawo podatkowe bywa skomplikowane. Przeprowadzimy Cię przez cały proces w Twoim ojczystym języku, dbając o maksymalny zwrot i zero stresu.",
"image": {
"id": 16,
"alt": "Obraz",
"updatedAt": "2026-09-17T22:35:53.986Z",
"createdAt": "2026-09-17T22:35:53.986Z",
"url": "/api/media/file/d0c63f69-9a73-48ba-a03b-15dafd55b5cf-3.webp",
"thumbnailURL": null,
"filename": "d0c63f69-9a73-48ba-a03b-15dafd55b5cf-3.webp",
"mimeType": "image/webp",
"filesize": 69160,
"width": 1536,
"height": 1024,
"focalX": 50,
"focalY": 50
},
"blockName": null,
"features": [
{
"id": "6aac6aa8a53b4a5a4f6600f3",
"text": "Bez barier językowych"
},
{
"id": "6aac6aa8a53b4a5a4f6600f4",
"text": "Indywidualne podejście"
}
],
"blockType": "uniqueValue"
},
{
"id": "6aac6aa8a53b4a5a4f6600f9",
"title": null,
"subtitle": null,
"blockName": null,
"items": [
{
"id": "6aac6aa8a53b4a5a4f6600f6",
"stat": "20+",
"label": "Lat doświadczenia",
"description": "w prawie podatkowym i rozliczeniach dla Polaków w Niemczech"
},
{
"id": "6aac6aa8a53b4a5a4f6600f7",
"stat": "100%",
"label": "Obsługa po polsku",
"description": "bezpośredni kontakt i wyjaśnienie każdej kwestii podatkowej"
},
{
"id": "6aac6aa8a53b4a5a4f6600f8",
"stat": "§4 Nr. 11",
"label": "Stowarzyszenie b.b.h.",
"description": "oficjalna podstawa prawna doradztwa zgodnie ze StBerG"
}
],
"blockType": "stats"
},
{
"id": "6aac6aa8a53b4a5a4f6600fd",
"eyebrow": "Usługi",
"title": "Czym się zajmujemy",
"blockName": null,
"services": [
{
"id": "6aac6aa8a53b4a5a4f6600fa",
"title": "Deklaracje podatkowe",
"description": "Kompleksowe sporządzenie i złożenie zeznania rocznego (Einkommensteuererklärung).",
"linkText": "Zobacz więcej",
"linkUrl": null
},
{
"id": "6aac6aa8a53b4a5a4f6600fb",
"title": "Kontrola decyzji",
"description": "Szczegółowa weryfikacja decyzji podatkowej (Steuerbescheid) z urzędu skarbowego.",
"linkText": "Zobacz więcej",
"linkUrl": null
},
{
"id": "6aac6aa8a53b4a5a4f6600fc",
"title": "Ulgi i dopłaty",
"description": "Pomoc w uzyskaniu Kindergeld, Riester-Rente oraz odliczaniu kosztów dojazdów i podwójnego gospodarstwa.",
"linkText": "Zobacz więcej",
"linkUrl": null
}
],
"blockType": "servicesList"
},
{
"id": "6aac6aa8a53b4a5a4f660101",
"eyebrow": null,
"title": "Jak to działa",
"subtitle": "Prosty i przejrzysty proces rozliczenia w 3 krokach — bez zbędnych formalności.",
"ctaText": "Zobacz cały proces",
"ctaUrl": "/pl/jak-sie-umowic",
"image": {
"id": 15,
"alt": "Obraz",
"updatedAt": "2026-09-17T22:35:53.848Z",
"createdAt": "2026-09-17T22:35:53.848Z",
"url": "/api/media/file/ac4e2098-0b88-4f6f-98ed-72461a36784c-3.webp",
"thumbnailURL": null,
"filename": "ac4e2098-0b88-4f6f-98ed-72461a36784c-3.webp",
"mimeType": "image/webp",
"filesize": 55450,
"width": 1536,
"height": 1024,
"focalX": 50,
"focalY": 50
},
"blockName": null,
"steps": [
{
"id": "6aac6aa8a53b4a5a4f6600fe",
"title": "Napisz na WhatsApp",
"description": "Wyślij krótką wiadomość lub zadzwoń, by rozpocząć proces."
},
{
"id": "6aac6aa8a53b4a5a4f6600ff",
"title": "Przekaż dokumenty",
"description": "Prześlij nam wymagane dokumenty bezpiecznie online lub dostarcz osobiście."
},
{
"id": "6aac6aa8a53b4a5a4f660100",
"title": "Odbierz zwrot",
"description": "Zajmiemy się resztą i poinformujemy Cię o spodziewanym zwrocie z urzędu skarbowego."
}
],
"blockType": "process"
},
{
"id": "6aac6aa8a53b4a5a4f660102",
"eyebrow": "Zaufanie",
"title": "Zaufanie i bezpieczeństwo",
"description": "Działamy w ramach b.b.h. Lohnsteuerhilfeverein e.V. (Beratungsstelle). Oferujemy profesjonalną pomoc w sprawach podatku dochodowego w ramach uprawnień wynikających z §4 Nr. 11 StBerG.",
"logo": null,
"linkText": "Poznaj stowarzyszenie",
"linkUrl": "/pl/o-nas",
"blockName": null,
"trustPoints": [],
"blockType": "affiliation"
},
{
"id": "6aac6aa8a53b4a5a4f660106",
"title": "Opinie klientów",
"blockName": null,
"testimonials": [
{
"id": "6aac6aa8a53b4a5a4f660103",
"quote": "Pan Janusz pomógł mi odzyskać podatek za 3 ostatnie lata. Wszystko jasno wytłumaczone po polsku.",
"author": "Piotr, Monachium",
"source": null
},
{
"id": "6aac6aa8a53b4a5a4f660104",
"quote": "Szybki kontakt przez WhatsApp i pełne wsparcie przy odwołaniu od decyzji Finanzamt. Polecam!",
"author": "Anna, Berlin",
"source": null
},
{
"id": "6aac6aa8a53b4a5a4f660105",
"quote": "Ogromna wiedza o ulgach dla pracowników budowlanych. Zwrot był znacznie wyższy niż się spodziewałem.",
"author": "Krzysztof, Dortmund",
"source": null
}
],
"blockType": "testimonials"
},
{
"id": "6aac6aa8a53b4a5a4f66010f",
"title": "Częste pytania",
"ctaText": null,
"ctaUrl": null,
"blockName": null,
"questions": [
{
"id": "6aac6aa8a53b4a5a4f660107",
"category": null,
"question": "Czy muszę przyjeżdżać osobiście do biura?",
"answer": "Nie, wszystkie formalności możemy załatwić w 100% zdalnie. Dokumenty przesyłasz online przez bezpieczne kanały, a konsultacje prowadzimy telefonicznie lub przez WhatsApp."
},
{
"id": "6aac6aa8a53b4a5a4f660108",
"category": null,
"question": "Do kiedy muszę złożyć deklarację podatkową w Niemczech?",
"answer": "Standardowy termin to 31 sierpnia kolejnego roku, jednak korzystając z usług doradcy podatkowego, zyskujesz dodatkowy czas – nawet do końca lutego lub maja następnego roku (w zależności od ustaleń na dany rok)."
},
{
"id": "6aac6aa8a53b4a5a4f660109",
"category": null,
"question": "Jak długo czeka się na zwrot podatku z urzędu skarbowego?",
"answer": "Czas oczekiwania na decyzję i przelew zależy od urzędu skarbowego – najczęściej wynosi on od 3 do 8 tygodni po złożeniu kompletnej deklaracji."
},
{
"id": "6aac6aa8a53b4a5a4f66010a",
"category": null,
"question": "Czy załatwiacie również zasiłek Kindergeld?",
"answer": "Tak, pomagamy w formalnościach związanych z pozyskaniem zasiłku rodzinnego Kindergeld dla pracowników zatrudnionych na terenie Niemiec."
},
{
"id": "6aac6aa8a53b4a5a4f66010b",
"category": null,
"question": "Jakie dokumenty są potrzebne do rozliczenia?",
"answer": "Do podstawowego rozliczenia potrzebujesz Lohnsteuerbescheinigung (karty podatkowej od pracodawcy), zaświadczenia o dochodach w Polsce (EU/EWR) oraz dokumentów potwierdzających koszty (np. dojazdy, podwójne gospodarstwo)."
},
{
"id": "6aac6aa8a53b4a5a4f66010c",
"category": null,
"question": "Czy muszę przychodzić osobiście do biura?",
"answer": "Nie, cały proces możemy przeprowadzić w 100% online. Dokumenty możesz przesłać nam bezpiecznie przez WhatsApp, e-mail lub pocztą tradycyjną."
},
{
"id": "6aac6aa8a53b4a5a4f66010d",
"category": null,
"question": "Ile kosztuje rozliczenie podatku z Niemiec?",
"answer": "Koszt rozliczenia ustalamy indywidualnie, bazując na stopniu skomplikowania sprawy i rodzaju deklarowanych dochodów. Zawsze informujemy o cenie przed rozpoczęciem prac."
},
{
"id": "6aac6aa8a53b4a5a4f66010e",
"category": null,
"question": "Czy pomagacie przy wnioskach o Kindergeld?",
"answer": "Tak, pomagamy w formalnościach związanych z pozyskaniem zasiłku rodzinnego Kindergeld dla pracowników zatrudnionych na terenie Niemiec."
}
],
"blockType": "faqTeaser"
},
{
"id": "6aac6aa8a53b4a5a4f660110",
"eyebrow": "Kontakt",
"title": "Gotowy na wyższy zwrot podatku?",
"subtitle": "Napisz do nas już teraz. Odpowiemy najszybciej jak to możliwe.",
"buttonText": "Rozpocznij na WhatsApp",
"blockName": null,
"blockType": "finalCta"
}
]
+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"
}
]
+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 forms = await payload.find({ collection: 'forms' })
console.log("FORMS:", forms.docs.map(f => ({ id: f.id, title: f.title })))
process.exit(0)
}
run().catch(console.error)
+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()
+473
View File
@@ -0,0 +1,473 @@
[
{
"slug": "uslugi",
"layout": [
{
"id": "6aac6b4ada77e55afbdd567a",
"title": "Kompleksowe rozliczenia podatkowe dla Polaków w Niemczech",
"subtitle": "Profesjonalne sporządzanie deklaracji, weryfikacja decyzji podatkowych i bieżące wsparcie w kontaktach z niemieckim urzędem skarbowym.",
"image": {
"id": 10,
"alt": "Obraz",
"updatedAt": "2026-09-17T22:35:52.987Z",
"createdAt": "2026-09-17T22:35:52.987Z",
"url": "/api/media/file/38670a24-644b-42db-ac36-fd77f78352ba-3.webp",
"thumbnailURL": null,
"filename": "38670a24-644b-42db-ac36-fd77f78352ba-3.webp",
"mimeType": "image/webp",
"filesize": 74328,
"width": 1536,
"height": 1024,
"focalX": 50,
"focalY": 50
},
"blockName": null,
"blockType": "pageHeader"
},
{
"id": "6aac6b4ada77e55afbdd5682",
"title": "Czym się zajmujemy",
"subtitle": "Zapewniamy rzetelne i kompleksowe wsparcie na każdym etapie rozliczenia podatku dochodowego w Niemczech.",
"blockName": null,
"services": [
{
"id": "6aac6b4ada77e55afbdd567b",
"title": "Sporządzenie deklaracji podatkowej (Einkommensteuererklärung)",
"anchor": "sporzadzenie-deklaracji-podatkowej-einkommensteuererklarung",
"description": "Przygotowujemy kompletne zeznanie podatkowe, uwzględniając wszystkie przysługujące Ci ulgi, koszty uzyskania przychodu oraz odliczenia. Dbamy o maksymalizację Twojego zwrotu i pełną zgodność z niemieckim prawem.",
"icon": "fileText"
},
{
"id": "6aac6b4ada77e55afbdd567c",
"title": "Wstępne obliczenie przewidywanego zwrotu",
"anchor": "wstepne-obliczenie-przewidywanego-zwrotu",
"description": "Przed wysłaniem dokumentów do urzędu wyliczamy dokładną kwotę spodziewanego zwrotu lub ewentualnej dopłaty. Dzięki temu wiesz dokładnie, czego się spodziewać jeszcze przed wydaniem decyzji przez urzędu skarbowego.",
"icon": "calculator"
},
{
"id": "6aac6b4ada77e55afbdd567d",
"title": "Kontrola decyzji podatkowej (Steuerbescheid)",
"anchor": "kontrola-decyzji-podatkowej-steuerbescheid",
"description": "Szczegółowo weryfikujemy otrzymaną decyzję z urzędu skarbowego pod kątem ewentualnych błędów urzędników. Sprawdzamy, czy urząd uwzględnił wszystkie zgłoszone koszty i ulgi.",
"icon": "shieldCheck"
},
{
"id": "6aac6b4ada77e55afbdd567e",
"title": "Składanie odwołań (Einspruch)",
"anchor": "skladanie-odwolan-einspruch",
"description": "W przypadku niekorzystnej lub błędnej decyzji urzędu sporządzamy oficjalne odwołanie i reprezentujemy Cię w postępowaniu wyjaśniającym, skutecznie dochodząc Twoich praw.",
"icon": "scale"
},
{
"id": "6aac6b4ada77e55afbdd567f",
"title": "Całoroczne doradztwo podatkowe",
"anchor": "caloroczne-doradztwo-podatkowe",
"description": "Pozostajemy do Twojej dyspozycji przez cały rok podatkowy. Odpowiadamy na pytania, pomagamy w bieżącej korespondencji z urzędem i doradzamy przy zmianie sytuacji życiowej.",
"icon": "helpCircle"
},
{
"id": "6aac6b4ada77e55afbdd5680",
"title": "Doradztwo przy wyborze klasy podatkowej (Steuerklasse)",
"anchor": "doradztwo-przy-wyborze-klasy-podatkowej-steuerklasse",
"description": "Doradzamy optymalny dobór klas podatkowych dla małżeństw i osób pracujących w Niemczech. Pomagamy wybrać wariant (np. III/V lub IV/IV), który przyniesie największe korzyści finansowe.",
"icon": "users"
},
{
"id": "6aac6b4ada77e55afbdd5681",
"title": "Ulgi, dopłaty i odliczenia (Kindergeld, dojazdy, podwójne gospodarstwo)",
"anchor": "ulgi-doplaty-i-odliczenia-kindergeld-dojazdy-podwojne-gospodarstwo",
"description": "Pomagamy w uzyskaniu Kindergeld, Riester-Rente oraz prawidłowym odliczeniu kosztów podwójnego gospodarstwa domowego (doppelter Haushalt) i dojazdów do pracy (Pendlerpauschale).",
"icon": "coins"
}
],
"blockType": "servicesDetailed"
},
{
"id": "6aac6b4ada77e55afbdd568c",
"title": "Dla kogo — zakres uprawnień doradczych wg §4 Nr. 11 StBerG",
"subtitle": "Jako punkt doradczy b.b.h. Lohnsteuerhilfeverein e.V. działamy ściśle w ramach ustawowych uprawnień dla stowarzyszeń pomocy podatkowej.",
"eligibleTitle": "Kto może skorzystać z naszej pomocy",
"limitationsTitle": "Dopuszczalne przychody dodatkowe (najem, kapitał, inne)",
"limitationsText": "Osoby uzyskujące dodatkowe przychody (np. z najmu i dzierżawy, kapitałów pieniężnych, innych źródeł) — doradztwo jest możliwe wyłącznie wtedy, gdy łączne przychody z tych źródeł nie przekraczają 18 000 € (dla osób samotnych) lub 36 000 € (dla małżeństw rozliczających się wspólnie) w roku podatkowym.",
"excludedTitle": "Kogo NIE obejmują nasze uprawnienia (granica ustawowa)",
"legalNotice": "Pomoc podatkowa jest świadczona wyłącznie na rzecz członków stowarzyszenia b.b.h. Lohnsteuerhilfeverein e.V. zgodnie z § 4 Nr. 11 Steuerberatungsgesetz (StBerG).",
"blockName": null,
"eligibleItems": [
{
"id": "6aac6b4ada77e55afbdd5683",
"text": "Pracownicy zatrudnieni na umowę o pracę (Arbeitnehmer) oraz pracownicy tymczasowi"
},
{
"id": "6aac6b4ada77e55afbdd5684",
"text": "Urzędnicy państwowi (Beamte)"
},
{
"id": "6aac6b4ada77e55afbdd5685",
"text": "Emeryci i renciści (Rentner und Pensionäre)"
},
{
"id": "6aac6b4ada77e55afbdd5686",
"text": "Osoby bezrobotne oraz pobierające zasiłki socjalne"
},
{
"id": "6aac6b4ada77e55afbdd5687",
"text": "Odbiorcy świadczeń rodzinnych (Kindergeld, Elterngeld) oraz alimentów"
}
],
"excludedItems": [
{
"id": "6aac6b4ada77e55afbdd5688",
"text": "Osoby prowadzące jednoosobową działalność gospodarczą (Gewerbe)"
},
{
"id": "6aac6b4ada77e55afbdd5689",
"text": "Przedsiębiorcy, spółki oraz osoby samozatrudnione (Selbständige)"
},
{
"id": "6aac6b4ada77e55afbdd568a",
"text": "Osoby uzyskujące przychody z działalności rolniczej i leśnej (Land- und Forstwirtschaft)"
},
{
"id": "6aac6b4ada77e55afbdd568b",
"text": "Osoby z przychodami z najmu lub kapitału przekraczającymi limity 18 000 € / 36 000 €"
}
],
"blockType": "eligibility"
},
{
"id": "6aac6b4ada77e55afbdd568d",
"eyebrow": "Kontakt",
"title": "Masz pytania dotyczące Twojego rozliczenia?",
"subtitle": "Skontaktuj się bezpośrednio przez WhatsApp. Przeanalizujemy Twoją sytuację i pomożemy uzyskać należny zwrot.",
"buttonText": "Napisz na WhatsApp",
"image": {
"id": 10,
"alt": "Obraz",
"updatedAt": "2026-09-17T22:35:52.987Z",
"createdAt": "2026-09-17T22:35:52.987Z",
"url": "/api/media/file/38670a24-644b-42db-ac36-fd77f78352ba-3.webp",
"thumbnailURL": null,
"filename": "38670a24-644b-42db-ac36-fd77f78352ba-3.webp",
"mimeType": "image/webp",
"filesize": 74328,
"width": 1536,
"height": 1024,
"focalX": 50,
"focalY": 50
},
"blockName": null,
"blockType": "finalCta"
}
]
},
{
"slug": "jak-sie-umowic",
"layout": [
{
"id": "6aac6afbef4df35aa15f8d03",
"title": "Jak się umówić",
"subtitle": "Prosty, przejrzysty i w 100% zdalny proces rozliczenia podatkowego. Wszystko załatwisz bez wychodzenia z domu.",
"image": {
"id": 9,
"alt": "About section image",
"updatedAt": "2026-09-17T22:33:12.549Z",
"createdAt": "2026-09-17T22:33:12.549Z",
"url": "/api/media/file/about-img.webp",
"thumbnailURL": null,
"filename": "about-img.webp",
"mimeType": "image/webp",
"filesize": 72208,
"width": 1536,
"height": 1024,
"focalX": 50,
"focalY": 50
},
"blockName": null,
"blockType": "pageHeader"
},
{
"id": "6aac6afbef4df35aa15f8d07",
"eyebrow": null,
"title": "Rozliczenie w 3 prostych krokach",
"subtitle": "Zapewniamy pełne wsparcie i jasną komunikację na każdym etapie.",
"ctaText": null,
"ctaUrl": null,
"image": {
"id": 8,
"alt": "Hero background",
"updatedAt": "2026-09-17T22:33:12.415Z",
"createdAt": "2026-09-17T22:33:12.414Z",
"url": "/api/media/file/hero-bg.webp",
"thumbnailURL": null,
"filename": "hero-bg.webp",
"mimeType": "image/webp",
"filesize": 74328,
"width": 1536,
"height": 1024,
"focalX": 50,
"focalY": 50
},
"blockName": null,
"steps": [
{
"id": "6aac6afbef4df35aa15f8d04",
"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."
},
{
"id": "6aac6afbef4df35aa15f8d05",
"title": "Przekaż dokumenty",
"description": "Prześlij zaświadczenia o dochodach (Lohnsteuerbescheinigung, EU/EWR), podatkowy numer identyfikacyjny (IdNr) lub PESEL oraz ewentualne potwierdzenia kosztów."
},
{
"id": "6aac6afbef4df35aa15f8d06",
"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."
}
],
"blockType": "process"
},
{
"id": "6aac6afbef4df35aa15f8d08",
"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",
"blockName": null,
"blockType": "contactInfo"
},
{
"id": "6aac6afbef4df35aa15f8d09",
"eyebrow": null,
"title": "Chcesz szybko odzyskać podatek?",
"subtitle": "Nie zwlekaj z rozliczeniem. Napisz teraz, a zajmiemy się formalnościami od początku do końca.",
"buttonText": "Napisz na WhatsApp",
"image": {
"id": 9,
"alt": "About section image",
"updatedAt": "2026-09-17T22:33:12.549Z",
"createdAt": "2026-09-17T22:33:12.549Z",
"url": "/api/media/file/about-img.webp",
"thumbnailURL": null,
"filename": "about-img.webp",
"mimeType": "image/webp",
"filesize": 72208,
"width": 1536,
"height": 1024,
"focalX": 50,
"focalY": 50
},
"blockName": null,
"blockType": "finalCta"
}
]
},
{
"slug": "o-nas",
"layout": [
{
"id": "6aac6afbef4df35aa15f8ce2",
"title": "O nas",
"subtitle": "Ponad 20 lat rzetelnego wsparcia podatkowego dla Polaków pracujących w Niemczech.",
"image": {
"id": 7,
"alt": "Obraz",
"updatedAt": "2026-09-17T22:31:48.622Z",
"createdAt": "2026-09-17T22:31:48.622Z",
"url": "/api/media/file/d0c63f69-9a73-48ba-a03b-15dafd55b5cf-2.webp",
"thumbnailURL": null,
"filename": "d0c63f69-9a73-48ba-a03b-15dafd55b5cf-2.webp",
"mimeType": "image/webp",
"filesize": 69160,
"width": 1536,
"height": 1024,
"focalX": 50,
"focalY": 50
},
"blockName": null,
"blockType": "pageHeader"
},
{
"id": "6aac6afbef4df35aa15f8ce9",
"title": "Janusz Kędzierski",
"image": {
"id": 6,
"alt": "Obraz",
"updatedAt": "2026-09-17T22:31:48.453Z",
"createdAt": "2026-09-17T22:31:48.453Z",
"url": "/api/media/file/ac4e2098-0b88-4f6f-98ed-72461a36784c-2.webp",
"thumbnailURL": null,
"filename": "ac4e2098-0b88-4f6f-98ed-72461a36784c-2.webp",
"mimeType": "image/webp",
"filesize": 55450,
"width": 1536,
"height": 1024,
"focalX": 50,
"focalY": 50
},
"blockName": null,
"paragraphs": [
{
"id": "6aac6afbef4df35aa15f8ce3",
"text": "Od ponad dwóch dekad pomagam polskim pracownikom i specjalistom w Niemczech w bezpiecznym, bezstresowym i w pełni legalnym rozliczaniu podatków dochodowych przed urzędem skarbowym."
},
{
"id": "6aac6afbef4df35aa15f8ce4",
"text": "Moim celem jest zdjęcie z Ciebie ciężaru skomplikowanych niemieckich przepisów skarbowych. Cały proces prowadzimy w języku polskim — od wstępnej analizy dokumentów, przez precyzyjne wyliczenia ulg i kosztów uzyskania przychodu, aż po weryfikację końcowej decyzji podatkowej."
}
],
"credentials": [
{
"id": "6aac6afbef4df35aa15f8ce5",
"text": "Ponad 20 lat praktyki w niemieckim prawie podatkowym"
},
{
"id": "6aac6afbef4df35aa15f8ce6",
"text": "Beratungsstellenleiter w b.b.h. Lohnsteuerhilfeverein e.V."
},
{
"id": "6aac6afbef4df35aa15f8ce7",
"text": "Pełna obsługa w języku polskim i niemieckim"
},
{
"id": "6aac6afbef4df35aa15f8ce8",
"text": "Maksymalizacja zwrotu podatku i zero stresu formalnego"
}
],
"blockType": "bio"
},
{
"id": "6aac6afbef4df35aa15f8ceb",
"title": "Nasz zespół",
"subtitle": "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.",
"blockName": null,
"members": [
{
"id": "6aac6afbef4df35aa15f8cea",
"name": "Janusz Kędzierski",
"role": "Właściciel / Beratungsstellenleiter",
"description": "Wieloletni praktyk prawa podatkowego ze specjalizacją w doradztwie dla polskich pracowników w Niemczech.",
"photo": null,
"bookingUrl": "/kontakt"
}
],
"blockType": "team"
},
{
"id": "6aac6afbef4df35aa15f8cec",
"eyebrow": null,
"title": "Gotowy na rozliczenie podatku?",
"subtitle": "Skontaktuj się z nami już dziś. Przeanalizujemy Twoją sytuację i pomożemy uzyskać maksymalny zwrot z niemieckiego urzędu skarbowego.",
"buttonText": "Napisz na WhatsApp",
"image": {
"id": 8,
"alt": "Hero background",
"updatedAt": "2026-09-17T22:33:12.415Z",
"createdAt": "2026-09-17T22:33:12.414Z",
"url": "/api/media/file/hero-bg.webp",
"thumbnailURL": null,
"filename": "hero-bg.webp",
"mimeType": "image/webp",
"filesize": 74328,
"width": 1536,
"height": 1024,
"focalX": 50,
"focalY": 50
},
"blockName": null,
"blockType": "finalCta"
}
]
},
{
"slug": "faq",
"layout": [
{
"id": "6aac6afbef4df35aa15f8cdf",
"text": "Treść w przygotowaniu",
"blockName": null,
"blockType": "TextSection"
}
]
},
{
"slug": "kontakt",
"layout": [
{
"id": "6aac6b4ada77e55afbdd56a0",
"title": "Kontakt",
"subtitle": "Masz pytania dotyczące rozliczenia podatkowego w Niemczech? Skontaktuj się z nami — chętnie pomożemy.",
"image": {
"id": 5,
"alt": "Obraz",
"updatedAt": "2026-09-17T22:31:48.145Z",
"createdAt": "2026-09-17T22:31:48.145Z",
"url": "/api/media/file/9a2983cc-482c-4ff7-9f1d-31679e4dfc56-2.webp",
"thumbnailURL": null,
"filename": "9a2983cc-482c-4ff7-9f1d-31679e4dfc56-2.webp",
"mimeType": "image/webp",
"filesize": 70318,
"width": 1672,
"height": 941,
"focalX": 50,
"focalY": 50
},
"blockName": null,
"blockType": "pageHeader"
},
{
"id": "6aac6b4ada77e55afbdd56a1",
"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",
"blockName": null,
"blockType": "contactInfo"
}
]
},
{
"slug": "stowarzyszenie",
"layout": [
{
"id": "6aac6afbef4df35aa15f8cd6",
"text": "Treść w przygotowaniu",
"blockName": null,
"blockType": "TextSection"
}
]
},
{
"slug": "polityka-prywatnosci",
"layout": [
{
"id": "6aac6afbef4df35aa15f8cd3",
"text": "Treść w przygotowaniu",
"blockName": null,
"blockType": "TextSection"
}
]
},
{
"slug": "polityka-cookies",
"layout": [
{
"id": "6aac6afbef4df35aa15f8cd0",
"text": "Treść w przygotowaniu",
"blockName": null,
"blockType": "TextSection"
}
]
},
{
"slug": "impressum",
"layout": [
{
"id": "6aac6afbef4df35aa15f8ccd",
"text": "Treść w przygotowaniu",
"blockName": null,
"blockType": "TextSection"
}
]
}
]
-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()
+77
View File
@@ -0,0 +1,77 @@
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 snapshotPath = path.join(process.cwd(), 'scripts', 'content-snapshot.json')
const snapshot = JSON.parse(fs.readFileSync(snapshotPath, 'utf-8'))
const kontaktSnap = snapshot.pages.find((p: any) => p.pl.slug === 'kontakt')
if (!kontaktSnap) {
console.error("No kontakt in snapshot")
return
}
// Find actual page ID
const dbPages = await payload.find({ collection: 'pages', where: { slug: { equals: 'kontakt' } } })
const dbPage = dbPages.docs[0]
if (!dbPage) {
console.error("No kontakt in DB")
return
}
for (const loc of ['pl', 'de', 'en']) {
let layout = kontaktSnap[loc]?.layout
if (!layout && loc === 'en') {
// fallback to PL for layout structure
layout = JSON.parse(JSON.stringify(kontaktSnap.pl.layout))
}
if (!layout) continue;
const newLayout = layout.map((block: any) => {
const b = { ...block }
delete b.id // VERY IMPORTANT to avoid SQLite unique constraint errors or table mismatches
// If we find contactInfo, UPGRADE it to contactSection and attach form 1!
if (b.blockType === 'contactInfo' || b.blockType === 'contactSection') {
b.blockType = 'contactSection'
b.form = 1 // We created form 1 earlier
}
// Fix EN translations if this is EN
if (loc === 'en') {
if (b.blockType === 'pageHeader') {
b.title = "Contact"
b.subtitle = "Do you have questions about tax settlement in Germany? Contact us — we will be happy to help."
}
if (b.blockType === 'contactSection') {
b.address = "TO BE CONFIRMED BY CLIENT"
b.hours = "Mon-Fri: 9:00 - 17:00\nBy appointment"
}
}
return b
})
const title = loc === 'en' ? 'Contact' : (loc === 'de' ? 'Kontakt' : 'Kontakt')
await payload.update({
collection: 'pages',
id: dbPage.id,
locale: loc as any,
data: {
title,
layout: newLayout
}
})
console.log(`Restored kontakt in ${loc}`)
}
process.exit(0)
}
run().catch(console.error)
+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)
+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 docs = await payload.find({
collection: 'pages',
where: { slug: { equals: 'uslugi' } }, // wait, slug in DE might be different! Let's query by ID 2
locale: 'de'
})
const doc = docs.docs[0]
const cta = doc.layout?.find(b => b.blockType === 'finalCta') as any
console.log("USLUGI DE CTA IMAGE:", typeof cta?.image, cta?.image?.id || cta?.image)
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)
+75
View File
@@ -0,0 +1,75 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
async function run() {
const payload = await getPayload({ config: configPromise })
// 1. UPDATE GLOBALS
await payload.updateGlobal({
slug: 'footer',
locale: 'en',
data: {
copyright: '© 2024 Accounting Kędzierski. All rights reserved.',
association: {
title: 'Member of b.b.h. Lohnsteuerhilfeverein e.V.'
},
creator: {
label: 'Created by'
}
}
})
console.log("Footer updated")
// 2. UPDATE PAGES (Titles and basic fields)
const translations: Record<string, any> = {
'home': { title: 'Home' },
'uslugi': { title: 'Services' },
'jak-sie-umowic': { title: 'How to make an appointment' },
'o-nas': { title: 'About Us' },
'faq': { title: 'FAQ' },
'kontakt': { title: 'Contact' },
'stowarzyszenie': { title: 'Association' },
'polityka-prywatnosci': { title: 'Privacy Policy' },
'polityka-cookies': { title: 'Cookie Policy' },
'impressum': { title: 'Imprint' }
}
const pages = await payload.find({ collection: 'pages', limit: 100, locale: 'pl' })
for (const page of pages.docs) {
const slug = page.slug || ''
const enData = translations[slug] || { title: page.title }
// For Home, let's keep the hero translations if it's there
if (slug === 'home') {
enData.layout = page.layout?.map(block => {
if (block.blockType === 'hero') {
return {
...block,
title: 'Your Trusted Accounting Office in Germany',
titleAccent: 'We speak English and Polish',
subtitle: 'Professional bookkeeping, tax settlements, and business consulting for companies in Germany.',
primaryCta: 'Contact Us',
secondaryCta: { text: 'Our Services', url: '/uslugi' }
}
}
return block
})
}
try {
await payload.update({
collection: 'pages',
id: page.id,
locale: 'en',
data: enData
})
console.log(`Page updated: ${slug}`)
} catch (e) {
console.error(`Error updating page ${slug}`, e)
}
}
console.log("Done")
process.exit(0)
}
run().catch(console.error)
+271
View File
@@ -0,0 +1,271 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
const translations: Record<string, Record<string, any>> = {
'uslugi': {
en: {
pageHeader: { title: "Comprehensive tax settlements in Germany", subtitle: "Professional tax return preparation, tax assessment verification, and ongoing support." },
servicesDetailed: {
title: "What we do",
subtitle: "We provide reliable and comprehensive support at every stage of income tax settlement in Germany.",
services: [
{ title: "Tax return preparation (Einkommensteuererklärung)", description: "We prepare a complete tax return, taking into account all applicable allowances, deductible expenses, and deductions. We ensure maximum refund and full compliance with German law." },
{ title: "Preliminary calculation of expected refund", description: "Before sending the documents to the tax office, we calculate the exact amount of the expected refund or potential surcharge. You know exactly what to expect." },
{ title: "Tax assessment verification (Steuerbescheid)", description: "We thoroughly verify the received decision from the tax office for any errors by officials. We check if the office has included all reported costs and allowances." },
{ title: "Filing appeals (Einspruch)", description: "In the event of an unfavorable or incorrect decision from the office, we prepare an official appeal and represent you in the clarification proceedings." },
{ title: "Year-round tax consulting", description: "We remain at your disposal throughout the tax year. We answer questions, help in ongoing correspondence with the office and advise when life situations change." },
{ title: "Advice on choosing a tax class (Steuerklasse)", description: "We advise on the optimal selection of tax classes for marriages and people working in Germany. We help to choose the variant (e.g. III/V or IV/IV) that will bring the greatest financial benefits." },
{ title: "Allowances, subsidies and deductions", description: "We help in obtaining Kindergeld, Riester-Rente and correct deduction of the costs of maintaining a double household (doppelter Haushalt) and commuting to work (Pendlerpauschale)." }
]
},
eligibility: {
title: "For whom — scope of advisory powers under §4 No. 11 StBerG",
subtitle: "As an advisory point of b.b.h. Lohnsteuerhilfeverein e.V. we operate strictly within statutory limits for tax assistance associations.",
eligibleTitle: "Who can benefit from our help",
limitationsTitle: "Permissible additional income (rental, capital, other)",
limitationsText: "Persons earning additional income — advice is possible only if the total income from these sources does not exceed €18,000 (singles) or €36,000 (married couples).",
excludedTitle: "Who is NOT covered by our services (statutory limit)",
legalNotice: "Tax assistance is provided only to members of the b.b.h. Lohnsteuerhilfeverein e.V. according to § 4 No. 11 Steuerberatungsgesetz (StBerG).",
eligibleItems: [
{ text: "Employees (Arbeitnehmer) and temporary workers" },
{ text: "Civil servants (Beamte)" },
{ text: "Pensioners and retirees (Rentner und Pensionäre)" },
{ text: "Unemployed persons and social benefit recipients" },
{ text: "Recipients of family benefits (Kindergeld, Elterngeld) and alimony" }
],
excludedItems: [
{ text: "Sole traders (Gewerbe)" },
{ text: "Entrepreneurs, partnerships and self-employed (Selbständige)" },
{ text: "Persons generating income from agriculture and forestry (Land- und Forstwirtschaft)" },
{ text: "Persons with rental or capital income exceeding limits" }
]
}
},
de: {
pageHeader: { title: "Umfassende Steuererklärungen in Deutschland", subtitle: "Professionelle Erstellung, Prüfung von Steuerbescheiden und laufende Unterstützung." },
servicesDetailed: {
title: "Was wir tun",
subtitle: "Wir bieten zuverlässige und umfassende Unterstützung in jeder Phase der Einkommensteuererklärung in Deutschland.",
services: [
{ title: "Erstellung der Einkommensteuererklärung", description: "Wir erstellen eine vollständige Steuererklärung unter Berücksichtigung aller zustehenden Freibeträge, Werbungskosten und Abzüge. Wir sorgen für maximale Rückerstattung und volle Übereinstimmung mit deutschem Recht." },
{ title: "Vorläufige Berechnung der erwarteten Rückerstattung", description: "Bevor wir die Dokumente an das Finanzamt senden, berechnen wir die genaue Höhe der erwarteten Rückerstattung oder einer möglichen Nachzahlung." },
{ title: "Prüfung des Steuerbescheids", description: "Wir prüfen den erhaltenen Bescheid des Finanzamtes eingehend auf Fehler der Sachbearbeiter. Wir prüfen, ob das Amt alle gemeldeten Kosten und Freibeträge berücksichtigt hat." },
{ title: "Einspruchsverfahren", description: "Im Falle eines unvorteilhaften oder falschen Bescheids des Amtes erstellen wir einen offiziellen Einspruch und vertreten Sie im Klärungsverfahren." },
{ title: "Ganzjährige Steuerberatung", description: "Wir stehen Ihnen während des gesamten Steuerjahres zur Verfügung. Wir beantworten Fragen, helfen bei der laufenden Korrespondenz mit dem Amt und beraten bei Veränderungen der Lebenssituation." },
{ title: "Beratung bei der Wahl der Steuerklasse", description: "Wir beraten bei der optimalen Wahl der Steuerklassen für Verheiratete und Arbeitnehmer in Deutschland. Wir helfen bei der Wahl der Variante (z. B. III/V oder IV/IV)." },
{ title: "Freibeträge, Zuschüsse und Abzüge", description: "Wir helfen bei der Erlangung von Kindergeld, Riester-Rente und dem korrekten Abzug der Kosten für eine doppelte Haushaltsführung und Fahrtkosten zur Arbeit (Pendlerpauschale)." }
]
},
eligibility: {
title: "Für wen — Beratungsumfang nach §4 Nr. 11 StBerG",
subtitle: "Als Beratungsstelle des b.b.h. Lohnsteuerhilfeverein e.V. arbeiten wir streng im Rahmen der gesetzlichen Befugnisse.",
eligibleTitle: "Wer unsere Hilfe in Anspruch nehmen kann",
limitationsTitle: "Zulässige Zusatzeinnahmen (Vermietung, Kapital, Sonstige)",
limitationsText: "Personen mit Zusatzeinkünften — eine Beratung ist nur möglich, wenn die Gesamteinnahmen aus diesen Quellen 18.000 € (Alleinstehende) oder 36.000 € (Verheiratete) nicht überschreiten.",
excludedTitle: "Wer NICHT unter unsere Leistungen fällt",
legalNotice: "Die Hilfeleistung in Steuersachen erfolgt ausschließlich für Mitglieder des b.b.h. Lohnsteuerhilfeverein e.V. gem. § 4 Nr. 11 StBerG.",
eligibleItems: [
{ text: "Arbeitnehmer und Leiharbeiter" },
{ text: "Beamte" },
{ text: "Rentner und Pensionäre" },
{ text: "Arbeitslose und Bezieher von Sozialleistungen" },
{ text: "Bezieher von Familienleistungen (Kindergeld, Elterngeld) und Unterhalt" }
],
excludedItems: [
{ text: "Gewerbetreibende" },
{ text: "Unternehmer, Gesellschaften und Selbständige" },
{ text: "Personen mit Einkünften aus Land- und Forstwirtschaft" },
{ text: "Personen mit Miet- oder Kapitaleinkünften, die die Grenzen überschreiten" }
]
}
}
},
'jak-sie-umowic': {
en: {
pageHeader: { title: "How to make an appointment", subtitle: "Simple, transparent, and 100% remote tax settlement process. You can handle everything without leaving your home." },
process: {
title: "Settlement in 3 simple steps",
subtitle: "We provide full support and clear communication at every stage.",
steps: [
{ title: "Message us on WhatsApp", description: "Contact us directly via WhatsApp or the form below to start and discuss the details of your tax situation." },
{ title: "Hand over documents", description: "Send your income certificates (Lohnsteuerbescheinigung, EU/EWR), tax identification number (IdNr), and potential expense confirmations." },
{ title: "Receive ready settlement for signature", description: "We prepare the complete declaration, calculate the expected refund, and send the ready document for your approval and signature." }
]
},
contactInfo: { hours: "Mon-Fri: 9:00 - 17:00\nBy appointment" }
},
de: {
pageHeader: { title: "Termin vereinbaren", subtitle: "Einfacher, transparenter und 100% digitaler Prozess der Steuererklärung. Alles von zu Hause aus." },
process: {
title: "Steuererklärung in 3 einfachen Schritten",
subtitle: "Wir bieten volle Unterstützung und klare Kommunikation in jeder Phase.",
steps: [
{ title: "Auf WhatsApp schreiben", description: "Kontaktieren Sie uns direkt über WhatsApp oder das Formular unten, um zu beginnen und die Details zu besprechen." },
{ title: "Dokumente übergeben", description: "Senden Sie uns Ihre Lohnsteuerbescheinigungen (ggf. EU/EWR), Steueridentifikationsnummer (IdNr) und mögliche Kostenbelege." },
{ title: "Erklärung zur Unterschrift erhalten", description: "Wir erstellen die komplette Erklärung, berechnen die erwartete Erstattung und senden das fertige Dokument zur Freigabe und Unterschrift." }
]
},
contactInfo: { hours: "Mo-Fr: 9:00 - 17:00\nNach Vereinbarung" }
}
},
'o-nas': {
en: {
pageHeader: { title: "About Us", subtitle: "Over 20 years of reliable tax support for Poles working in Germany." },
bio: {
paragraphs: [
{ text: "For over two decades, I have been helping Polish workers and specialists in Germany to safely, stress-free, and fully legally settle income taxes before the tax office." },
{ text: "My goal is to take the burden of complicated German tax regulations off your shoulders. We conduct the entire process in Polish — from initial document analysis, through precise calculations of allowances, to verification of the final tax decision." }
],
credentials: [
{ text: "Over 20 years of practice in German tax law" },
{ text: "Beratungsstellenleiter at b.b.h. Lohnsteuerhilfeverein e.V." },
{ text: "Full service in Polish and German" },
{ text: "Maximizing tax refunds and zero formal stress" }
]
},
team: {
title: "Our Team",
subtitle: "Meet the experts who are committed to your finances. Each of us brings years of experience to guarantee your peace of mind and safety.",
members: [
{ role: "Owner / Beratungsstellenleiter", description: "Long-time tax law practitioner specializing in consulting for Polish workers in Germany." }
]
}
},
de: {
pageHeader: { title: "Über uns", subtitle: "Über 20 Jahre zuverlässige Steuerunterstützung für Polen, die in Deutschland arbeiten." },
bio: {
paragraphs: [
{ text: "Seit über zwei Jahrzehnten helfe ich polnischen Arbeitnehmern und Fachkräften in Deutschland bei der sicheren, stressfreien und völlig legalen Erledigung der Einkommensteuer gegenüber dem Finanzamt." },
{ text: "Mein Ziel ist es, Ihnen die Last der komplizierten deutschen Steuervorschriften von den Schultern zu nehmen. Wir führen den gesamten Prozess auf Polnisch durch — von der ersten Dokumentenanalyse über präzise Berechnungen von Freibeträgen bis hin zur Überprüfung des finalen Steuerbescheids." }
],
credentials: [
{ text: "Über 20 Jahre Praxis im deutschen Steuerrecht" },
{ text: "Beratungsstellenleiter im b.b.h. Lohnsteuerhilfeverein e.V." },
{ text: "Vollständiger Service in Polnisch und Deutsch" },
{ text: "Maximierung der Steuerrückerstattung und null formaler Stress" }
]
},
team: {
title: "Unser Team",
subtitle: "Lernen Sie die Experten kennen, die sich für Ihre Finanzen einsetzen. Jeder von uns bringt jahrelange Erfahrung mit, um Ihre Ruhe und Sicherheit zu garantieren.",
members: [
{ role: "Inhaber / Beratungsstellenleiter", description: "Langjähriger Steuerrechtspraktiker mit Spezialisierung auf die Beratung polnischer Arbeitnehmer in Deutschland." }
]
}
}
}
}
async function run() {
const payload = await getPayload({ config: configPromise })
const pages = await payload.find({ collection: 'pages', limit: 100, locale: 'pl' })
for (const page of pages.docs) {
if (!translations[page.slug || '']) continue;
for (const loc of ['en', 'de'] as const) {
const dbPage = await payload.findByID({ collection: 'pages', id: page.id, locale: loc })
if (!dbPage || !dbPage.layout) continue;
const tPage = translations[page.slug!][loc];
let modified = false;
const layout = dbPage.layout.map((block: any) => {
// preserve IDs
for (const key of Object.keys(block)) {
if (block[key] && typeof block[key] === 'object' && block[key].id !== undefined) {
block[key] = block[key].id
}
}
if (block.blockType === 'pageHeader' && tPage.pageHeader) {
block.title = tPage.pageHeader.title;
block.subtitle = tPage.pageHeader.subtitle;
modified = true;
}
if (block.blockType === 'servicesDetailed' && tPage.servicesDetailed) {
block.title = tPage.servicesDetailed.title;
block.subtitle = tPage.servicesDetailed.subtitle;
block.services = block.services.map((s: any, i: number) => ({
...s,
title: tPage.servicesDetailed.services[i]?.title || s.title,
description: tPage.servicesDetailed.services[i]?.description || s.description
}))
modified = true;
}
if (block.blockType === 'eligibility' && tPage.eligibility) {
block.title = tPage.eligibility.title;
block.subtitle = tPage.eligibility.subtitle;
block.eligibleTitle = tPage.eligibility.eligibleTitle;
block.limitationsTitle = tPage.eligibility.limitationsTitle;
block.limitationsText = tPage.eligibility.limitationsText;
block.excludedTitle = tPage.eligibility.excludedTitle;
block.legalNotice = tPage.eligibility.legalNotice;
block.eligibleItems = block.eligibleItems.map((s: any, i: number) => ({
...s, text: tPage.eligibility.eligibleItems[i]?.text || s.text
}))
block.excludedItems = block.excludedItems.map((s: any, i: number) => ({
...s, text: tPage.eligibility.excludedItems[i]?.text || s.text
}))
modified = true;
}
if (block.blockType === 'process' && tPage.process) {
block.title = tPage.process.title;
block.subtitle = tPage.process.subtitle;
block.steps = block.steps.map((s: any, i: number) => ({
...s,
title: tPage.process.steps[i]?.title || s.title,
description: tPage.process.steps[i]?.description || s.description
}))
modified = true;
}
if (block.blockType === 'bio' && tPage.bio) {
block.paragraphs = block.paragraphs.map((s: any, i: number) => ({
...s, text: tPage.bio.paragraphs[i]?.text || s.text
}))
block.credentials = block.credentials.map((s: any, i: number) => ({
...s, text: tPage.bio.credentials[i]?.text || s.text
}))
modified = true;
}
if (block.blockType === 'team' && tPage.team) {
block.title = tPage.team.title;
block.subtitle = tPage.team.subtitle;
block.members = block.members.map((s: any, i: number) => ({
...s,
role: tPage.team.members[i]?.role || s.role,
description: tPage.team.members[i]?.description || s.description
}))
modified = true;
}
if (block.blockType === 'contactInfo' && tPage.contactInfo) {
block.hours = tPage.contactInfo.hours;
modified = true;
}
return block;
});
if (modified) {
await payload.update({
collection: 'pages',
id: page.id,
locale: loc,
data: { layout }
})
console.log(`Fully translated ${page.slug} to ${loc}`)
}
}
}
process.exit(0)
}
run().catch(console.error)
+97
View File
@@ -0,0 +1,97 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
async function run() {
const payload = await getPayload({ config: configPromise })
// Translation mapping for FinalCTA to EN
const enCta = {
title: "Have questions about your tax return?",
subtitle: "Contact us directly via WhatsApp. We will analyze your situation and help you get the refund you deserve.",
buttonText: "Message on WhatsApp",
}
// Translation mapping for FinalCTA to DE
const deCta = {
title: "Haben Sie Fragen zu Ihrer Steuererklärung?",
subtitle: "Kontaktieren Sie uns direkt über WhatsApp. Wir analysieren Ihre Situation und helfen Ihnen, die Ihnen zustehende Rückerstattung zu erhalten.",
buttonText: "Auf WhatsApp schreiben",
}
const pages = await payload.find({ collection: 'pages', limit: 100, locale: 'pl' })
for (const page of pages.docs) {
if (page.slug === 'home') continue; // already mostly translated
// EN Update
const enPage = await payload.findByID({ collection: 'pages', id: page.id, locale: 'en' })
if (enPage && enPage.layout) {
let modified = false
const layout = enPage.layout.map((block: any) => {
// Strip populated ids
for (const key of Object.keys(block)) {
if (block[key] && typeof block[key] === 'object' && block[key].id !== undefined) {
block[key] = block[key].id
}
}
if (block.blockType === 'finalCta') {
block.title = enCta.title
block.subtitle = enCta.subtitle
block.buttonText = enCta.buttonText
modified = true
}
if (block.blockType === 'pageHeader') {
if (page.slug === 'uslugi') {
block.title = "Our Services"
block.subtitle = "Professional tax and accounting support in Germany."
} else if (page.slug === 'kontakt') {
block.title = "Contact"
block.subtitle = "Do you have questions about tax settlement in Germany? Contact us — we will be happy to help."
}
modified = true
}
return block
})
if (modified) {
await payload.update({ collection: 'pages', id: page.id, locale: 'en', data: { layout } })
console.log(`Updated EN translations for ${page.slug}`)
}
}
// DE Update
const dePage = await payload.findByID({ collection: 'pages', id: page.id, locale: 'de' })
if (dePage && dePage.layout) {
let modified = false
const layout = dePage.layout.map((block: any) => {
for (const key of Object.keys(block)) {
if (block[key] && typeof block[key] === 'object' && block[key].id !== undefined) {
block[key] = block[key].id
}
}
if (block.blockType === 'finalCta') {
block.title = deCta.title
block.subtitle = deCta.subtitle
block.buttonText = deCta.buttonText
modified = true
}
if (block.blockType === 'pageHeader') {
if (page.slug === 'uslugi') {
block.title = "Unsere Dienstleistungen"
block.subtitle = "Professionelle steuerliche und buchhalterische Betreuung in Deutschland."
} else if (page.slug === 'kontakt') {
block.title = "Kontakt"
block.subtitle = "Haben Sie Fragen zur Steuererklärung in Deutschland? Kontaktieren Sie uns — wir helfen Ihnen gerne weiter."
}
modified = true
}
return block
})
if (modified) {
await payload.update({ collection: 'pages', id: page.id, locale: 'de', data: { layout } })
console.log(`Updated DE translations for ${page.slug}`)
}
}
}
process.exit(0)
}
run().catch(console.error)
+54
View File
@@ -0,0 +1,54 @@
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', limit: 100, locale: 'pl' })
for (const page of pages.docs) {
if (!['faq', 'kontakt', 'stowarzyszenie', 'polityka-prywatnosci', 'polityka-cookies', 'impressum'].includes(page.slug || '')) continue;
for (const loc of ['en', 'de'] as const) {
const dbPage = await payload.findByID({ collection: 'pages', id: page.id, locale: loc })
if (!dbPage || !dbPage.layout) continue;
let modified = false;
const layout = dbPage.layout.map((block: any) => {
// preserve IDs
for (const key of Object.keys(block)) {
if (block[key] && typeof block[key] === 'object' && block[key].id !== undefined) {
block[key] = block[key].id
}
}
if (block.blockType === 'contactInfo') {
block.hours = loc === 'en' ? "Mon-Fri: 9:00 - 17:00\nBy appointment" : "Mo-Fr: 9:00 - 17:00\nNach Vereinbarung"
block.address = "TO BE CONFIRMED BY CLIENT"
modified = true;
}
if (block.blockType === 'TextSection') {
block.text = loc === 'en' ? "Content in preparation" : "Inhalt in Vorbereitung"
modified = true;
}
return block;
});
if (modified) {
await payload.update({
collection: 'pages',
id: page.id,
locale: loc,
data: { layout }
})
console.log(`Fully translated ${page.slug} to ${loc}`)
}
}
}
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()
-27
View File
@@ -1,27 +0,0 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
async function run() {
const payload = await getPayload({ config: configPromise })
const plPage = await payload.findByID({
collection: 'pages',
id: 5,
locale: 'pl'
})
const dePage = await payload.findByID({
collection: 'pages',
id: 5,
locale: 'de'
})
console.log("=== KONTAKT PL LAYOUT ===")
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()
+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()
@@ -11,6 +11,56 @@ import type { Page } from '../../../../payload-types'
type FAQFullBlock = Extract<NonNullable<Page['layout']>[0], { blockType: 'faqFull' }> type FAQFullBlock = Extract<NonNullable<Page['layout']>[0], { blockType: 'faqFull' }>
import type { Metadata } from 'next'
import { createPageMetadata } from '@intecion/ipal-kit'
const pageMetadata = createPageMetadata({
config: i18n,
baseUrl: process.env.NEXT_PUBLIC_SERVER_URL || 'https://www.jkedzierski.de'
})
export const revalidate = 3600
export async function generateStaticParams() {
const payload = await getPayload({ config: configPromise })
const pages = await payload.find({ collection: 'pages', limit: 1000 })
const params: { locale: string; slug: string[] }[] = []
for (const page of pages.docs) {
if (page.slug && typeof page.slug === 'object') {
for (const [locale, slug] of Object.entries(page.slug)) {
if (slug && typeof slug === 'string') {
// Assuming index page is just the locale root without slug
params.push({
locale,
slug: slug === 'home' ? [] : slug.split('/')
})
}
}
}
}
// fallback to empty slugs for languages if none matched
if (params.length === 0) {
params.push({ locale: 'pl', slug: [] })
params.push({ locale: 'en', slug: [] })
params.push({ locale: 'de', slug: [] })
}
return params
}
export async function generateMetadata(
props: { params: Promise<{ locale: string; slug?: string[] }> }
): Promise<Metadata> {
const params = await props.params
const { locale, slug } = params
const payload = await getPayload({ config: configPromise })
return pageMetadata({ payload, locale, slug })
}
export default async function Page(props: { export default async function Page(props: {
params: Promise<{ locale: string; slug?: string[] }> params: Promise<{ locale: string; slug?: string[] }>
}) { }) {
+17 -5
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'
@@ -20,10 +20,7 @@ const fraunces = Fraunces({
variable: '--font-heading', variable: '--font-heading',
}) })
export const metadata = {
title: 'Finanz- & Lohnbuchhaltung Dipl.-Ing. Janusz Kędzierski',
description: 'Profesjonalne doradztwo podatkowe.',
}
export default async function RootLayout(props: { export default async function RootLayout(props: {
children: React.ReactNode, children: React.ReactNode,
@@ -58,9 +55,24 @@ export default async function RootLayout(props: {
// CMS: fallback to generic link if no number is configured. // CMS: fallback to generic link if no number is configured.
const whatsappLink = companyData?.whatsappNumber ? `https://wa.me/${companyData.whatsappNumber}` : '#'; const whatsappLink = companyData?.whatsappNumber ? `https://wa.me/${companyData.whatsappNumber}` : '#';
const schemaOrg = {
"@context": "https://schema.org",
"@type": "AccountingService",
"name": siteData?.siteName || "Finanz- & Lohnbuchhaltung Dipl.-Ing. Janusz Kędzierski",
"telephone": companyData?.phone || '',
"email": companyData?.email || '',
"address": companyData?.address || '',
"openingHours": companyData?.hours || '',
"url": process.env.NEXT_PUBLIC_SERVER_URL || "https://www.jkedzierski.de"
};
return ( return (
<html lang={locale} suppressHydrationWarning className={`${publicSans.variable} ${fraunces.variable}`}> <html lang={locale} suppressHydrationWarning className={`${publicSans.variable} ${fraunces.variable}`}>
<body className="font-sans antialiased bg-background text-foreground min-h-screen flex flex-col"> <body className="font-sans antialiased bg-background text-foreground min-h-screen flex flex-col">
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaOrg) }}
/>
<Providers> <Providers>
<LanguageSlugsProvider> <LanguageSlugsProvider>
<ConsentProvider texts={consentTexts}> <ConsentProvider texts={consentTexts}>
+10
View File
@@ -21,6 +21,11 @@ import { StrikethroughFeatureClient as StrikethroughFeatureClient_e70f5e05f09f93
import { UnderlineFeatureClient as UnderlineFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client' import { UnderlineFeatureClient as UnderlineFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { BoldFeatureClient as BoldFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client' import { BoldFeatureClient as BoldFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { ItalicFeatureClient as ItalicFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client' import { ItalicFeatureClient as ItalicFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { OverviewComponent as OverviewComponent_a8a977ebc872c5d5ea7ee689724c0860 } from '@payloadcms/plugin-seo/client'
import { MetaTitleComponent as MetaTitleComponent_a8a977ebc872c5d5ea7ee689724c0860 } from '@payloadcms/plugin-seo/client'
import { MetaDescriptionComponent as MetaDescriptionComponent_a8a977ebc872c5d5ea7ee689724c0860 } from '@payloadcms/plugin-seo/client'
import { MetaImageComponent as MetaImageComponent_a8a977ebc872c5d5ea7ee689724c0860 } from '@payloadcms/plugin-seo/client'
import { PreviewComponent as PreviewComponent_a8a977ebc872c5d5ea7ee689724c0860 } from '@payloadcms/plugin-seo/client'
import { MaskedField as MaskedField_630f031911beff97691b963f10a20a86 } from '@intecion/ipal-kit/client' import { MaskedField as MaskedField_630f031911beff97691b963f10a20a86 } from '@intecion/ipal-kit/client'
import { TestEmailButton as TestEmailButton_630f031911beff97691b963f10a20a86 } from '@intecion/ipal-kit/client' import { TestEmailButton as TestEmailButton_630f031911beff97691b963f10a20a86 } from '@intecion/ipal-kit/client'
import { CollectionCards as CollectionCards_f9c02e79a4aed9a3924487c0cd4cafb1 } from '@payloadcms/next/rsc' import { CollectionCards as CollectionCards_f9c02e79a4aed9a3924487c0cd4cafb1 } from '@payloadcms/next/rsc'
@@ -50,6 +55,11 @@ export const importMap = {
"@payloadcms/richtext-lexical/client#UnderlineFeatureClient": UnderlineFeatureClient_e70f5e05f09f93e00b997edb1ef0c864, "@payloadcms/richtext-lexical/client#UnderlineFeatureClient": UnderlineFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#BoldFeatureClient": BoldFeatureClient_e70f5e05f09f93e00b997edb1ef0c864, "@payloadcms/richtext-lexical/client#BoldFeatureClient": BoldFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#ItalicFeatureClient": ItalicFeatureClient_e70f5e05f09f93e00b997edb1ef0c864, "@payloadcms/richtext-lexical/client#ItalicFeatureClient": ItalicFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/plugin-seo/client#OverviewComponent": OverviewComponent_a8a977ebc872c5d5ea7ee689724c0860,
"@payloadcms/plugin-seo/client#MetaTitleComponent": MetaTitleComponent_a8a977ebc872c5d5ea7ee689724c0860,
"@payloadcms/plugin-seo/client#MetaDescriptionComponent": MetaDescriptionComponent_a8a977ebc872c5d5ea7ee689724c0860,
"@payloadcms/plugin-seo/client#MetaImageComponent": MetaImageComponent_a8a977ebc872c5d5ea7ee689724c0860,
"@payloadcms/plugin-seo/client#PreviewComponent": PreviewComponent_a8a977ebc872c5d5ea7ee689724c0860,
"@intecion/ipal-kit/client#MaskedField": MaskedField_630f031911beff97691b963f10a20a86, "@intecion/ipal-kit/client#MaskedField": MaskedField_630f031911beff97691b963f10a20a86,
"@intecion/ipal-kit/client#TestEmailButton": TestEmailButton_630f031911beff97691b963f10a20a86, "@intecion/ipal-kit/client#TestEmailButton": TestEmailButton_630f031911beff97691b963f10a20a86,
"@payloadcms/next/rsc#CollectionCards": CollectionCards_f9c02e79a4aed9a3924487c0cd4cafb1 "@payloadcms/next/rsc#CollectionCards": CollectionCards_f9c02e79a4aed9a3924487c0cd4cafb1
+5
View File
@@ -0,0 +1,5 @@
import { buildRobots } from '@intecion/ipal-kit'
export default function robots() {
return buildRobots({ baseUrl: process.env.NEXT_PUBLIC_SERVER_URL || '' })
}
+13
View File
@@ -0,0 +1,13 @@
import { getPayload } from 'payload'
import configPromise from '@payload-config'
import { buildSitemapEntries } from '@intecion/ipal-kit'
import { i18n } from '../i18n.config'
export default async function sitemap() {
const payload = await getPayload({ config: configPromise })
return buildSitemapEntries({
payload,
config: i18n,
baseUrl: process.env.NEXT_PUBLIC_SERVER_URL || '',
})
}
+3 -4
View File
@@ -22,14 +22,13 @@ export const AffiliationBlock: React.FC<Props> = ({ eyebrow, title, description,
<div className="container-premium"> <div className="container-premium">
<div className="grid grid-cols-1 lg:grid-cols-12 gap-16 lg:gap-24 items-center"> <div className="grid grid-cols-1 lg:grid-cols-12 gap-16 lg:gap-24 items-center">
{/* Lewa kolumna: Logo (Stamp) */}
<div className="lg:col-span-5 flex justify-center lg:justify-end order-1"> <div className="lg:col-span-5 flex justify-center lg:justify-end order-1">
<motion.div <motion.div
initial={{ opacity: 0, scale: 1.5, rotate: -15 }} initial={{ opacity: 0, scale: 1.5, rotate: -15 }}
whileInView={{ opacity: 1, scale: 1, rotate: 0 }} whileInView={{ opacity: 1, scale: 1, rotate: 0 }}
viewport={{ once: true, margin: "-50px" }} viewport={{ once: true, margin: "-50px" }}
transition={{ type: "spring", stiffness: 100, damping: 15 }} transition={{ type: "spring", stiffness: 100, damping: 15 }}
className="w-48 h-48 md:w-64 md:h-64 relative bg-card rounded-full p-8 shadow-xl border-4 border-accent/20 flex items-center justify-center shrink-0" className={`w-48 h-48 md:w-64 md:h-64 relative ${logoUrl ? 'bg-white' : 'bg-card'} rounded-full p-8 shadow-xl border-4 border-accent/20 flex items-center justify-center shrink-0`}
> >
{logoUrl ? ( {logoUrl ? (
/* eslint-disable-next-line @next/next/no-img-element */ /* eslint-disable-next-line @next/next/no-img-element */
@@ -39,8 +38,8 @@ export const AffiliationBlock: React.FC<Props> = ({ eyebrow, title, description,
className="max-w-full max-h-full object-contain mix-blend-multiply" className="max-w-full max-h-full object-contain mix-blend-multiply"
/> />
) : ( ) : (
<div className="w-full h-full flex flex-col items-center justify-center text-border bg-card rounded-full"> <div className="w-full h-full flex flex-col items-center justify-center text-accent rounded-full">
<Landmark size={48} strokeWidth={1.5} /> <Landmark className="w-20 h-20 md:w-28 md:h-28" strokeWidth={1.5} />
</div> </div>
)} )}
</motion.div> </motion.div>
+17 -7
View File
@@ -15,7 +15,17 @@ export const ContactInfoBlock: React.FC<Props> = ({
hours = 'Pon-Pt: 9:00 - 17:00\nPo wcześniejszym umówieniu', hours = 'Pon-Pt: 9:00 - 17:00\nPo wcześniejszym umówieniu',
}) => { }) => {
const pathname = usePathname() const pathname = usePathname()
const isDe = pathname?.split('/').filter(Boolean)[0] === 'de' const localeSegment = pathname?.split('/').filter(Boolean)[0]
const isDe = localeSegment === 'de'
const isEn = localeSegment === 'en'
const t = {
contactInfo: isEn ? 'Contact Information' : (isDe ? 'Kontaktinformationen' : 'Dane kontaktowe'),
address: isEn ? 'Address' : (isDe ? 'Adresse' : 'Adres'),
phone: isEn ? 'Phone' : (isDe ? 'Telefon' : 'Telefon'),
email: isEn ? 'Email' : (isDe ? 'E-Mail' : 'E-mail'),
hours: isEn ? 'Working Hours' : (isDe ? 'Öffnungszeiten' : 'Godziny pracy'),
}
return ( return (
<section className="py-16 md:py-24 bg-background"> <section className="py-16 md:py-24 bg-background">
@@ -25,7 +35,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)'
}} }}
@@ -34,7 +44,7 @@ export const ContactInfoBlock: React.FC<Props> = ({
<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="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" />
<h2 className="font-heading text-2xl md:text-3xl font-bold text-foreground mb-10 text-center md:text-left"> <h2 className="font-heading text-2xl md:text-3xl font-bold text-foreground mb-10 text-center md:text-left">
{isDe ? 'Kontaktinformationen' : 'Dane kontaktowe'} {t.contactInfo}
</h2> </h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-10 md:gap-12"> <div className="grid grid-cols-1 md:grid-cols-2 gap-10 md:gap-12">
@@ -43,7 +53,7 @@ export const ContactInfoBlock: React.FC<Props> = ({
<div className="flex items-center gap-3 text-accent border-b border-border/50 pb-3"> <div className="flex items-center gap-3 text-accent border-b border-border/50 pb-3">
<MapPin size={24} strokeWidth={2.5} /> <MapPin size={24} strokeWidth={2.5} />
<h3 className="font-heading font-bold text-lg text-foreground tracking-wide"> <h3 className="font-heading font-bold text-lg text-foreground tracking-wide">
{isDe ? 'Adresse' : 'Adres'} {t.address}
</h3> </h3>
</div> </div>
<p className="text-foreground/80 leading-relaxed font-medium whitespace-pre-line text-base max-w-[65ch]"> <p className="text-foreground/80 leading-relaxed font-medium whitespace-pre-line text-base max-w-[65ch]">
@@ -57,7 +67,7 @@ export const ContactInfoBlock: React.FC<Props> = ({
<div className="flex items-center gap-3 text-accent border-b border-border/50 pb-3"> <div className="flex items-center gap-3 text-accent border-b border-border/50 pb-3">
<Phone size={24} strokeWidth={2.5} /> <Phone size={24} strokeWidth={2.5} />
<h3 className="font-heading font-bold text-lg text-foreground tracking-wide"> <h3 className="font-heading font-bold text-lg text-foreground tracking-wide">
{isDe ? 'Telefon' : 'Telefon'} {t.phone}
</h3> </h3>
</div> </div>
<a <a
@@ -74,7 +84,7 @@ export const ContactInfoBlock: React.FC<Props> = ({
<div className="flex items-center gap-3 text-accent border-b border-border/50 pb-3"> <div className="flex items-center gap-3 text-accent border-b border-border/50 pb-3">
<Mail size={24} strokeWidth={2.5} /> <Mail size={24} strokeWidth={2.5} />
<h3 className="font-heading font-bold text-lg text-foreground tracking-wide"> <h3 className="font-heading font-bold text-lg text-foreground tracking-wide">
{isDe ? 'E-Mail' : 'E-mail'} {t.email}
</h3> </h3>
</div> </div>
<a <a
@@ -91,7 +101,7 @@ export const ContactInfoBlock: React.FC<Props> = ({
<div className="flex items-center gap-3 text-accent border-b border-border/50 pb-3"> <div className="flex items-center gap-3 text-accent border-b border-border/50 pb-3">
<Clock size={24} strokeWidth={2.5} /> <Clock size={24} strokeWidth={2.5} />
<h3 className="font-heading font-bold text-lg text-foreground tracking-wide"> <h3 className="font-heading font-bold text-lg text-foreground tracking-wide">
{isDe ? 'Öffnungszeiten' : 'Godziny pracy'} {t.hours}
</h3> </h3>
</div> </div>
<p className="text-foreground/80 leading-relaxed font-medium whitespace-pre-line text-base max-w-[65ch]"> <p className="text-foreground/80 leading-relaxed font-medium whitespace-pre-line text-base max-w-[65ch]">

Some files were not shown because too many files have changed in this diff Show More