fix: resolve contact form placeholders, restore process block layout, and fix testimonials carousel scroll logic
This commit is contained in:
@@ -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).
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
[PODEJRZANY DUPLIKAT] Page(home).layout[3].items[1].stat | PL: "100%" | DE: "100%"
|
||||||
|
[PODEJRZANY DUPLIKAT] Page(home).layout[3].items[2].stat | PL: "§4 Nr. 11" | DE: "§4 Nr. 11"
|
||||||
|
[PODEJRZANY DUPLIKAT] Page(home).layout[7].testimonials[1].author | PL: "Anna, Berlin" | DE: "Anna, Berlin"
|
||||||
|
[PODEJRZANY DUPLIKAT] Page(home).layout[7].testimonials[2].author | PL: "Krzysztof, Dortmund" | DE: "Krzysztof, Dortmund"
|
||||||
|
[PODEJRZANY DUPLIKAT] Page(home).layout[9].eyebrow | PL: "Kontakt" | DE: "Kontakt"
|
||||||
|
[PODEJRZANY DUPLIKAT] Page(uslugi).layout[3].eyebrow | PL: "Kontakt" | DE: "Kontakt"
|
||||||
|
[PODEJRZANY DUPLIKAT] Page(jak-sie-umowic).layout[2].address | PL: "DO POTWIERDZENIA PRZEZ KLIENTA" | DE: "DO POTWIERDZENIA PRZEZ KLIENTA"
|
||||||
|
[PODEJRZANY DUPLIKAT] Page(o-nas).layout[1].title | PL: "Janusz Kędzierski" | DE: "Janusz Kędzierski"
|
||||||
|
[PODEJRZANY DUPLIKAT] Page(faq).title | PL: "FAQ" | DE: "FAQ"
|
||||||
|
[PODEJRZANY DUPLIKAT] Page(faq).layout[0].ctaUrl | PL: "#" | DE: "#"
|
||||||
|
[PODEJRZANY DUPLIKAT] Page(kontakt).title | PL: "Kontakt" | DE: "Kontakt"
|
||||||
|
[PODEJRZANY DUPLIKAT] Page(kontakt).layout[0].title | PL: "Kontakt" | DE: "Kontakt"
|
||||||
|
[PODEJRZANY DUPLIKAT] Page(kontakt).layout[0].subtitle | PL: "Masz pytania dotyczące rozliczenia podatkowego w N" | DE: "Masz pytania dotyczące rozliczenia podatkowego w N"
|
||||||
|
[PODEJRZANY DUPLIKAT] Page(kontakt).layout[1].address | PL: "DO POTWIERDZENIA PRZEZ KLIENTA" | DE: "DO POTWIERDZENIA PRZEZ KLIENTA"
|
||||||
|
[PODEJRZANY DUPLIKAT] Page(kontakt).layout[1].hours | PL: "Pon-Pt: 9:00 - 17:00 Po wcześniejszym umówieniu" | DE: "Pon-Pt: 9:00 - 17:00 Po wcześniejszym umówieniu"
|
||||||
|
[PODEJRZANY DUPLIKAT] Page(kontakt).layout[1].form.title | PL: "Kontaktformular" | DE: "Kontaktformular"
|
||||||
|
[PODEJRZANY DUPLIKAT] Page(impressum).title | PL: "Impressum" | DE: "Impressum"
|
||||||
|
[PODEJRZANY DUPLIKAT] Global(header).navigation[0].page.layout[3].eyebrow | PL: "Kontakt" | DE: "Kontakt"
|
||||||
|
[PODEJRZANY DUPLIKAT] Global(header).navigation[1].page.layout[2].address | PL: "DO POTWIERDZENIA PRZEZ KLIENTA" | DE: "DO POTWIERDZENIA PRZEZ KLIENTA"
|
||||||
|
[PODEJRZANY DUPLIKAT] Global(header).navigation[2].page.layout[1].title | PL: "Janusz Kędzierski" | DE: "Janusz Kędzierski"
|
||||||
|
[PODEJRZANY DUPLIKAT] Global(header).navigation[3].anchor | PL: "faq" | DE: "faq"
|
||||||
|
[PODEJRZANY DUPLIKAT] Global(header).navigation[4].page.title | PL: "Kontakt" | DE: "Kontakt"
|
||||||
|
[PODEJRZANY DUPLIKAT] Global(header).navigation[4].page.layout[0].title | PL: "Kontakt" | DE: "Kontakt"
|
||||||
|
[PODEJRZANY DUPLIKAT] Global(header).navigation[4].page.layout[0].subtitle | PL: "Masz pytania dotyczące rozliczenia podatkowego w N" | DE: "Masz pytania dotyczące rozliczenia podatkowego w N"
|
||||||
|
[PODEJRZANY DUPLIKAT] Global(header).navigation[4].page.layout[1].address | PL: "DO POTWIERDZENIA PRZEZ KLIENTA" | DE: "DO POTWIERDZENIA PRZEZ KLIENTA"
|
||||||
|
[PODEJRZANY DUPLIKAT] Global(header).navigation[4].page.layout[1].hours | PL: "Pon-Pt: 9:00 - 17:00 Po wcześniejszym umówieniu" | DE: "Pon-Pt: 9:00 - 17:00 Po wcześniejszym umówieniu"
|
||||||
|
[PODEJRZANY DUPLIKAT] Global(header).navigation[4].page.layout[1].form.title | PL: "Kontaktformular" | DE: "Kontaktformular"
|
||||||
|
[PODEJRZANY DUPLIKAT] Global(footer).navigation[0].page.layout[3].eyebrow | PL: "Kontakt" | DE: "Kontakt"
|
||||||
|
[PODEJRZANY DUPLIKAT] Global(footer).navigation[1].page.layout[2].address | PL: "DO POTWIERDZENIA PRZEZ KLIENTA" | DE: "DO POTWIERDZENIA PRZEZ KLIENTA"
|
||||||
|
[PODEJRZANY DUPLIKAT] Global(footer).navigation[2].page.layout[1].title | PL: "Janusz Kędzierski" | DE: "Janusz Kędzierski"
|
||||||
|
[PODEJRZANY DUPLIKAT] Global(footer).navigation[3].page.title | PL: "Kontakt" | DE: "Kontakt"
|
||||||
|
[PODEJRZANY DUPLIKAT] Global(footer).navigation[3].page.layout[0].title | PL: "Kontakt" | DE: "Kontakt"
|
||||||
|
[PODEJRZANY DUPLIKAT] Global(footer).navigation[3].page.layout[0].subtitle | PL: "Masz pytania dotyczące rozliczenia podatkowego w N" | DE: "Masz pytania dotyczące rozliczenia podatkowego w N"
|
||||||
|
[PODEJRZANY DUPLIKAT] Global(footer).navigation[3].page.layout[1].address | PL: "DO POTWIERDZENIA PRZEZ KLIENTA" | DE: "DO POTWIERDZENIA PRZEZ KLIENTA"
|
||||||
|
[PODEJRZANY DUPLIKAT] Global(footer).navigation[3].page.layout[1].hours | PL: "Pon-Pt: 9:00 - 17:00 Po wcześniejszym umówieniu" | DE: "Pon-Pt: 9:00 - 17:00 Po wcześniejszym umówieniu"
|
||||||
|
[PODEJRZANY DUPLIKAT] Global(footer).navigation[3].page.layout[1].form.title | PL: "Kontaktformular" | DE: "Kontaktformular"
|
||||||
|
[PODEJRZANY DUPLIKAT] Global(footer).navigation[6].page.title | PL: "Impressum" | DE: "Impressum"
|
||||||
|
[PODEJRZANY DUPLIKAT] Global(company).address | PL: "DO POTWIERDZENIA PRZEZ KLIENTA" | DE: "DO POTWIERDZENIA PRZEZ KLIENTA"
|
||||||
|
[PODEJRZANY DUPLIKAT] Global(company).hours | PL: "Pon-Pt: 9:00 - 17:00 Po wcześniejszym umówieniu" | DE: "Pon-Pt: 9:00 - 17:00 Po wcześniejszym umówieniu"
|
||||||
|
[PODEJRZANY DUPLIKAT] Global(site-settings).homepage.layout[3].items[1].stat | PL: "100%" | DE: "100%"
|
||||||
|
[PODEJRZANY DUPLIKAT] Global(site-settings).homepage.layout[3].items[2].stat | PL: "§4 Nr. 11" | DE: "§4 Nr. 11"
|
||||||
|
[PODEJRZANY DUPLIKAT] Global(site-settings).homepage.layout[7].testimonials[1].author | PL: "Anna, Berlin" | DE: "Anna, Berlin"
|
||||||
|
[PODEJRZANY DUPLIKAT] Global(site-settings).homepage.layout[7].testimonials[2].author | PL: "Krzysztof, Dortmund" | DE: "Krzysztof, Dortmund"
|
||||||
|
[PODEJRZANY DUPLIKAT] Global(site-settings).homepage.layout[9].eyebrow | PL: "Kontakt" | DE: "Kontakt"
|
||||||
|
[PODEJRZANY DUPLIKAT] Form(Kontaktformular).title | PL: "Kontaktformular" | DE: "Kontaktformular"
|
||||||
File diff suppressed because one or more lines are too long
@@ -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)"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -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)"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|||||||
@@ -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)"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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)"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -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)"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -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)"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -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ł\""
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -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)"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -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\""
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -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)"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -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\""
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -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\""
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -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)"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -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
@@ -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\""
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -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)"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"antipattern": "line-length",
|
||||||
|
"name": "Line length too long",
|
||||||
|
"description": "Text lines wider than ~80 characters are hard to read. The eye loses its place tracking back to the start of the next line. Add a max-width (65ch to 75ch) to text containers.",
|
||||||
|
"severity": "warning",
|
||||||
|
"category": "quality",
|
||||||
|
"file": "http://localhost:3000/pl/o-nas",
|
||||||
|
"line": 0,
|
||||||
|
"snippet": "~85 chars/line (aim for <80)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"antipattern": "nested-cards",
|
||||||
|
"name": "Nested cards",
|
||||||
|
"description": "Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.",
|
||||||
|
"severity": "warning",
|
||||||
|
"category": "slop",
|
||||||
|
"file": "http://localhost:3000/pl/o-nas",
|
||||||
|
"line": 0,
|
||||||
|
"snippet": "Card inside card"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"antipattern": "nested-cards",
|
||||||
|
"name": "Nested cards",
|
||||||
|
"description": "Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.",
|
||||||
|
"severity": "warning",
|
||||||
|
"category": "slop",
|
||||||
|
"file": "http://localhost:3000/pl/o-nas",
|
||||||
|
"line": 0,
|
||||||
|
"snippet": "Card inside card"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"antipattern": "nested-cards",
|
||||||
|
"name": "Nested cards",
|
||||||
|
"description": "Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.",
|
||||||
|
"severity": "warning",
|
||||||
|
"category": "slop",
|
||||||
|
"file": "http://localhost:3000/pl/o-nas",
|
||||||
|
"line": 0,
|
||||||
|
"snippet": "Card inside card"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"antipattern": "nested-cards",
|
||||||
|
"name": "Nested cards",
|
||||||
|
"description": "Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.",
|
||||||
|
"severity": "warning",
|
||||||
|
"category": "slop",
|
||||||
|
"file": "http://localhost:3000/pl/o-nas",
|
||||||
|
"line": 0,
|
||||||
|
"snippet": "Card inside card"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"antipattern": "nested-cards",
|
||||||
|
"name": "Nested cards",
|
||||||
|
"description": "Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.",
|
||||||
|
"severity": "warning",
|
||||||
|
"category": "slop",
|
||||||
|
"file": "http://localhost:3000/pl/o-nas",
|
||||||
|
"line": 0,
|
||||||
|
"snippet": "Card inside card"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"antipattern": "nested-cards",
|
||||||
|
"name": "Nested cards",
|
||||||
|
"description": "Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.",
|
||||||
|
"severity": "warning",
|
||||||
|
"category": "slop",
|
||||||
|
"file": "http://localhost:3000/pl/o-nas",
|
||||||
|
"line": 0,
|
||||||
|
"snippet": "Card inside card"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"antipattern": "nested-cards",
|
||||||
|
"name": "Nested cards",
|
||||||
|
"description": "Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.",
|
||||||
|
"severity": "warning",
|
||||||
|
"category": "slop",
|
||||||
|
"file": "http://localhost:3000/pl/o-nas",
|
||||||
|
"line": 0,
|
||||||
|
"snippet": "Card inside card"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"antipattern": "nested-cards",
|
||||||
|
"name": "Nested cards",
|
||||||
|
"description": "Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.",
|
||||||
|
"severity": "warning",
|
||||||
|
"category": "slop",
|
||||||
|
"file": "http://localhost:3000/pl/o-nas",
|
||||||
|
"line": 0,
|
||||||
|
"snippet": "Card inside card"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"antipattern": "cream-palette",
|
||||||
|
"name": "Cream / beige palette",
|
||||||
|
"description": "A warm cream or beige page background has become the default \"tasteful\" AI surface, reached for by reflex. Choose a background that comes from a deliberate palette, not the safe warm off-white.",
|
||||||
|
"severity": "warning",
|
||||||
|
"category": "slop",
|
||||||
|
"file": "http://localhost:3000/pl/o-nas",
|
||||||
|
"line": 0,
|
||||||
|
"snippet": "cream/beige page background rgb(247, 245, 241)"
|
||||||
|
}
|
||||||
|
]
|
||||||
+9
-1
@@ -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
Generated
+112
-99
@@ -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. |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { getPayload } from 'payload'
|
||||||
|
import configPromise from '../src/payload.config'
|
||||||
|
|
||||||
|
async function run() {
|
||||||
|
const payload = await getPayload({ config: configPromise })
|
||||||
|
|
||||||
|
const locales = ['pl', 'de']
|
||||||
|
let foundEmpty = false
|
||||||
|
|
||||||
|
for (const locale of locales) {
|
||||||
|
const pages = await payload.find({ collection: 'pages', locale: locale as 'pl'|'de', limit: 100 })
|
||||||
|
for (const page of pages.docs) {
|
||||||
|
const layout = page.layout || []
|
||||||
|
for (const block of layout) {
|
||||||
|
// Iterate over keys of the block to find arrays
|
||||||
|
for (const key of Object.keys(block)) {
|
||||||
|
const value = block[key]
|
||||||
|
if (Array.isArray(value) && value.length === 0) {
|
||||||
|
// Some arrays might naturally be empty if they are optional, but most of these are required
|
||||||
|
// Let's flag any empty array in these block fields
|
||||||
|
if (['steps', 'questions', 'services', 'members', 'stats', 'points', 'testimonials', 'logos', 'bullets', 'checkmarks'].includes(key)) {
|
||||||
|
console.log(`[UWAGA] Pusta tablica '${key}' znaleziona w bloku '${block.blockType}' na stronie '${page.slug}' (locale: ${locale})`)
|
||||||
|
foundEmpty = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!foundEmpty) {
|
||||||
|
console.log("Wszystkie tablice w blokach na stronach (PL i DE) są pełne. Stan: OK.")
|
||||||
|
}
|
||||||
|
|
||||||
|
process.exit(0)
|
||||||
|
}
|
||||||
|
run().catch(console.error)
|
||||||
@@ -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);
|
||||||
@@ -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();
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import { getPayload } from 'payload'
|
||||||
|
import configPromise from '../src/payload.config'
|
||||||
|
|
||||||
|
async function run() {
|
||||||
|
const payload = await getPayload({ config: configPromise })
|
||||||
|
|
||||||
|
for (const locale of ['pl', 'de']) {
|
||||||
|
const pages = await payload.find({
|
||||||
|
collection: 'pages',
|
||||||
|
locale: locale as 'pl' | 'de',
|
||||||
|
where: { slug: { equals: 'kontakt' } }
|
||||||
|
})
|
||||||
|
|
||||||
|
if (pages.docs.length > 0) {
|
||||||
|
const page = pages.docs[0];
|
||||||
|
const formBlock = page.layout?.find(b => b.blockType === 'contactSection');
|
||||||
|
if (formBlock && formBlock.form) {
|
||||||
|
// Fetch the form
|
||||||
|
const formId = typeof formBlock.form === 'object' ? formBlock.form.id : formBlock.form;
|
||||||
|
console.log(`\n=== FORMULARZ PODPIĘTY POD /${locale}/kontakt (ID: ${formId}) ===`);
|
||||||
|
|
||||||
|
const form = await payload.findByID({
|
||||||
|
collection: 'forms',
|
||||||
|
id: formId as number,
|
||||||
|
locale: locale as 'pl' | 'de',
|
||||||
|
});
|
||||||
|
|
||||||
|
const serviceField = form.fields?.find(f => f.name === 'service' && f.blockType === 'select');
|
||||||
|
console.log(`POLE SERVICE OPTIONS (LOCALE: ${locale}):`);
|
||||||
|
console.log(JSON.stringify(serviceField?.options, null, 2));
|
||||||
|
|
||||||
|
// Let's also check placeholders and other texts for step 5
|
||||||
|
console.log(`\nPOZOSTAŁE POLA (LOCALE: ${locale}):`);
|
||||||
|
form.fields?.forEach(f => {
|
||||||
|
if (f.blockType !== 'select') {
|
||||||
|
const placeholderValue = f.defaultValue || (f.name === 'email' ? (f.label === 'Adres e-mail' ? '[email protected]' : '[email protected]') : '');
|
||||||
|
console.log(`- ${f.name} (${f.blockType}): label="${f.label}", placeholder="${placeholderValue}"`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
run().catch(console.error);
|
||||||
@@ -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)
|
||||||
@@ -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)
|
||||||
@@ -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()
|
||||||
@@ -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()
|
||||||
@@ -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()
|
||||||
@@ -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);
|
||||||
@@ -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()
|
||||||
@@ -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()
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
[⣷] Pulling schema from database...
|
||||||
|
[2K[1G[✓] Pulling schema from database...
|
||||||
|
[15:52:56] [32mINFO[39m: [36m[ipal] Plugin initialized.[39m
|
||||||
|
|
||||||
|
=== 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=""
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
[⣷] Pulling schema from database...
|
||||||
|
[2K[1G[✓] Pulling schema from database...
|
||||||
|
[16:03:33] [32mINFO[39m: [36m[ipal] Plugin initialized.[39m
|
||||||
|
|
||||||
|
=== 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=""
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
[⣷] Pulling schema from database...
|
||||||
|
[2K[1G[✓] Pulling schema from database...
|
||||||
|
[15:54:43] [32mINFO[39m: [36m[ipal] Plugin initialized.[39m
|
||||||
|
|
||||||
|
=== 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=""
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
[⣷] Pulling schema from database...
|
||||||
|
[2K[1G[✓] Pulling schema from database...
|
||||||
|
[16:00:33] [32mINFO[39m: [36m[ipal] Plugin initialized.[39m
|
||||||
|
|
||||||
|
=== 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=""
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
[⣷] Pulling schema from database...
|
||||||
|
[2K[1G[✓] Pulling schema from database...
|
||||||
|
[16:02:47] [32mINFO[39m: [36m[ipal] Plugin initialized.[39m
|
||||||
|
|
||||||
|
=== 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=""
|
||||||
@@ -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);
|
||||||
@@ -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()
|
||||||
@@ -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()
|
||||||
@@ -0,0 +1,369 @@
|
|||||||
|
[⣷] Pulling schema from database...
|
||||||
|
[2K[1G[✓] Pulling schema from database...
|
||||||
|
[15:46:43] [32mINFO[39m: [36m[ipal] Plugin initialized.[39m
|
||||||
|
=== 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"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -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()
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
import { getPayload } from 'payload'
|
||||||
|
import configPromise from '../src/payload.config'
|
||||||
|
import fs from 'fs'
|
||||||
|
import path from 'path'
|
||||||
|
|
||||||
|
async function run() {
|
||||||
|
const payload = await getPayload({ config: configPromise })
|
||||||
|
|
||||||
|
const stepsPL = [
|
||||||
|
{
|
||||||
|
title: "Napisz na WhatsApp",
|
||||||
|
description: "Skontaktuj się z nami bezpośrednio przez WhatsApp lub formularz poniżej, aby rozpocząć i ustalić szczegóły Twojej sytuacji podatkowej."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Przekaż dokumenty",
|
||||||
|
description: "Prześlij zaświadczenia o dochodach (Lohnsteuerbescheinigung, EU/EWR), podatkowy numer identyfikacyjny (IdNr) lub PESEL oraz ewentualne potwierdzenia kosztów."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Odbierz gotowe rozliczenie do podpisu",
|
||||||
|
description: "Przygotowujemy kompletną deklarację, wyliczamy przewidywany zwrot z urzędu skarbowego i przesyłamy gotowy dokument do Twojej akceptacji i podpisu."
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const finalStepsDE = [
|
||||||
|
{
|
||||||
|
title: "WhatsApp kontaktieren",
|
||||||
|
description: "Schreiben Sie uns direkt über WhatsApp oder das untenstehende Formular, um Ihre Anfrage und steuerliche Situation zu besprechen."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Unterlagen einreichen",
|
||||||
|
description: "Übermitteln Sie Ihre Lohnsteuerbescheinigung, Steuer-Identifikationsnummer sowie relevante Belege für Werbungskosten und Freibeträge."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Fertige Erklärung zur Unterschrift",
|
||||||
|
description: "Wir erstellen Ihre vollständige Erklärung, berechnen die voraussichtliche Erstattung und übermitteln Ihnen die Unterlagen zur Freigabe."
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// 1. Update PL
|
||||||
|
const jsuPL = await payload.find({ collection: 'pages', locale: 'pl', where: { slug: { equals: 'jak-sie-umowic' } } })
|
||||||
|
if (jsuPL.docs.length > 0) {
|
||||||
|
const doc = jsuPL.docs[0]
|
||||||
|
const layout = doc.layout || []
|
||||||
|
let updated = false
|
||||||
|
for (let i = 0; i < layout.length; i++) {
|
||||||
|
if (layout[i].blockType === 'process') {
|
||||||
|
layout[i].steps = stepsPL.map((s: any) => ({
|
||||||
|
title: s.title,
|
||||||
|
description: s.description
|
||||||
|
}))
|
||||||
|
updated = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (updated) {
|
||||||
|
await payload.update({
|
||||||
|
collection: 'pages',
|
||||||
|
id: doc.id,
|
||||||
|
locale: 'pl',
|
||||||
|
data: { layout }
|
||||||
|
})
|
||||||
|
console.log("Zaktualizowano kroki dla PL")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Update DE
|
||||||
|
const jsuDE = await payload.find({ collection: 'pages', locale: 'de', where: { slug: { equals: 'terminvereinbarung' } } })
|
||||||
|
if (jsuDE.docs.length > 0) {
|
||||||
|
const doc = jsuDE.docs[0]
|
||||||
|
const layout = doc.layout || []
|
||||||
|
let updated = false
|
||||||
|
for (let i = 0; i < layout.length; i++) {
|
||||||
|
if (layout[i].blockType === 'process') {
|
||||||
|
layout[i].steps = finalStepsDE.map((s: any) => ({
|
||||||
|
title: s.title,
|
||||||
|
description: s.description
|
||||||
|
}))
|
||||||
|
updated = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (updated) {
|
||||||
|
await payload.update({
|
||||||
|
collection: 'pages',
|
||||||
|
id: doc.id,
|
||||||
|
locale: 'de',
|
||||||
|
data: { layout }
|
||||||
|
})
|
||||||
|
console.log("Zaktualizowano kroki dla DE")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
process.exit(0)
|
||||||
|
}
|
||||||
|
run().catch(console.error)
|
||||||
@@ -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()
|
|
||||||
@@ -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()
|
||||||
@@ -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()
|
||||||
@@ -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()
|
||||||
@@ -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()
|
||||||
@@ -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)
|
||||||
@@ -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()
|
||||||
@@ -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)
|
||||||
@@ -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()
|
||||||
@@ -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()
|
||||||
@@ -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()
|
||||||
@@ -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)
|
||||||
@@ -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)
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
[⣷] Pulling schema from database...
|
||||||
|
[2K[1G[✓] Pulling schema from database...
|
||||||
|
[15:53:24] [32mINFO[39m: [36m[ipal] Plugin initialized.[39m
|
||||||
|
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
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
[⣷] Pulling schema from database...
|
||||||
|
[2K[1G[✓] Pulling schema from database...
|
||||||
|
[16:07:07] [32mINFO[39m: [36m[ipal] Plugin initialized.[39m
|
||||||
|
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.
|
||||||
@@ -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()
|
||||||
@@ -4,24 +4,13 @@ import configPromise from '../src/payload.config'
|
|||||||
async function run() {
|
async function run() {
|
||||||
const payload = await getPayload({ config: configPromise })
|
const payload = await getPayload({ config: configPromise })
|
||||||
|
|
||||||
const plPage = await payload.findByID({
|
const formConf = payload.collections['forms'].config;
|
||||||
collection: 'pages',
|
const titleField = formConf.fields.find(f => f.name === 'title');
|
||||||
id: 5,
|
console.log('Form title is localized:', titleField?.localized);
|
||||||
locale: 'pl'
|
|
||||||
})
|
|
||||||
|
|
||||||
const dePage = await payload.findByID({
|
const kontaktALL = await payload.find({ collection: 'pages', locale: 'all', where: { slug: { equals: 'kontakt' } } });
|
||||||
collection: 'pages',
|
console.log(JSON.stringify(kontaktALL.docs[0], null, 2));
|
||||||
id: 5,
|
|
||||||
locale: 'de'
|
|
||||||
})
|
|
||||||
|
|
||||||
console.log("=== KONTAKT PL LAYOUT ===")
|
process.exit(0);
|
||||||
console.log(JSON.stringify(plPage.layout, null, 2))
|
|
||||||
console.log("=== KONTAKT DE LAYOUT ===")
|
|
||||||
console.log(JSON.stringify(dePage.layout, null, 2))
|
|
||||||
|
|
||||||
process.exit(0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
run()
|
run()
|
||||||
|
|||||||
@@ -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()
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { getPayload } from 'payload'
|
||||||
|
import configPromise from '../src/payload.config'
|
||||||
|
|
||||||
|
async function run() {
|
||||||
|
const payload = await getPayload({ config: configPromise })
|
||||||
|
|
||||||
|
console.log('--- VERIFICATION ---');
|
||||||
|
|
||||||
|
const k = await payload.find({ collection: 'pages', locale: 'de', where: { slug: { equals: 'kontakt' } } });
|
||||||
|
console.log('Kontakt DE layout[0].subtitle:', k.docs[0].layout[0].subtitle);
|
||||||
|
console.log('Kontakt DE layout[1].address:', k.docs[0].layout[1].address);
|
||||||
|
console.log('Kontakt DE layout[1].hours:', k.docs[0].layout[1].hours);
|
||||||
|
|
||||||
|
const j = await payload.find({ collection: 'pages', locale: 'de', where: { slug: { equals: 'terminvereinbarung' } } });
|
||||||
|
if (j.docs.length > 0) {
|
||||||
|
console.log('JSU DE layout[2].address:', j.docs[0].layout[2].address);
|
||||||
|
} else {
|
||||||
|
console.log('JSU DE not found with slug terminvereinbarung');
|
||||||
|
}
|
||||||
|
|
||||||
|
const c = await payload.findGlobal({ slug: 'company', locale: 'de' });
|
||||||
|
console.log('Company DE address:', c.address);
|
||||||
|
console.log('Company DE hours:', c.hours);
|
||||||
|
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
run()
|
||||||
@@ -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'
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export const ContactInfoBlock: React.FC<Props> = ({
|
|||||||
whileInView={{ opacity: 1, y: 0 }}
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
viewport={{ once: true, margin: "-50px" }}
|
viewport={{ once: true, margin: "-50px" }}
|
||||||
transition={{ duration: 0.6, ease: [0.16, 1, 0.3, 1] }}
|
transition={{ duration: 0.6, ease: [0.16, 1, 0.3, 1] }}
|
||||||
className="w-full max-w-4xl bg-card border border-border p-8 md:p-16 relative overflow-hidden"
|
className="w-full max-w-4xl mx-auto bg-card border border-border p-8 md:p-16 relative overflow-hidden"
|
||||||
style={{
|
style={{
|
||||||
boxShadow: '0 10px 40px -10px rgba(0,0,0,0.08), 0 1px 3px rgba(0,0,0,0.03)'
|
boxShadow: '0 10px 40px -10px rgba(0,0,0,0.08), 0 1px 3px rgba(0,0,0,0.03)'
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -0,0 +1,127 @@
|
|||||||
|
'use client'
|
||||||
|
import React from 'react'
|
||||||
|
import { usePathname } from 'next/navigation'
|
||||||
|
import { MapPin, Phone, Mail, Clock } from 'lucide-react'
|
||||||
|
import { motion } from 'framer-motion'
|
||||||
|
|
||||||
|
export const ContactSectionClient: React.FC<any> = ({
|
||||||
|
address,
|
||||||
|
phone,
|
||||||
|
email,
|
||||||
|
hours,
|
||||||
|
formNode,
|
||||||
|
}) => {
|
||||||
|
const pathname = usePathname()
|
||||||
|
const isDe = pathname?.split('/').filter(Boolean)[0] === 'de'
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="py-16 md:py-24 bg-background">
|
||||||
|
<div className="container-premium">
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12 lg:gap-16">
|
||||||
|
|
||||||
|
{/* Lewa kolumna: Informacje kontaktowe */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, x: -16 }}
|
||||||
|
whileInView={{ opacity: 1, x: 0 }}
|
||||||
|
viewport={{ once: true, margin: "-50px" }}
|
||||||
|
transition={{ duration: 0.6, ease: [0.16, 1, 0.3, 1] }}
|
||||||
|
className="flex flex-col"
|
||||||
|
>
|
||||||
|
<h2 className="font-heading text-3xl md:text-4xl font-extrabold text-foreground mb-8 text-balance">
|
||||||
|
{isDe ? 'Kontaktinformationen' : 'Dane kontaktowe'}
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div className="bg-card border border-border p-8 relative overflow-hidden flex-grow"
|
||||||
|
style={{ boxShadow: '0 10px 40px -10px rgba(0,0,0,0.08), 0 1px 3px rgba(0,0,0,0.03)' }}
|
||||||
|
>
|
||||||
|
<div className="absolute top-0 right-0 w-12 h-12 bg-background border-l border-b border-border shadow-sm transform translate-x-6 -translate-y-6 rotate-45 pointer-events-none" />
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-1 gap-10">
|
||||||
|
{address && (
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
<div className="flex items-center gap-3 text-accent border-b border-border/50 pb-3">
|
||||||
|
<MapPin size={24} strokeWidth={2.5} />
|
||||||
|
<h3 className="font-heading font-bold text-lg text-foreground tracking-wide">
|
||||||
|
{isDe ? 'Adresse' : 'Adres'}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<p className="text-foreground/80 leading-relaxed font-medium whitespace-pre-line text-base">
|
||||||
|
{address}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{phone && (
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
<div className="flex items-center gap-3 text-accent border-b border-border/50 pb-3">
|
||||||
|
<Phone size={24} strokeWidth={2.5} />
|
||||||
|
<h3 className="font-heading font-bold text-lg text-foreground tracking-wide">
|
||||||
|
{isDe ? 'Telefon' : 'Telefon'}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<a
|
||||||
|
href={`tel:${phone.replace(/\s+/g, '')}`}
|
||||||
|
className="text-foreground/80 leading-relaxed font-medium text-base hover:text-accent transition-colors"
|
||||||
|
>
|
||||||
|
{phone}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{email && (
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
<div className="flex items-center gap-3 text-accent border-b border-border/50 pb-3">
|
||||||
|
<Mail size={24} strokeWidth={2.5} />
|
||||||
|
<h3 className="font-heading font-bold text-lg text-foreground tracking-wide">
|
||||||
|
{isDe ? 'E-Mail' : 'E-mail'}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<a
|
||||||
|
href={`mailto:${email}`}
|
||||||
|
className="text-foreground/80 leading-relaxed font-medium text-base hover:text-accent transition-colors"
|
||||||
|
>
|
||||||
|
{email}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{hours && (
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
<div className="flex items-center gap-3 text-accent border-b border-border/50 pb-3">
|
||||||
|
<Clock size={24} strokeWidth={2.5} />
|
||||||
|
<h3 className="font-heading font-bold text-lg text-foreground tracking-wide">
|
||||||
|
{isDe ? 'Öffnungszeiten' : 'Godziny pracy'}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<p className="text-foreground/80 leading-relaxed font-medium whitespace-pre-line text-base">
|
||||||
|
{hours}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
{/* Prawa kolumna: Formularz kontaktowy */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, x: 16 }}
|
||||||
|
whileInView={{ opacity: 1, x: 0 }}
|
||||||
|
viewport={{ once: true, margin: "-50px" }}
|
||||||
|
transition={{ duration: 0.6, ease: [0.16, 1, 0.3, 1], delay: 0.1 }}
|
||||||
|
className="flex flex-col"
|
||||||
|
>
|
||||||
|
<h2 className="font-heading text-3xl md:text-4xl font-extrabold text-foreground mb-8 text-balance">
|
||||||
|
{isDe ? 'Schreiben Sie uns' : 'Napisz do nas'}
|
||||||
|
</h2>
|
||||||
|
<div className="bg-card border border-border p-8 relative overflow-hidden flex-grow"
|
||||||
|
style={{ boxShadow: '0 10px 40px -10px rgba(0,0,0,0.08), 0 1px 3px rgba(0,0,0,0.03)' }}
|
||||||
|
>
|
||||||
|
{formNode}
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import type { Page } from '../../payload-types'
|
||||||
|
import { ContactSectionClient } from './Client'
|
||||||
|
import { FormRenderer } from '../../components/FormRenderer'
|
||||||
|
|
||||||
|
type Props = Extract<NonNullable<Page['layout']>[0], { blockType: 'contactSection' }>
|
||||||
|
|
||||||
|
export const ContactSectionBlock: React.FC<Props> = (props) => {
|
||||||
|
const { form } = props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ContactSectionClient
|
||||||
|
{...props}
|
||||||
|
formNode={
|
||||||
|
typeof form === 'object' && form !== null ? (
|
||||||
|
<FormRenderer form={form} />
|
||||||
|
) : (
|
||||||
|
<p className="text-foreground/60">Formularz nie został podpięty.</p>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import type { Block } from 'payload'
|
||||||
|
|
||||||
|
export const ContactSection: Block = {
|
||||||
|
slug: 'contactSection',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'address',
|
||||||
|
type: 'textarea',
|
||||||
|
localized: true,
|
||||||
|
admin: {
|
||||||
|
description: 'Użyj Enter by przejść do nowej linii',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'phone',
|
||||||
|
type: 'text',
|
||||||
|
localized: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'email',
|
||||||
|
type: 'text',
|
||||||
|
localized: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'hours',
|
||||||
|
type: 'textarea',
|
||||||
|
localized: true,
|
||||||
|
admin: {
|
||||||
|
description: 'Użyj Enter by przejść do nowej linii',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'form',
|
||||||
|
type: 'relationship',
|
||||||
|
relationTo: 'forms',
|
||||||
|
required: true,
|
||||||
|
localized: true,
|
||||||
|
admin: {
|
||||||
|
description: 'Wybierz formularz kontaktowy. Dzięki opcji "localized" możesz wybrać inny formularz dla języka polskiego i inny dla niemieckiego.',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@ import { FinalCTABlock } from './FinalCTA/Component'
|
|||||||
|
|
||||||
import { PageHeaderBlock } from './PageHeader/Component'
|
import { PageHeaderBlock } from './PageHeader/Component'
|
||||||
import { ContactInfoBlock } from './ContactInfo/Component'
|
import { ContactInfoBlock } from './ContactInfo/Component'
|
||||||
|
import { ContactSectionBlock } from './ContactSection/Component'
|
||||||
import { BioBlock } from './Bio/Component'
|
import { BioBlock } from './Bio/Component'
|
||||||
import { TeamBlock } from './Team/Component'
|
import { TeamBlock } from './Team/Component'
|
||||||
import { ServicesDetailedBlock } from './ServicesDetailed/Component'
|
import { ServicesDetailedBlock } from './ServicesDetailed/Component'
|
||||||
@@ -25,6 +26,7 @@ import { StatsBlock } from './Stats/Component'
|
|||||||
const blockComponents = {
|
const blockComponents = {
|
||||||
pageHeader: PageHeaderBlock,
|
pageHeader: PageHeaderBlock,
|
||||||
contactInfo: ContactInfoBlock,
|
contactInfo: ContactInfoBlock,
|
||||||
|
contactSection: ContactSectionBlock,
|
||||||
bio: BioBlock,
|
bio: BioBlock,
|
||||||
team: TeamBlock,
|
team: TeamBlock,
|
||||||
servicesDetailed: ServicesDetailedBlock,
|
servicesDetailed: ServicesDetailedBlock,
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ export const ServicesDetailedBlock: React.FC<Props> = ({
|
|||||||
return (
|
return (
|
||||||
<motion.div
|
<motion.div
|
||||||
key={service.id || index}
|
key={service.id || index}
|
||||||
|
id={service.anchor}
|
||||||
variants={{
|
variants={{
|
||||||
hidden: { opacity: 0, y: isFirst ? 24 : 16 },
|
hidden: { opacity: 0, y: isFirst ? 24 : 16 },
|
||||||
visible: {
|
visible: {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { Block } from 'payload'
|
import type { Block } from 'payload'
|
||||||
|
import { buildSlugField } from '@intecion/ipal-kit'
|
||||||
|
|
||||||
export const ServicesDetailed: Block = {
|
export const ServicesDetailed: Block = {
|
||||||
slug: 'servicesDetailed',
|
slug: 'servicesDetailed',
|
||||||
@@ -12,6 +13,7 @@ export const ServicesDetailed: Block = {
|
|||||||
required: true,
|
required: true,
|
||||||
fields: [
|
fields: [
|
||||||
{ name: 'title', type: 'text', required: true },
|
{ name: 'title', type: 'text', required: true },
|
||||||
|
buildSlugField({ name: 'anchor', from: 'title', required: true, localized: false }),
|
||||||
{ name: 'description', type: 'textarea', required: true },
|
{ name: 'description', type: 'textarea', required: true },
|
||||||
{
|
{
|
||||||
name: 'icon',
|
name: 'icon',
|
||||||
|
|||||||
@@ -7,13 +7,15 @@ export const Team: Block = {
|
|||||||
name: 'title',
|
name: 'title',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
localized: true,
|
localized: true,
|
||||||
defaultValue: 'Nasz zespół',
|
defaultValue: ({ locale }: { locale: string }) => locale === 'de' ? 'Unser Team' : 'Nasz zespół',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'subtitle',
|
name: 'subtitle',
|
||||||
type: 'textarea',
|
type: 'textarea',
|
||||||
localized: true,
|
localized: true,
|
||||||
defaultValue: 'Poznaj ekspertów, którzy z zaangażowaniem dbają o Twoje finanse. Każdy z nas wnosi wieloletnie doświadczenie, by zagwarantować Ci spokój i bezpieczeństwo.',
|
defaultValue: ({ locale }: { locale: string }) => locale === 'de'
|
||||||
|
? 'Lernen Sie die Experten kennen, die sich mit Engagement um Ihre Finanzen kümmern. Jeder von uns bringt langjährige Erfahrung mit, um Ihnen Sicherheit und Ruhe zu garantieren.'
|
||||||
|
: 'Poznaj ekspertów, którzy z zaangażowaniem dbają o Twoje finanse. Każdy z nas wnosi wieloletnie doświadczenie, by zagwarantować Ci spokój i bezpieczeństwo.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'members',
|
name: 'members',
|
||||||
|
|||||||
@@ -32,21 +32,38 @@ export const TestimonialsCarousel: React.FC<CarouselProps> = ({ title, items })
|
|||||||
if (!containerRef.current) return
|
if (!containerRef.current) return
|
||||||
const { scrollLeft, scrollWidth, clientWidth } = containerRef.current
|
const { scrollLeft, scrollWidth, clientWidth } = containerRef.current
|
||||||
|
|
||||||
// Obliczamy który element jest najbardziej widoczny
|
// Jeśli przewinęliśmy do samego końca (uwzględniamy drobną tolerancję na różnice pikselowe w przeglądarkach)
|
||||||
// Szerokość jednego elementu (przy założeniu że wszystkie są równe i wypełniają szerokość kontenera na mobile,
|
if (Math.abs(scrollWidth - clientWidth - scrollLeft) < 5) {
|
||||||
// lub połowę na desktop)
|
if (activeIndex !== items.length - 1) {
|
||||||
const cardWidth = containerRef.current.firstElementChild?.clientWidth || clientWidth
|
setActiveIndex(items.length - 1)
|
||||||
const newIndex = Math.round(scrollLeft / cardWidth)
|
|
||||||
|
|
||||||
if (newIndex !== activeIndex) {
|
|
||||||
setActiveIndex(newIndex)
|
|
||||||
}
|
}
|
||||||
}, [activeIndex])
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Znajdź kartę, która jest najbliżej lewej krawędzi (scrollLeft)
|
||||||
|
const children = Array.from(containerRef.current.children) as HTMLElement[]
|
||||||
|
let closestIndex = 0
|
||||||
|
let minDiff = Infinity
|
||||||
|
|
||||||
|
children.forEach((child, index) => {
|
||||||
|
const diff = Math.abs(child.offsetLeft - scrollLeft)
|
||||||
|
if (diff < minDiff) {
|
||||||
|
minDiff = diff
|
||||||
|
closestIndex = index
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (closestIndex !== activeIndex) {
|
||||||
|
setActiveIndex(closestIndex)
|
||||||
|
}
|
||||||
|
}, [activeIndex, items.length])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const el = containerRef.current
|
const el = containerRef.current
|
||||||
if (el) {
|
if (el) {
|
||||||
el.addEventListener('scroll', handleScroll, { passive: true })
|
el.addEventListener('scroll', handleScroll, { passive: true })
|
||||||
|
// Wywołaj raz na start, żeby upewnić się że stan jest zsynchronizowany
|
||||||
|
handleScroll()
|
||||||
return () => el.removeEventListener('scroll', handleScroll)
|
return () => el.removeEventListener('scroll', handleScroll)
|
||||||
}
|
}
|
||||||
}, [handleScroll])
|
}, [handleScroll])
|
||||||
@@ -54,15 +71,17 @@ export const TestimonialsCarousel: React.FC<CarouselProps> = ({ title, items })
|
|||||||
const scrollTo = (index: number) => {
|
const scrollTo = (index: number) => {
|
||||||
if (!containerRef.current) return
|
if (!containerRef.current) return
|
||||||
|
|
||||||
const cardWidth = containerRef.current.firstElementChild?.clientWidth || containerRef.current.clientWidth
|
const children = containerRef.current.children
|
||||||
|
if (index >= 0 && index < children.length) {
|
||||||
|
const child = children[index] as HTMLElement
|
||||||
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
||||||
|
|
||||||
containerRef.current.scrollTo({
|
containerRef.current.scrollTo({
|
||||||
left: index * cardWidth,
|
left: child.offsetLeft,
|
||||||
behavior: prefersReducedMotion ? 'auto' : 'smooth'
|
behavior: prefersReducedMotion ? 'auto' : 'smooth'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const scrollPrev = () => {
|
const scrollPrev = () => {
|
||||||
scrollTo(Math.max(activeIndex - 1, 0))
|
scrollTo(Math.max(activeIndex - 1, 0))
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { FinalCTA } from '../blocks/FinalCTA/config'
|
|||||||
|
|
||||||
import { PageHeader } from '../blocks/PageHeader/config'
|
import { PageHeader } from '../blocks/PageHeader/config'
|
||||||
import { ContactInfo } from '../blocks/ContactInfo/config'
|
import { ContactInfo } from '../blocks/ContactInfo/config'
|
||||||
|
import { ContactSection } from '../blocks/ContactSection/config'
|
||||||
import { TextSection } from '../blocks/TextSection/config'
|
import { TextSection } from '../blocks/TextSection/config'
|
||||||
import { ServicesDetailed } from '../blocks/ServicesDetailed/config'
|
import { ServicesDetailed } from '../blocks/ServicesDetailed/config'
|
||||||
import { Eligibility } from '../blocks/Eligibility/config'
|
import { Eligibility } from '../blocks/Eligibility/config'
|
||||||
@@ -45,6 +46,7 @@ export const Pages: CollectionConfig = {
|
|||||||
blocks: [
|
blocks: [
|
||||||
PageHeader,
|
PageHeader,
|
||||||
ContactInfo,
|
ContactInfo,
|
||||||
|
ContactSection,
|
||||||
Bio,
|
Bio,
|
||||||
Team,
|
Team,
|
||||||
ServicesDetailed,
|
ServicesDetailed,
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
'use client'
|
||||||
|
export { ConsentProvider, CookieBanner, CookieButton, useConsentContext } from '@intecion/ipal-kit/client'
|
||||||
@@ -2,7 +2,8 @@
|
|||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { MessageCircle } from 'lucide-react'
|
import { MessageCircle } from 'lucide-react'
|
||||||
import { motion, AnimatePresence } from 'framer-motion'
|
import { motion, AnimatePresence } from 'framer-motion'
|
||||||
import { useConsentContext } from '@intecion/ipal-kit/client'
|
|
||||||
|
import { useConsentContext } from '../Consent'
|
||||||
|
|
||||||
export const FloatingWhatsApp = ({ whatsappLink, ariaLabel }: { whatsappLink: string, ariaLabel: string }) => {
|
export const FloatingWhatsApp = ({ whatsappLink, ariaLabel }: { whatsappLink: string, ariaLabel: string }) => {
|
||||||
const [isVisible, setIsVisible] = useState(true)
|
const [isVisible, setIsVisible] = useState(true)
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
'use server'
|
||||||
|
|
||||||
|
import { getPayload } from 'payload'
|
||||||
|
import configPromise from '../../payload.config'
|
||||||
|
import { submitForm } from '@intecion/ipal-kit/server'
|
||||||
|
import { headers } from 'next/headers'
|
||||||
|
|
||||||
|
export async function submitContactFormAction(formData: FormData) {
|
||||||
|
const payload = await getPayload({ config: configPromise })
|
||||||
|
|
||||||
|
const formId = formData.get('formId') as string
|
||||||
|
const turnstileToken = formData.get('cf-turnstile-response') as string | undefined
|
||||||
|
|
||||||
|
const data: Record<string, unknown> = {}
|
||||||
|
for (const [key, value] of formData.entries()) {
|
||||||
|
if (key !== 'formId' && key !== 'cf-turnstile-response' && !key.startsWith('$ACTION')) {
|
||||||
|
data[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The consent checkbox usually sends "on" when checked.
|
||||||
|
// We need to convert it to true for the form builder to accept it as checked boolean if the field is a checkbox.
|
||||||
|
// We can do this automatically for all fields that are not empty but 'on' or 'true' or just leave it.
|
||||||
|
// Wait, payload-plugin-form-builder expects boolean for checkboxes.
|
||||||
|
for (const [key, value] of Object.entries(data)) {
|
||||||
|
if (value === 'on') data[key] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const heads = await headers()
|
||||||
|
const ip = heads.get('x-forwarded-for') || heads.get('x-real-ip') || undefined
|
||||||
|
|
||||||
|
const result = await submitForm({
|
||||||
|
payload,
|
||||||
|
formId,
|
||||||
|
data,
|
||||||
|
turnstileToken,
|
||||||
|
ip,
|
||||||
|
consentFieldName: 'consent', // we can set default 'consent' or leave undefined (it defaults to 'consent')
|
||||||
|
})
|
||||||
|
|
||||||
|
// Next.js Server Actions must return plain objects
|
||||||
|
return JSON.parse(JSON.stringify(result))
|
||||||
|
}
|
||||||
@@ -0,0 +1,173 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import React, { useState } from 'react'
|
||||||
|
import { Turnstile, resolveFormMessage } from '@intecion/ipal-kit/client'
|
||||||
|
import { submitContactFormAction } from './actions'
|
||||||
|
|
||||||
|
export function FormClient({
|
||||||
|
form,
|
||||||
|
notifications,
|
||||||
|
turnstileSiteKey
|
||||||
|
}: {
|
||||||
|
form: any;
|
||||||
|
notifications: any;
|
||||||
|
turnstileSiteKey: string
|
||||||
|
}) {
|
||||||
|
const [status, setStatus] = useState<'idle' | 'submitting' | 'success'>('idle')
|
||||||
|
const [errorMsg, setErrorMsg] = useState<string | null>(null)
|
||||||
|
const [turnstileToken, setTurnstileToken] = useState<string | null>(null)
|
||||||
|
|
||||||
|
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
|
||||||
|
e.preventDefault()
|
||||||
|
setStatus('submitting')
|
||||||
|
setErrorMsg(null)
|
||||||
|
|
||||||
|
const formData = new FormData(e.currentTarget)
|
||||||
|
formData.set('formId', form.id.toString())
|
||||||
|
if (turnstileToken) {
|
||||||
|
formData.set('cf-turnstile-response', turnstileToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await submitContactFormAction(formData)
|
||||||
|
if (result.success) {
|
||||||
|
setStatus('success')
|
||||||
|
} else {
|
||||||
|
setStatus('idle')
|
||||||
|
setErrorMsg(resolveFormMessage(result, notifications))
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
setStatus('idle')
|
||||||
|
setErrorMsg('Wystąpił błąd podczas wysyłania formularza. Spróbuj ponownie później.')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status === 'success') {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col items-center justify-center p-8 text-center bg-accent/5 border border-accent/20 rounded-lg">
|
||||||
|
<div className="w-16 h-16 bg-accent text-accent-foreground rounded-full flex items-center justify-center mb-6">
|
||||||
|
<svg className="w-8 h-8" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h3 className="text-2xl font-bold text-foreground mb-4 font-heading">{form.confirmationType === 'message' && form.confirmationMessage ? 'Wiadomość wysłana' : 'Sukces'}</h3>
|
||||||
|
<p className="text-foreground/80 leading-relaxed max-w-md">
|
||||||
|
Dziękujemy za kontakt. Odpowiemy najszybciej jak to możliwe.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={handleSubmit} className="flex flex-col gap-6">
|
||||||
|
{errorMsg && (
|
||||||
|
<div className="p-4 bg-destructive/10 border border-destructive/20 text-destructive rounded-lg text-sm font-medium">
|
||||||
|
{errorMsg}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{form.fields?.map((field: any, idx: number) => {
|
||||||
|
if (field.blockType === 'text') {
|
||||||
|
return (
|
||||||
|
<div key={idx} className="flex flex-col gap-2">
|
||||||
|
{field.label && <label htmlFor={field.name} className="text-sm font-medium text-foreground/80">{field.label} {field.required && <span className="text-destructive">*</span>}</label>}
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id={field.name}
|
||||||
|
name={field.name}
|
||||||
|
required={field.required}
|
||||||
|
placeholder={field.defaultValue || ''}
|
||||||
|
className="flex h-12 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (field.blockType === 'email') {
|
||||||
|
return (
|
||||||
|
<div key={idx} className="flex flex-col gap-2">
|
||||||
|
{field.label && <label htmlFor={field.name} className="text-sm font-medium text-foreground/80">{field.label} {field.required && <span className="text-destructive">*</span>}</label>}
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
id={field.name}
|
||||||
|
name={field.name}
|
||||||
|
required={field.required}
|
||||||
|
placeholder={field.defaultValue || (field.label === 'Adres e-mail' ? '[email protected]' : '[email protected]')}
|
||||||
|
className="flex h-12 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (field.blockType === 'textarea') {
|
||||||
|
return (
|
||||||
|
<div key={idx} className="flex flex-col gap-2">
|
||||||
|
{field.label && <label htmlFor={field.name} className="text-sm font-medium text-foreground/80">{field.label} {field.required && <span className="text-destructive">*</span>}</label>}
|
||||||
|
<textarea
|
||||||
|
id={field.name}
|
||||||
|
name={field.name}
|
||||||
|
required={field.required}
|
||||||
|
rows={5}
|
||||||
|
placeholder={field.defaultValue || ''}
|
||||||
|
className="flex min-h-[120px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (field.blockType === 'select') {
|
||||||
|
return (
|
||||||
|
<div key={idx} className="flex flex-col gap-2">
|
||||||
|
{field.label && <label htmlFor={field.name} className="text-sm font-medium text-foreground/80">{field.label} {field.required && <span className="text-destructive">*</span>}</label>}
|
||||||
|
<select
|
||||||
|
id={field.name}
|
||||||
|
name={field.name}
|
||||||
|
required={field.required}
|
||||||
|
className="flex h-12 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
|
>
|
||||||
|
<option value="">--</option>
|
||||||
|
{field.options?.map((opt: any, oIdx: number) => (
|
||||||
|
<option key={oIdx} value={opt.value}>{opt.label}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (field.blockType === 'checkbox') {
|
||||||
|
return (
|
||||||
|
<div key={idx} className="flex flex-row items-start gap-3 mt-2">
|
||||||
|
<div className="flex items-center h-6">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id={field.name}
|
||||||
|
name={field.name}
|
||||||
|
required={field.required}
|
||||||
|
defaultChecked={field.defaultValue}
|
||||||
|
className="w-5 h-5 rounded border-border text-accent focus:ring-accent"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{field.label && (
|
||||||
|
<label htmlFor={field.name} className="text-sm text-foreground/80 leading-tight">
|
||||||
|
<span dangerouslySetInnerHTML={{ __html: field.label }} />
|
||||||
|
{field.required && <span className="text-destructive ml-1">*</span>}
|
||||||
|
</label>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
})}
|
||||||
|
|
||||||
|
{turnstileSiteKey && (
|
||||||
|
<div className="my-2">
|
||||||
|
<Turnstile siteKey={turnstileSiteKey} onToken={setTurnstileToken} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
disabled={status === 'submitting'}
|
||||||
|
className="mt-4 h-14 bg-accent hover:bg-accent/90 text-white font-bold font-heading tracking-wide uppercase px-8 flex items-center justify-center transition-colors disabled:opacity-70 disabled:cursor-not-allowed"
|
||||||
|
>
|
||||||
|
{status === 'submitting' ? 'Wysyłanie...' : (form.submitButtonLabel || 'Wyślij')}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { getPayload } from 'payload'
|
||||||
|
import configPromise from '../../payload.config'
|
||||||
|
import { FormClient } from './client'
|
||||||
|
import { getNotificationTexts, getSiteIntegrations } from '@intecion/ipal-kit'
|
||||||
|
import { headers } from 'next/headers'
|
||||||
|
|
||||||
|
export async function FormRenderer({ form }: { form: any }) {
|
||||||
|
if (typeof form !== 'object' || !form) return null
|
||||||
|
|
||||||
|
const payload = await getPayload({ config: configPromise })
|
||||||
|
|
||||||
|
// Get current locale from headers or fallback
|
||||||
|
const heads = await headers()
|
||||||
|
const pathname = heads.get('x-pathname') || ''
|
||||||
|
const locale = pathname.split('/').filter(Boolean)[0] === 'de' ? 'de' : 'pl'
|
||||||
|
|
||||||
|
const notifications = await getNotificationTexts({ payload, locale })
|
||||||
|
const siteIntegrations = await getSiteIntegrations<any>(payload, { locale })
|
||||||
|
|
||||||
|
const turnstileSiteKey = siteIntegrations?.turnstile?.siteKey || ''
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormClient
|
||||||
|
form={form}
|
||||||
|
notifications={notifications.form}
|
||||||
|
turnstileSiteKey={turnstileSiteKey}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -29,16 +29,16 @@ export function Header({ navItems = [], whatsappNumber }: { navItems?: any[], wh
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<header
|
<header
|
||||||
className={`fixed top-0 left-0 right-0 z-50 transition-all duration-300 ${
|
className={`fixed top-0 left-0 right-0 z-50 transition-all duration-300 bg-background/90 backdrop-blur-md shadow-sm border-b border-border ${
|
||||||
isScrolled
|
isScrolled
|
||||||
? "bg-background/90 backdrop-blur-md shadow-sm border-b border-border py-4"
|
? "py-4"
|
||||||
: "bg-transparent py-6"
|
: "py-6"
|
||||||
}`}
|
}`}
|
||||||
style={{ paddingRight: 'var(--scrollbar-width)' }}
|
style={{ paddingRight: 'var(--scrollbar-width)' }}
|
||||||
>
|
>
|
||||||
<div className="container-premium flex items-center justify-between">
|
<div className="container-premium flex items-center justify-between">
|
||||||
{/* Logo */}
|
{/* Logo */}
|
||||||
<Link href={`/${currentLocale.code}`} className="group flex items-center gap-3 z-50" aria-label="Finanz- & Lohnbuchhaltung Dipl.-Ing. Janusz Kędzierski">
|
<Link href={`/${currentLocale.code}`} className="group flex items-center gap-3 z-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2 focus-visible:ring-offset-background rounded-sm" aria-label="Finanz- & Lohnbuchhaltung Dipl.-Ing. Janusz Kędzierski">
|
||||||
<div className="w-10 h-10 rounded-none bg-accent text-white flex items-center justify-center font-heading font-bold text-xl group-hover:bg-accent-hover transition-colors shadow-sm">
|
<div className="w-10 h-10 rounded-none bg-accent text-white flex items-center justify-center font-heading font-bold text-xl group-hover:bg-accent-hover transition-colors shadow-sm">
|
||||||
JK
|
JK
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -71,6 +71,8 @@ export interface Config {
|
|||||||
users: User;
|
users: User;
|
||||||
media: Media;
|
media: Media;
|
||||||
pages: Page;
|
pages: Page;
|
||||||
|
forms: Form;
|
||||||
|
'form-submissions': FormSubmission;
|
||||||
'payload-mcp-api-keys': PayloadMcpApiKey;
|
'payload-mcp-api-keys': PayloadMcpApiKey;
|
||||||
'payload-kv': PayloadKv;
|
'payload-kv': PayloadKv;
|
||||||
'payload-locked-documents': PayloadLockedDocument;
|
'payload-locked-documents': PayloadLockedDocument;
|
||||||
@@ -82,6 +84,8 @@ export interface Config {
|
|||||||
users: UsersSelect<false> | UsersSelect<true>;
|
users: UsersSelect<false> | UsersSelect<true>;
|
||||||
media: MediaSelect<false> | MediaSelect<true>;
|
media: MediaSelect<false> | MediaSelect<true>;
|
||||||
pages: PagesSelect<false> | PagesSelect<true>;
|
pages: PagesSelect<false> | PagesSelect<true>;
|
||||||
|
forms: FormsSelect<false> | FormsSelect<true>;
|
||||||
|
'form-submissions': FormSubmissionsSelect<false> | FormSubmissionsSelect<true>;
|
||||||
'payload-mcp-api-keys': PayloadMcpApiKeysSelect<false> | PayloadMcpApiKeysSelect<true>;
|
'payload-mcp-api-keys': PayloadMcpApiKeysSelect<false> | PayloadMcpApiKeysSelect<true>;
|
||||||
'payload-kv': PayloadKvSelect<false> | PayloadKvSelect<true>;
|
'payload-kv': PayloadKvSelect<false> | PayloadKvSelect<true>;
|
||||||
'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>;
|
'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>;
|
||||||
@@ -230,6 +234,25 @@ export interface Page {
|
|||||||
blockName?: string | null;
|
blockName?: string | null;
|
||||||
blockType: 'contactInfo';
|
blockType: 'contactInfo';
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
/**
|
||||||
|
* Użyj Enter by przejść do nowej linii
|
||||||
|
*/
|
||||||
|
address?: string | null;
|
||||||
|
phone?: string | null;
|
||||||
|
email?: string | null;
|
||||||
|
/**
|
||||||
|
* Użyj Enter by przejść do nowej linii
|
||||||
|
*/
|
||||||
|
hours?: string | null;
|
||||||
|
/**
|
||||||
|
* Wybierz formularz kontaktowy. Dzięki opcji "localized" możesz wybrać inny formularz dla języka polskiego i inny dla niemieckiego.
|
||||||
|
*/
|
||||||
|
form: number | Form;
|
||||||
|
id?: string | null;
|
||||||
|
blockName?: string | null;
|
||||||
|
blockType: 'contactSection';
|
||||||
|
}
|
||||||
| {
|
| {
|
||||||
title: string;
|
title: string;
|
||||||
paragraphs: {
|
paragraphs: {
|
||||||
@@ -269,6 +292,10 @@ export interface Page {
|
|||||||
subtitle?: string | null;
|
subtitle?: string | null;
|
||||||
services: {
|
services: {
|
||||||
title: string;
|
title: string;
|
||||||
|
/**
|
||||||
|
* Auto-generated from the title when left empty. You can override it.
|
||||||
|
*/
|
||||||
|
anchor: string;
|
||||||
description: string;
|
description: string;
|
||||||
icon?: ('fileText' | 'calculator' | 'shieldCheck' | 'scale' | 'helpCircle' | 'users' | 'coins') | null;
|
icon?: ('fileText' | 'calculator' | 'shieldCheck' | 'scale' | 'helpCircle' | 'users' | 'coins') | null;
|
||||||
id?: string | null;
|
id?: string | null;
|
||||||
@@ -508,6 +535,193 @@ export interface Page {
|
|||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "forms".
|
||||||
|
*/
|
||||||
|
export interface Form {
|
||||||
|
id: number;
|
||||||
|
title: string;
|
||||||
|
fields?:
|
||||||
|
| (
|
||||||
|
| {
|
||||||
|
name: string;
|
||||||
|
label?: string | null;
|
||||||
|
width?: number | null;
|
||||||
|
required?: boolean | null;
|
||||||
|
defaultValue?: boolean | null;
|
||||||
|
id?: string | null;
|
||||||
|
blockName?: string | null;
|
||||||
|
blockType: 'checkbox';
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
name: string;
|
||||||
|
label?: string | null;
|
||||||
|
width?: number | null;
|
||||||
|
required?: boolean | null;
|
||||||
|
id?: string | null;
|
||||||
|
blockName?: string | null;
|
||||||
|
blockType: 'country';
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
name: string;
|
||||||
|
label?: string | null;
|
||||||
|
width?: number | null;
|
||||||
|
required?: boolean | null;
|
||||||
|
id?: string | null;
|
||||||
|
blockName?: string | null;
|
||||||
|
blockType: 'email';
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
message?: {
|
||||||
|
root: {
|
||||||
|
type: string;
|
||||||
|
children: {
|
||||||
|
type: any;
|
||||||
|
version: number;
|
||||||
|
[k: string]: unknown;
|
||||||
|
}[];
|
||||||
|
direction: ('ltr' | 'rtl') | null;
|
||||||
|
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
|
||||||
|
indent: number;
|
||||||
|
version: number;
|
||||||
|
};
|
||||||
|
[k: string]: unknown;
|
||||||
|
} | null;
|
||||||
|
id?: string | null;
|
||||||
|
blockName?: string | null;
|
||||||
|
blockType: 'message';
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
name: string;
|
||||||
|
label?: string | null;
|
||||||
|
width?: number | null;
|
||||||
|
defaultValue?: number | null;
|
||||||
|
required?: boolean | null;
|
||||||
|
id?: string | null;
|
||||||
|
blockName?: string | null;
|
||||||
|
blockType: 'number';
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
name: string;
|
||||||
|
label?: string | null;
|
||||||
|
width?: number | null;
|
||||||
|
defaultValue?: string | null;
|
||||||
|
placeholder?: string | null;
|
||||||
|
options?:
|
||||||
|
| {
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
id?: string | null;
|
||||||
|
}[]
|
||||||
|
| null;
|
||||||
|
required?: boolean | null;
|
||||||
|
id?: string | null;
|
||||||
|
blockName?: string | null;
|
||||||
|
blockType: 'select';
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
name: string;
|
||||||
|
label?: string | null;
|
||||||
|
width?: number | null;
|
||||||
|
required?: boolean | null;
|
||||||
|
id?: string | null;
|
||||||
|
blockName?: string | null;
|
||||||
|
blockType: 'state';
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
name: string;
|
||||||
|
label?: string | null;
|
||||||
|
width?: number | null;
|
||||||
|
defaultValue?: string | null;
|
||||||
|
required?: boolean | null;
|
||||||
|
id?: string | null;
|
||||||
|
blockName?: string | null;
|
||||||
|
blockType: 'text';
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
name: string;
|
||||||
|
label?: string | null;
|
||||||
|
width?: number | null;
|
||||||
|
defaultValue?: string | null;
|
||||||
|
required?: boolean | null;
|
||||||
|
id?: string | null;
|
||||||
|
blockName?: string | null;
|
||||||
|
blockType: 'textarea';
|
||||||
|
}
|
||||||
|
)[]
|
||||||
|
| null;
|
||||||
|
submitButtonLabel?: string | null;
|
||||||
|
confirmationType?: ('message' | 'redirect') | null;
|
||||||
|
confirmationMessage?: {
|
||||||
|
root: {
|
||||||
|
type: string;
|
||||||
|
children: {
|
||||||
|
type: any;
|
||||||
|
version: number;
|
||||||
|
[k: string]: unknown;
|
||||||
|
}[];
|
||||||
|
direction: ('ltr' | 'rtl') | null;
|
||||||
|
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
|
||||||
|
indent: number;
|
||||||
|
version: number;
|
||||||
|
};
|
||||||
|
[k: string]: unknown;
|
||||||
|
} | null;
|
||||||
|
redirect?: {
|
||||||
|
type?: ('reference' | 'custom') | null;
|
||||||
|
reference?: {
|
||||||
|
relationTo: 'pages';
|
||||||
|
value: number | Page;
|
||||||
|
} | null;
|
||||||
|
url?: string | null;
|
||||||
|
};
|
||||||
|
emails?:
|
||||||
|
| {
|
||||||
|
emailTo?: string | null;
|
||||||
|
cc?: string | null;
|
||||||
|
bcc?: string | null;
|
||||||
|
replyTo?: string | null;
|
||||||
|
emailFrom?: string | null;
|
||||||
|
subject: string;
|
||||||
|
message?: {
|
||||||
|
root: {
|
||||||
|
type: string;
|
||||||
|
children: {
|
||||||
|
type: any;
|
||||||
|
version: number;
|
||||||
|
[k: string]: unknown;
|
||||||
|
}[];
|
||||||
|
direction: ('ltr' | 'rtl') | null;
|
||||||
|
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
|
||||||
|
indent: number;
|
||||||
|
version: number;
|
||||||
|
};
|
||||||
|
[k: string]: unknown;
|
||||||
|
} | null;
|
||||||
|
id?: string | null;
|
||||||
|
}[]
|
||||||
|
| null;
|
||||||
|
updatedAt: string;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "form-submissions".
|
||||||
|
*/
|
||||||
|
export interface FormSubmission {
|
||||||
|
id: number;
|
||||||
|
form: number | Form;
|
||||||
|
submissionData?:
|
||||||
|
| {
|
||||||
|
field: string;
|
||||||
|
value: string;
|
||||||
|
id?: string | null;
|
||||||
|
}[]
|
||||||
|
| null;
|
||||||
|
updatedAt: string;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* API keys control which collections, resources, tools, and prompts MCP clients can access
|
* API keys control which collections, resources, tools, and prompts MCP clients can access
|
||||||
*
|
*
|
||||||
@@ -571,6 +785,14 @@ export interface PayloadLockedDocument {
|
|||||||
relationTo: 'pages';
|
relationTo: 'pages';
|
||||||
value: number | Page;
|
value: number | Page;
|
||||||
} | null)
|
} | null)
|
||||||
|
| ({
|
||||||
|
relationTo: 'forms';
|
||||||
|
value: number | Form;
|
||||||
|
} | null)
|
||||||
|
| ({
|
||||||
|
relationTo: 'form-submissions';
|
||||||
|
value: number | FormSubmission;
|
||||||
|
} | null)
|
||||||
| ({
|
| ({
|
||||||
relationTo: 'payload-mcp-api-keys';
|
relationTo: 'payload-mcp-api-keys';
|
||||||
value: number | PayloadMcpApiKey;
|
value: number | PayloadMcpApiKey;
|
||||||
@@ -696,6 +918,17 @@ export interface PagesSelect<T extends boolean = true> {
|
|||||||
id?: T;
|
id?: T;
|
||||||
blockName?: T;
|
blockName?: T;
|
||||||
};
|
};
|
||||||
|
contactSection?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
address?: T;
|
||||||
|
phone?: T;
|
||||||
|
email?: T;
|
||||||
|
hours?: T;
|
||||||
|
form?: T;
|
||||||
|
id?: T;
|
||||||
|
blockName?: T;
|
||||||
|
};
|
||||||
bio?:
|
bio?:
|
||||||
| T
|
| T
|
||||||
| {
|
| {
|
||||||
@@ -743,6 +976,7 @@ export interface PagesSelect<T extends boolean = true> {
|
|||||||
| T
|
| T
|
||||||
| {
|
| {
|
||||||
title?: T;
|
title?: T;
|
||||||
|
anchor?: T;
|
||||||
description?: T;
|
description?: T;
|
||||||
icon?: T;
|
icon?: T;
|
||||||
id?: T;
|
id?: T;
|
||||||
@@ -986,6 +1220,157 @@ export interface PagesSelect<T extends boolean = true> {
|
|||||||
updatedAt?: T;
|
updatedAt?: T;
|
||||||
createdAt?: T;
|
createdAt?: T;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "forms_select".
|
||||||
|
*/
|
||||||
|
export interface FormsSelect<T extends boolean = true> {
|
||||||
|
title?: T;
|
||||||
|
fields?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
checkbox?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
name?: T;
|
||||||
|
label?: T;
|
||||||
|
width?: T;
|
||||||
|
required?: T;
|
||||||
|
defaultValue?: T;
|
||||||
|
id?: T;
|
||||||
|
blockName?: T;
|
||||||
|
};
|
||||||
|
country?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
name?: T;
|
||||||
|
label?: T;
|
||||||
|
width?: T;
|
||||||
|
required?: T;
|
||||||
|
id?: T;
|
||||||
|
blockName?: T;
|
||||||
|
};
|
||||||
|
email?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
name?: T;
|
||||||
|
label?: T;
|
||||||
|
width?: T;
|
||||||
|
required?: T;
|
||||||
|
id?: T;
|
||||||
|
blockName?: T;
|
||||||
|
};
|
||||||
|
message?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
message?: T;
|
||||||
|
id?: T;
|
||||||
|
blockName?: T;
|
||||||
|
};
|
||||||
|
number?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
name?: T;
|
||||||
|
label?: T;
|
||||||
|
width?: T;
|
||||||
|
defaultValue?: T;
|
||||||
|
required?: T;
|
||||||
|
id?: T;
|
||||||
|
blockName?: T;
|
||||||
|
};
|
||||||
|
select?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
name?: T;
|
||||||
|
label?: T;
|
||||||
|
width?: T;
|
||||||
|
defaultValue?: T;
|
||||||
|
placeholder?: T;
|
||||||
|
options?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
label?: T;
|
||||||
|
value?: T;
|
||||||
|
id?: T;
|
||||||
|
};
|
||||||
|
required?: T;
|
||||||
|
id?: T;
|
||||||
|
blockName?: T;
|
||||||
|
};
|
||||||
|
state?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
name?: T;
|
||||||
|
label?: T;
|
||||||
|
width?: T;
|
||||||
|
required?: T;
|
||||||
|
id?: T;
|
||||||
|
blockName?: T;
|
||||||
|
};
|
||||||
|
text?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
name?: T;
|
||||||
|
label?: T;
|
||||||
|
width?: T;
|
||||||
|
defaultValue?: T;
|
||||||
|
required?: T;
|
||||||
|
id?: T;
|
||||||
|
blockName?: T;
|
||||||
|
};
|
||||||
|
textarea?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
name?: T;
|
||||||
|
label?: T;
|
||||||
|
width?: T;
|
||||||
|
defaultValue?: T;
|
||||||
|
required?: T;
|
||||||
|
id?: T;
|
||||||
|
blockName?: T;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
submitButtonLabel?: T;
|
||||||
|
confirmationType?: T;
|
||||||
|
confirmationMessage?: T;
|
||||||
|
redirect?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
type?: T;
|
||||||
|
reference?: T;
|
||||||
|
url?: T;
|
||||||
|
};
|
||||||
|
emails?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
emailTo?: T;
|
||||||
|
cc?: T;
|
||||||
|
bcc?: T;
|
||||||
|
replyTo?: T;
|
||||||
|
emailFrom?: T;
|
||||||
|
subject?: T;
|
||||||
|
message?: T;
|
||||||
|
id?: T;
|
||||||
|
};
|
||||||
|
updatedAt?: T;
|
||||||
|
createdAt?: T;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "form-submissions_select".
|
||||||
|
*/
|
||||||
|
export interface FormSubmissionsSelect<T extends boolean = true> {
|
||||||
|
form?: T;
|
||||||
|
submissionData?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
field?: T;
|
||||||
|
value?: T;
|
||||||
|
id?: T;
|
||||||
|
};
|
||||||
|
updatedAt?: T;
|
||||||
|
createdAt?: T;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* This interface was referenced by `Config`'s JSON-Schema
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
* via the `definition` "payload-mcp-api-keys_select".
|
* via the `definition` "payload-mcp-api-keys_select".
|
||||||
|
|||||||
+12
-2
@@ -1,6 +1,6 @@
|
|||||||
import { sqliteAdapter } from '@payloadcms/db-sqlite'
|
import { sqliteAdapter } from '@payloadcms/db-sqlite'
|
||||||
import { mcpPlugin } from '@payloadcms/plugin-mcp'
|
import { mcpPlugin } from '@payloadcms/plugin-mcp'
|
||||||
import { ipalKit } from '@intecion/ipal-kit'
|
import { ipalKit, panelSmtpAdapter } from '@intecion/ipal-kit'
|
||||||
import { lexicalEditor } from '@payloadcms/richtext-lexical'
|
import { lexicalEditor } from '@payloadcms/richtext-lexical'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { buildConfig } from 'payload'
|
import { buildConfig } from 'payload'
|
||||||
@@ -43,5 +43,15 @@ export default buildConfig({
|
|||||||
fallback: true,
|
fallback: true,
|
||||||
defaultLocale: i18n.defaultLocale,
|
defaultLocale: i18n.defaultLocale,
|
||||||
},
|
},
|
||||||
plugins: [mcpPlugin({}), ipalKit({ i18n, pages: { slug: 'pages' } })],
|
email: panelSmtpAdapter(),
|
||||||
|
plugins: [
|
||||||
|
mcpPlugin({}),
|
||||||
|
ipalKit({
|
||||||
|
i18n,
|
||||||
|
pages: { slug: 'pages' },
|
||||||
|
forms: {
|
||||||
|
redirectRelationships: ['pages']
|
||||||
|
}
|
||||||
|
})
|
||||||
|
],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { chromium } from '@playwright/test';
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const browser = await chromium.launch();
|
||||||
|
|
||||||
|
// Desktop
|
||||||
|
const desktopContext = await browser.newContext({
|
||||||
|
viewport: { width: 1280, height: 800 }
|
||||||
|
});
|
||||||
|
const desktopPage = await desktopContext.newPage();
|
||||||
|
await desktopPage.goto('http://localhost:3000/pl');
|
||||||
|
// Poczekaj na załadowanie
|
||||||
|
await desktopPage.waitForTimeout(2000);
|
||||||
|
await desktopPage.screenshot({ path: 'screenshot_desktop.png' });
|
||||||
|
await desktopContext.close();
|
||||||
|
|
||||||
|
// Mobile
|
||||||
|
const mobileContext = await browser.newContext({
|
||||||
|
viewport: { width: 375, height: 667 }
|
||||||
|
});
|
||||||
|
const mobilePage = await mobileContext.newPage();
|
||||||
|
await mobilePage.goto('http://localhost:3000/pl');
|
||||||
|
await mobilePage.waitForTimeout(2000);
|
||||||
|
await mobilePage.screenshot({ path: 'screenshot_mobile.png' });
|
||||||
|
await mobileContext.close();
|
||||||
|
|
||||||
|
await browser.close();
|
||||||
|
console.log('Screenshots taken!');
|
||||||
|
})();
|
||||||
Reference in New Issue
Block a user