diff --git a/.agents/context/operations.md b/.agents/context/operations.md index 2ec2372..540d7e6 100644 --- a/.agents/context/operations.md +++ b/.agents/context/operations.md @@ -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. 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 ### Rozwiązanie "Pustego Czarnego Obszaru" @@ -36,3 +43,7 @@ Macierz wynikowa: 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. + +### 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). diff --git a/audit_results.txt b/audit_results.txt new file mode 100644 index 0000000..ff5812c --- /dev/null +++ b/audit_results.txt @@ -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" \ No newline at end of file diff --git a/de_kontakt_form_grep.txt b/de_kontakt_form_grep.txt new file mode 100644 index 0000000..45d8748 --- /dev/null +++ b/de_kontakt_form_grep.txt @@ -0,0 +1,2 @@ +})("data-theme","theme","light",null,["light","dark"],null,false,true)
JK

Kontakt

Haben Sie Fragen zu Ihrer Steuererklärung in Deutschland? Kontaktieren Sie uns — wir helfen Ihnen gerne.

Kontaktinformationen

Adresse

Vom Kunden zu bestätigen

Öffnungszeiten

Mo-Fr: 9:00 - 17:00 +Nach vorheriger Terminvereinbarung

Schreiben Sie uns

diff --git a/detect_dark_de.json b/detect_dark_de.json new file mode 100644 index 0000000..5332b6d --- /dev/null +++ b/detect_dark_de.json @@ -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)" + } +] diff --git a/detect_dark_hero.json b/detect_dark_hero.json new file mode 100644 index 0000000..29c25bf --- /dev/null +++ b/detect_dark_hero.json @@ -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" + } +] diff --git a/detect_de.json b/detect_de.json index bb67b8c..e69de29 100644 --- a/detect_de.json +++ b/detect_de.json @@ -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)" - } -] diff --git a/detect_pl.json b/detect_pl.json index 60c1634..e69de29 100644 --- a/detect_pl.json +++ b/detect_pl.json @@ -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)" - } -] diff --git a/detect_pl_faq.json b/detect_pl_faq.json index 5752cd5..906a3a3 100644 --- a/detect_pl_faq.json +++ b/detect_pl_faq.json @@ -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", "name": "Cream / beige palette", diff --git a/detect_pl_faq_final.json b/detect_pl_faq_final.json new file mode 100644 index 0000000..906a3a3 --- /dev/null +++ b/detect_pl_faq_final.json @@ -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)" + } +] diff --git a/detect_pl_faq_new.json b/detect_pl_faq_new.json new file mode 100644 index 0000000..906a3a3 --- /dev/null +++ b/detect_pl_faq_new.json @@ -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)" + } +] diff --git a/detect_pl_impressum.json b/detect_pl_impressum.json new file mode 100644 index 0000000..9178b5a --- /dev/null +++ b/detect_pl_impressum.json @@ -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)" + } +] diff --git a/detect_pl_jak-sie-umowic.json b/detect_pl_jak-sie-umowic.json new file mode 100644 index 0000000..a1307bf --- /dev/null +++ b/detect_pl_jak-sie-umowic.json @@ -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ł\"" + } +] diff --git a/detect_pl_jak-sie-umowic_final.json b/detect_pl_jak-sie-umowic_final.json new file mode 100644 index 0000000..48ec2c1 --- /dev/null +++ b/detect_pl_jak-sie-umowic_final.json @@ -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)" + } +] diff --git a/detect_pl_kontakt.json b/detect_pl_kontakt.json new file mode 100644 index 0000000..bc18f72 --- /dev/null +++ b/detect_pl_kontakt.json @@ -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\"" + } +] diff --git a/detect_pl_kontakt_final.json b/detect_pl_kontakt_final.json new file mode 100644 index 0000000..aa1cf83 --- /dev/null +++ b/detect_pl_kontakt_final.json @@ -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)" + } +] diff --git a/detect_pl_kontakt_new.json b/detect_pl_kontakt_new.json new file mode 100644 index 0000000..53d0347 --- /dev/null +++ b/detect_pl_kontakt_new.json @@ -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\"" + } +] diff --git a/detect_pl_o-nas.json b/detect_pl_o-nas.json new file mode 100644 index 0000000..c8267f2 --- /dev/null +++ b/detect_pl_o-nas.json @@ -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\"" + } +] diff --git a/detect_pl_o-nas_final.json b/detect_pl_o-nas_final.json new file mode 100644 index 0000000..41e6b25 --- /dev/null +++ b/detect_pl_o-nas_final.json @@ -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)" + } +] diff --git a/detect_pl_polityka-prywatnosci.json b/detect_pl_polityka-prywatnosci.json new file mode 100644 index 0000000..0b2f245 --- /dev/null +++ b/detect_pl_polityka-prywatnosci.json @@ -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)" + } +] diff --git a/detect_pl_uslugi.json b/detect_pl_uslugi.json index ac77476..976e19e 100644 --- a/detect_pl_uslugi.json +++ b/detect_pl_uslugi.json @@ -7,7 +7,7 @@ "category": "quality", "file": "http://localhost:3000/pl/uslugi", "line": 0, - "snippet": "~100 chars/line (aim for <80)" + "snippet": "~86 chars/line (aim for <80)" }, { "antipattern": "line-length", @@ -27,7 +27,7 @@ "category": "quality", "file": "http://localhost:3000/pl/uslugi", "line": 0, - "snippet": "~131 chars/line (aim for <80)" + "snippet": "~86 chars/line (aim for <80)" }, { "antipattern": "line-length", @@ -37,27 +37,7 @@ "category": "quality", "file": "http://localhost:3000/pl/uslugi", "line": 0, - "snippet": "~151 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)" + "snippet": "~86 chars/line (aim for <80)" }, { "antipattern": "cream-palette", @@ -68,5 +48,25 @@ "file": "http://localhost:3000/pl/uslugi", "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/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\"" } ] diff --git a/detect_pl_uslugi_final.json b/detect_pl_uslugi_final.json new file mode 100644 index 0000000..15091f8 --- /dev/null +++ b/detect_pl_uslugi_final.json @@ -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)" + } +] diff --git a/detect_team_dark.json b/detect_team_dark.json new file mode 100644 index 0000000..829b2cc --- /dev/null +++ b/detect_team_dark.json @@ -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" + } +] diff --git a/detect_team_light.json b/detect_team_light.json new file mode 100644 index 0000000..41e6b25 --- /dev/null +++ b/detect_team_light.json @@ -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)" + } +] diff --git a/package.json b/package.json index 01dfc14..2f8c217 100644 --- a/package.json +++ b/package.json @@ -21,10 +21,14 @@ "dependencies": { "@intecion/ipal-kit": "^1.1.2", "@payloadcms/db-postgres": "latest", - "@payloadcms/db-sqlite": "^3.88.0", + "@payloadcms/db-sqlite": "latest", "@payloadcms/next": "latest", + "@payloadcms/plugin-cloud-storage": "^3.89.0", + "@payloadcms/plugin-form-builder": "latest", "@payloadcms/plugin-mcp": "latest", + "@payloadcms/plugin-seo": "^3.89.0", "@payloadcms/richtext-lexical": "latest", + "@payloadcms/storage-s3": "^3.89.0", "@payloadcms/ui": "latest", "@tailwindcss/postcss": "^4.3.3", "clsx": "^2.1.1", @@ -69,5 +73,9 @@ "esbuild", "unrs-resolver" ] + }, + "overrides": { + "@payloadcms/*": "3.89.0", + "payload": "3.89.0" } } diff --git a/pl_kontakt_form_grep.txt b/pl_kontakt_form_grep.txt new file mode 100644 index 0000000..c85740b --- /dev/null +++ b/pl_kontakt_form_grep.txt @@ -0,0 +1,2 @@ +})("data-theme","theme","light",null,["light","dark"],null,false,true)
JK

Kontakt

Masz pytania dotyczące rozliczenia podatkowego w Niemczech? Skontaktuj się z nami — chętnie pomożemy.

Dane kontaktowe

Adres

DO POTWIERDZENIA PRZEZ KLIENTA

Godziny pracy

Pon-Pt: 9:00 - 17:00 +Po wcześniejszym umówieniu

Napisz do nas

diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d6205de..b0be04e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,25 +10,37 @@ importers: dependencies: '@intecion/ipal-kit': specifier: ^1.1.2 - version: 1.1.2(7a514cf7d7dc8f49b191472d316c7395) + version: 1.1.2(7568ca3524200483fde555f829a4b90a) '@payloadcms/db-postgres': specifier: latest - version: 3.88.0(@libsql/client@0.14.0)(payload@3.88.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(supports-color@7.2.0) + version: 3.89.0(@libsql/client@0.14.0)(payload@3.89.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(supports-color@7.2.0) '@payloadcms/db-sqlite': - specifier: ^3.88.0 - version: 3.88.0(@types/pg@8.20.0)(payload@3.88.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(pg@8.20.0)(supports-color@7.2.0) + specifier: latest + version: 3.89.0(@types/pg@8.20.0)(payload@3.89.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(pg@8.20.0)(supports-color@7.2.0) '@payloadcms/next': specifier: latest - version: 3.88.0(@types/react@19.2.14)(graphql@16.14.2)(monaco-editor@0.56.0)(next@16.3.3(@babel/core@7.29.7(supports-color@7.2.0))(@playwright/test@1.59.1)(@types/node@24.12.3)(babel-plugin-macros@3.1.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.77.4))(payload@3.88.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(supports-color@7.2.0)(typescript@6.0.3) + version: 3.89.0(@types/react@19.2.14)(graphql@16.14.2)(monaco-editor@0.56.0)(next@16.3.3(@babel/core@7.29.7(supports-color@7.2.0))(@playwright/test@1.59.1)(@types/node@24.12.3)(babel-plugin-macros@3.1.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.77.4))(payload@3.89.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(supports-color@7.2.0)(typescript@6.0.3) + '@payloadcms/plugin-cloud-storage': + specifier: ^3.89.0 + version: 3.89.0(@types/react@19.2.14)(monaco-editor@0.56.0)(next@16.3.3(@babel/core@7.29.7(supports-color@7.2.0))(@playwright/test@1.59.1)(@types/node@24.12.3)(babel-plugin-macros@3.1.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.77.4))(payload@3.89.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(supports-color@7.2.0)(typescript@6.0.3) + '@payloadcms/plugin-form-builder': + specifier: latest + version: 3.89.0(@types/react@19.2.14)(monaco-editor@0.56.0)(next@16.3.3(@babel/core@7.29.7(supports-color@7.2.0))(@playwright/test@1.59.1)(@types/node@24.12.3)(babel-plugin-macros@3.1.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.77.4))(payload@3.89.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(supports-color@7.2.0)(typescript@6.0.3) '@payloadcms/plugin-mcp': specifier: latest - version: 3.88.0(next@16.3.3(@babel/core@7.29.7(supports-color@7.2.0))(@playwright/test@1.59.1)(@types/node@24.12.3)(babel-plugin-macros@3.1.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.77.4))(payload@3.88.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(supports-color@7.2.0) + version: 3.89.0(next@16.3.3(@babel/core@7.29.7(supports-color@7.2.0))(@playwright/test@1.59.1)(@types/node@24.12.3)(babel-plugin-macros@3.1.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.77.4))(payload@3.89.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(supports-color@7.2.0) + '@payloadcms/plugin-seo': + specifier: ^3.89.0 + version: 3.89.0(@types/react@19.2.14)(monaco-editor@0.56.0)(next@16.3.3(@babel/core@7.29.7(supports-color@7.2.0))(@playwright/test@1.59.1)(@types/node@24.12.3)(babel-plugin-macros@3.1.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.77.4))(payload@3.89.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(supports-color@7.2.0)(typescript@6.0.3) '@payloadcms/richtext-lexical': specifier: latest - version: 3.88.0(@faceless-ui/modal@3.0.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(@faceless-ui/scroll-info@2.0.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(@payloadcms/next@3.88.0(@types/react@19.2.14)(graphql@16.14.2)(monaco-editor@0.56.0)(next@16.3.3(@babel/core@7.29.7(supports-color@7.2.0))(@playwright/test@1.59.1)(@types/node@24.12.3)(babel-plugin-macros@3.1.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.77.4))(payload@3.88.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(supports-color@7.2.0)(typescript@6.0.3))(@types/react@19.2.14)(monaco-editor@0.56.0)(next@16.3.3(@babel/core@7.29.7(supports-color@7.2.0))(@playwright/test@1.59.1)(@types/node@24.12.3)(babel-plugin-macros@3.1.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.77.4))(payload@3.88.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(supports-color@7.2.0)(typescript@6.0.3)(yjs@13.6.32) + version: 3.89.0(@faceless-ui/modal@3.0.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(@faceless-ui/scroll-info@2.0.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(@payloadcms/next@3.89.0(@types/react@19.2.14)(graphql@16.14.2)(monaco-editor@0.56.0)(next@16.3.3(@babel/core@7.29.7(supports-color@7.2.0))(@playwright/test@1.59.1)(@types/node@24.12.3)(babel-plugin-macros@3.1.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.77.4))(payload@3.89.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(supports-color@7.2.0)(typescript@6.0.3))(@types/react@19.2.14)(monaco-editor@0.56.0)(next@16.3.3(@babel/core@7.29.7(supports-color@7.2.0))(@playwright/test@1.59.1)(@types/node@24.12.3)(babel-plugin-macros@3.1.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.77.4))(payload@3.89.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(supports-color@7.2.0)(typescript@6.0.3)(yjs@13.6.32) + '@payloadcms/storage-s3': + specifier: ^3.89.0 + version: 3.89.0(@types/react@19.2.14)(monaco-editor@0.56.0)(next@16.3.3(@babel/core@7.29.7(supports-color@7.2.0))(@playwright/test@1.59.1)(@types/node@24.12.3)(babel-plugin-macros@3.1.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.77.4))(payload@3.89.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(supports-color@7.2.0)(typescript@6.0.3) '@payloadcms/ui': specifier: latest - version: 3.88.0(@types/react@19.2.14)(monaco-editor@0.56.0)(next@16.3.3(@babel/core@7.29.7(supports-color@7.2.0))(@playwright/test@1.59.1)(@types/node@24.12.3)(babel-plugin-macros@3.1.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.77.4))(payload@3.88.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(supports-color@7.2.0)(typescript@6.0.3) + version: 3.89.0(@types/react@19.2.14)(monaco-editor@0.56.0)(next@16.3.3(@babel/core@7.29.7(supports-color@7.2.0))(@playwright/test@1.59.1)(@types/node@24.12.3)(babel-plugin-macros@3.1.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.77.4))(payload@3.89.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(supports-color@7.2.0)(typescript@6.0.3) '@tailwindcss/postcss': specifier: ^4.3.3 version: 4.3.3 @@ -58,7 +70,7 @@ importers: version: 0.4.6(react-dom@19.2.6(react@19.2.6))(react@19.2.6) payload: specifier: latest - version: 3.88.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3) + version: 3.89.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3) postcss: specifier: ^8.5.26 version: 8.5.26 @@ -1575,88 +1587,88 @@ packages: resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} engines: {node: '>=12.4.0'} - '@payloadcms/db-postgres@3.88.0': - resolution: {integrity: sha512-uP2b3yH8MulguWSH7j8vJaFX3ols4fVKPtsvZ49XQvDsafBAAUZjc1aCldo1ozoVfhC46tx4pv0ovtHQc8zdcA==} + '@payloadcms/db-postgres@3.89.0': + resolution: {integrity: sha512-Z6sa/8ahAYqW7ttcAL4Y1+50w5SaEN0hJdNVJCzgBFB/1nnrcmFaKO5m++aeeTsAdffSRXV9J8G5n8fBChMr5w==} peerDependencies: - payload: 3.88.0 + payload: 3.89.0 - '@payloadcms/db-sqlite@3.88.0': - resolution: {integrity: sha512-ZszS6LfejrfSmYNjb7EkUjt6A9zUPDNLSYN3Y5uxWqTRL5yDehnR2ZW/cbpvECtieX2cZ8z3bst8WVnWCtFPNw==} + '@payloadcms/db-sqlite@3.89.0': + resolution: {integrity: sha512-0qY1k3fGxAXoe3r44mm3Iku1BIUeRgjDYq0kIvRdcls+PVOT/7nXsIZgTkjrNn5Cu4mujLIpTVwDGsTcN0fI2w==} peerDependencies: - payload: 3.88.0 + payload: 3.89.0 - '@payloadcms/drizzle@3.88.0': - resolution: {integrity: sha512-qtPVSLjAFnQl1GwaQPhmnYTwCCWgTnRm4ps5yJxmN3RPdLF6S77gvqZMnC4HTeH7YADQJW3nFgQCVYExxWE6hA==} + '@payloadcms/drizzle@3.89.0': + resolution: {integrity: sha512-/Ijf87WfIaIGDrlAQBOXcNxRZy+IDfpZQvpmJpA4BwO7PEVMqU6nHSUYVgbFHy6+4XYHvsTp31/4xo06yQwiOw==} peerDependencies: - payload: 3.88.0 + payload: 3.89.0 - '@payloadcms/graphql@3.88.0': - resolution: {integrity: sha512-ync+bPFl0UacwBs1wVQkPlaSWWQJc4LjGG9OuRHEW7wYW3aeVQ+H2zfdFL6/cVJGP3zOq+XIlvYYw8G86Ki+Vw==} + '@payloadcms/graphql@3.89.0': + resolution: {integrity: sha512-oa77vdW2yKwla8hXKDQOYvCLZNn6rmR8Th2QgGfq3gAvlR5hEnZ7KH9VK23AfkimXRuHKnI0tH8K+OaBOKdStA==} hasBin: true peerDependencies: graphql: ^16.8.1 - payload: 3.88.0 + payload: 3.89.0 - '@payloadcms/next@3.88.0': - resolution: {integrity: sha512-3f2YOGSmtTp85YnlJm9FusRgshKJbiLI8uXs8iQNMwU7RZJnLCURG8iv/6sjKHO0owIfw3vIL5qgxdtIBuQwpA==} + '@payloadcms/next@3.89.0': + resolution: {integrity: sha512-1PEzFEQIRhrOQh1cKegmNcaOQxWXA5wflxMkgqZqKZN62i64/gMB4MWspyfBEHjiAT0NPFDZyK7e522qEOj4vg==} engines: {node: ^18.20.2 || >=20.9.0} peerDependencies: graphql: ^16.8.1 next: '>=15.2.9 <15.3.0 || >=15.3.9 <15.4.0 || >=15.4.11 <15.5.0 || >=16.2.6 <17.0.0' - payload: 3.88.0 + payload: 3.89.0 - '@payloadcms/plugin-cloud-storage@3.88.0': - resolution: {integrity: sha512-wIcRVedtBZIQMu7dpSE1TOJKJd0HuVSUNPrV2KxIbtqQFm+ly34NCJYY0m1+K0YS6my8BKdcB4jj2exg8454Ig==} + '@payloadcms/plugin-cloud-storage@3.89.0': + resolution: {integrity: sha512-dzaT6ybIw0WU1bT2tit3ZRzPb4imbV8m8SWYzOE0gzbQQuDLT69K5RPK3o4U3NK0iEFSsA+InU/xP5kQFDaTQw==} peerDependencies: - payload: 3.88.0 + payload: 3.89.0 react: ^19.0.1 || ^19.1.2 || ^19.2.1 react-dom: ^19.0.1 || ^19.1.2 || ^19.2.1 - '@payloadcms/plugin-form-builder@3.88.0': - resolution: {integrity: sha512-C915jEHADVN9xW7fSq/bYIZig1zjfQo+t4wrgiqjOCmB81oMgyqLRB3RlL2e6y4PzBUtgss2VS40vn/tVD/61Q==} + '@payloadcms/plugin-form-builder@3.89.0': + resolution: {integrity: sha512-Niq+ukQmoS7Gq6ZLEE1YxW09YSFWhwYIgVMFmsy6jEHrEnNVIDhISmxRTtZj6me/NArkLpEYAUYfXqskttXzPg==} peerDependencies: - payload: 3.88.0 + payload: 3.89.0 react: ^19.0.1 || ^19.1.2 || ^19.2.1 react-dom: ^19.0.1 || ^19.1.2 || ^19.2.1 - '@payloadcms/plugin-mcp@3.88.0': - resolution: {integrity: sha512-+L4SeiYFs4hBfnwmoyxChs2jAL/dcSW16YXRQ+7ua8AIYJRveNGOHtcFZgKH0t/bHE1nK/1df9Wy2EpmBnAcTw==} + '@payloadcms/plugin-mcp@3.89.0': + resolution: {integrity: sha512-0EhNJhOXpKsNE4rGBlkJ+facu+i8/VC8fOmMx3tVr+tA7C31ppjtMwWSXt6it7gualkyJctmvoQy942DiepzNQ==} peerDependencies: - payload: 3.88.0 + payload: 3.89.0 - '@payloadcms/plugin-seo@3.88.0': - resolution: {integrity: sha512-3eFWoqpXZQZ3ghdFksPo7nQ08XUG0PXWc/qf1ibtXiKPlMN43Asa8Z9wGNKYtkDhIM+OYi6EmlFzRLWkCRcwrg==} + '@payloadcms/plugin-seo@3.89.0': + resolution: {integrity: sha512-LmH1gKAXiIaxe5xPGqizMaCOHah+wn2nfl5Rxi2Mr6jAFG/XnmkYzlXeIdduQiGx6X4f+sDHkgqm4+YI6A/89Q==} peerDependencies: - payload: 3.88.0 + payload: 3.89.0 react: ^19.0.1 || ^19.1.2 || ^19.2.1 react-dom: ^19.0.1 || ^19.1.2 || ^19.2.1 - '@payloadcms/richtext-lexical@3.88.0': - resolution: {integrity: sha512-lY8oIUiggykWKB3ljMfSidxBtA8ahMq0Qqp28OuvaCNIDp0HTMIPxcHvFOQZngMqtQHhpautD6vcQR0Q9CEHHQ==} + '@payloadcms/richtext-lexical@3.89.0': + resolution: {integrity: sha512-IV9sKT/fYptRJXk1XfIKW6V2sBWma4hofBMPI3b1Ab0ClKlyudwrtRtqv9cZzxwoNMYhtC8h+3SQMOq10eI2bQ==} engines: {node: ^18.20.2 || >=20.9.0} peerDependencies: '@faceless-ui/modal': 3.0.0 '@faceless-ui/scroll-info': 2.0.0 - '@payloadcms/next': 3.88.0 - payload: 3.88.0 + '@payloadcms/next': 3.89.0 + payload: 3.89.0 react: ^19.0.1 || ^19.1.2 || ^19.2.1 react-dom: ^19.0.1 || ^19.1.2 || ^19.2.1 - '@payloadcms/storage-s3@3.88.0': - resolution: {integrity: sha512-+35JRD4y7ImnIJuPSBhECHGGml0A+xju9BT/zW+lRUycDvvA0cqZNP+t95OVoZrxIB6V75Vg8GKPvAaRAmgOEw==} + '@payloadcms/storage-s3@3.89.0': + resolution: {integrity: sha512-IEx1Wz4wpvJR07sP7ni+YqiyZkeIuk/iD5YFMAkQrC2whQNgJRwEn7yulC+PGGSrRWMV4ryIHF363rTBVkDmVw==} engines: {node: ^18.20.2 || >=20.9.0} peerDependencies: - payload: 3.88.0 + payload: 3.89.0 - '@payloadcms/translations@3.88.0': - resolution: {integrity: sha512-AZVQjjW5pKxO+K9ODeh6yw6/EfXK1hxGPw55KzzDC5srO3hjNskgyWtkauKvbz+S0+xFspANbRc4OD/Rg/74WA==} + '@payloadcms/translations@3.89.0': + resolution: {integrity: sha512-XLPOdv9NoPcqr21zBTu0uyJk0qGRIF7JVl2H2UTecpweQSMlQCackhUKWz1LKBO7QcKgkNSVHFnMNcizRDcPDg==} - '@payloadcms/ui@3.88.0': - resolution: {integrity: sha512-iwQhHPDn4Oj2bPg1UV467BoxfUNo1rUmD8jMZaw2Vtt/nmxAPl3W3CWN/CsOX/yITuPuGHJxznOg4PZNMF3gEg==} + '@payloadcms/ui@3.89.0': + resolution: {integrity: sha512-jKc8kgSjaICZ39sNC4dr4MLkAj2lR9ikzSwcHzPGOsJlleOVPJq5jM9bmAnqEOhtbpPNl0CGlpo6RIeECnBmwg==} engines: {node: ^18.20.2 || >=20.9.0} peerDependencies: next: '>=15.2.9 <15.3.0 || >=15.3.9 <15.4.0 || >=15.4.11 <15.5.0 || >=16.2.6 <17.0.0' - payload: 3.88.0 + payload: 3.89.0 react: ^19.0.1 || ^19.1.2 || ^19.2.1 react-dom: ^19.0.1 || ^19.1.2 || ^19.2.1 @@ -4208,8 +4220,8 @@ packages: pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} - payload@3.88.0: - resolution: {integrity: sha512-O7zuS80bvEGLte+7xZjwN05+ox5BCsGcQT2M6+CTote07JQOOvHJoiuoyQFw6cUElcFTWGMC5dy03w7J7sTYGg==} + payload@3.89.0: + resolution: {integrity: sha512-n0rDsMDTlKkNDqNllSLow1xEn2XaktD2oyi6Gl7i/LAD7xRBaS+tj27ca9EeNM5QmoGSd734UCHYj5Vr4JQYNA==} engines: {node: ^18.20.2 || >=20.9.0} hasBin: true peerDependencies: @@ -6211,17 +6223,17 @@ snapshots: '@img/sharp-win32-x64@0.35.4': optional: true - '@intecion/ipal-kit@1.1.2(7a514cf7d7dc8f49b191472d316c7395)': + '@intecion/ipal-kit@1.1.2(7568ca3524200483fde555f829a4b90a)': dependencies: - '@payloadcms/next': 3.88.0(@types/react@19.2.14)(graphql@16.14.2)(monaco-editor@0.56.0)(next@16.3.3(@babel/core@7.29.7(supports-color@7.2.0))(@playwright/test@1.59.1)(@types/node@24.12.3)(babel-plugin-macros@3.1.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.77.4))(payload@3.88.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(supports-color@7.2.0)(typescript@6.0.3) - '@payloadcms/plugin-form-builder': 3.88.0(@types/react@19.2.14)(monaco-editor@0.56.0)(next@16.3.3(@babel/core@7.29.7(supports-color@7.2.0))(@playwright/test@1.59.1)(@types/node@24.12.3)(babel-plugin-macros@3.1.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.77.4))(payload@3.88.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(supports-color@7.2.0)(typescript@6.0.3) - '@payloadcms/plugin-seo': 3.88.0(@types/react@19.2.14)(monaco-editor@0.56.0)(next@16.3.3(@babel/core@7.29.7(supports-color@7.2.0))(@playwright/test@1.59.1)(@types/node@24.12.3)(babel-plugin-macros@3.1.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.77.4))(payload@3.88.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(supports-color@7.2.0)(typescript@6.0.3) - '@payloadcms/storage-s3': 3.88.0(@types/react@19.2.14)(monaco-editor@0.56.0)(next@16.3.3(@babel/core@7.29.7(supports-color@7.2.0))(@playwright/test@1.59.1)(@types/node@24.12.3)(babel-plugin-macros@3.1.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.77.4))(payload@3.88.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(supports-color@7.2.0)(typescript@6.0.3) - '@payloadcms/ui': 3.88.0(@types/react@19.2.14)(monaco-editor@0.56.0)(next@16.3.3(@babel/core@7.29.7(supports-color@7.2.0))(@playwright/test@1.59.1)(@types/node@24.12.3)(babel-plugin-macros@3.1.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.77.4))(payload@3.88.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(supports-color@7.2.0)(typescript@6.0.3) + '@payloadcms/next': 3.89.0(@types/react@19.2.14)(graphql@16.14.2)(monaco-editor@0.56.0)(next@16.3.3(@babel/core@7.29.7(supports-color@7.2.0))(@playwright/test@1.59.1)(@types/node@24.12.3)(babel-plugin-macros@3.1.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.77.4))(payload@3.89.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(supports-color@7.2.0)(typescript@6.0.3) + '@payloadcms/plugin-form-builder': 3.89.0(@types/react@19.2.14)(monaco-editor@0.56.0)(next@16.3.3(@babel/core@7.29.7(supports-color@7.2.0))(@playwright/test@1.59.1)(@types/node@24.12.3)(babel-plugin-macros@3.1.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.77.4))(payload@3.89.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(supports-color@7.2.0)(typescript@6.0.3) + '@payloadcms/plugin-seo': 3.89.0(@types/react@19.2.14)(monaco-editor@0.56.0)(next@16.3.3(@babel/core@7.29.7(supports-color@7.2.0))(@playwright/test@1.59.1)(@types/node@24.12.3)(babel-plugin-macros@3.1.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.77.4))(payload@3.89.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(supports-color@7.2.0)(typescript@6.0.3) + '@payloadcms/storage-s3': 3.89.0(@types/react@19.2.14)(monaco-editor@0.56.0)(next@16.3.3(@babel/core@7.29.7(supports-color@7.2.0))(@playwright/test@1.59.1)(@types/node@24.12.3)(babel-plugin-macros@3.1.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.77.4))(payload@3.89.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(supports-color@7.2.0)(typescript@6.0.3) + '@payloadcms/ui': 3.89.0(@types/react@19.2.14)(monaco-editor@0.56.0)(next@16.3.3(@babel/core@7.29.7(supports-color@7.2.0))(@playwright/test@1.59.1)(@types/node@24.12.3)(babel-plugin-macros@3.1.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.77.4))(payload@3.89.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(supports-color@7.2.0)(typescript@6.0.3) lucide-react: 0.400.0(react@19.2.6) next: 16.3.3(@babel/core@7.29.7(supports-color@7.2.0))(@playwright/test@1.59.1)(@types/node@24.12.3)(babel-plugin-macros@3.1.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.77.4) nodemailer: 8.0.11 - payload: 3.88.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3) + payload: 3.89.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) server-only: 0.0.1 @@ -6571,14 +6583,14 @@ snapshots: '@nolyfill/is-core-module@1.0.39': {} - '@payloadcms/db-postgres@3.88.0(@libsql/client@0.14.0)(payload@3.88.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(supports-color@7.2.0)': + '@payloadcms/db-postgres@3.89.0(@libsql/client@0.14.0)(payload@3.89.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(supports-color@7.2.0)': dependencies: - '@payloadcms/drizzle': 3.88.0(@libsql/client@0.14.0)(@types/pg@8.20.0)(payload@3.88.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(pg@8.20.0) + '@payloadcms/drizzle': 3.89.0(@libsql/client@0.14.0)(@types/pg@8.20.0)(payload@3.89.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(pg@8.20.0) '@types/pg': 8.20.0 console-table-printer: 2.12.1 drizzle-kit: 0.31.7(supports-color@7.2.0) drizzle-orm: 0.45.2(@libsql/client@0.14.0)(@types/pg@8.20.0)(pg@8.20.0) - payload: 3.88.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3) + payload: 3.89.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3) pg: 8.20.0 prompts: 2.4.2 to-snake-case: 1.0.0 @@ -6614,14 +6626,14 @@ snapshots: - sqlite3 - supports-color - '@payloadcms/db-sqlite@3.88.0(@types/pg@8.20.0)(payload@3.88.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(pg@8.20.0)(supports-color@7.2.0)': + '@payloadcms/db-sqlite@3.89.0(@types/pg@8.20.0)(payload@3.89.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(pg@8.20.0)(supports-color@7.2.0)': dependencies: '@libsql/client': 0.14.0 - '@payloadcms/drizzle': 3.88.0(@libsql/client@0.14.0)(@types/pg@8.20.0)(payload@3.88.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(pg@8.20.0) + '@payloadcms/drizzle': 3.89.0(@libsql/client@0.14.0)(@types/pg@8.20.0)(payload@3.89.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(pg@8.20.0) console-table-printer: 2.12.1 drizzle-kit: 0.31.7(supports-color@7.2.0) drizzle-orm: 0.45.2(@libsql/client@0.14.0)(@types/pg@8.20.0)(pg@8.20.0) - payload: 3.88.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3) + payload: 3.89.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3) prompts: 2.4.2 to-snake-case: 1.0.0 uuid: 13.0.2 @@ -6658,12 +6670,12 @@ snapshots: - supports-color - utf-8-validate - '@payloadcms/drizzle@3.88.0(@libsql/client@0.14.0)(@types/pg@8.20.0)(payload@3.88.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(pg@8.20.0)': + '@payloadcms/drizzle@3.89.0(@libsql/client@0.14.0)(@types/pg@8.20.0)(payload@3.89.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(pg@8.20.0)': dependencies: console-table-printer: 2.12.1 dequal: 2.0.3 drizzle-orm: 0.45.2(@libsql/client@0.14.0)(@types/pg@8.20.0)(pg@8.20.0) - payload: 3.88.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3) + payload: 3.89.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3) prompts: 2.4.2 to-snake-case: 1.0.0 uuid: 13.0.2 @@ -6698,25 +6710,25 @@ snapshots: - sql.js - sqlite3 - '@payloadcms/graphql@3.88.0(graphql@16.14.2)(payload@3.88.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(typescript@6.0.3)': + '@payloadcms/graphql@3.89.0(graphql@16.14.2)(payload@3.89.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(typescript@6.0.3)': dependencies: graphql: 16.14.2 graphql-scalars: 1.22.2(graphql@16.14.2) - payload: 3.88.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3) + payload: 3.89.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3) pluralize: 8.0.0 ts-essentials: 10.0.3(typescript@6.0.3) tsx: 4.22.4 transitivePeerDependencies: - typescript - '@payloadcms/next@3.88.0(@types/react@19.2.14)(graphql@16.14.2)(monaco-editor@0.56.0)(next@16.3.3(@babel/core@7.29.7(supports-color@7.2.0))(@playwright/test@1.59.1)(@types/node@24.12.3)(babel-plugin-macros@3.1.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.77.4))(payload@3.88.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(supports-color@7.2.0)(typescript@6.0.3)': + '@payloadcms/next@3.89.0(@types/react@19.2.14)(graphql@16.14.2)(monaco-editor@0.56.0)(next@16.3.3(@babel/core@7.29.7(supports-color@7.2.0))(@playwright/test@1.59.1)(@types/node@24.12.3)(babel-plugin-macros@3.1.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.77.4))(payload@3.89.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(supports-color@7.2.0)(typescript@6.0.3)': dependencies: '@dnd-kit/core': 6.3.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@dnd-kit/modifiers': 9.0.0(@dnd-kit/core@6.3.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react@19.2.6) '@dnd-kit/sortable': 10.0.0(@dnd-kit/core@6.3.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react@19.2.6) - '@payloadcms/graphql': 3.88.0(graphql@16.14.2)(payload@3.88.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(typescript@6.0.3) - '@payloadcms/translations': 3.88.0 - '@payloadcms/ui': 3.88.0(@types/react@19.2.14)(monaco-editor@0.56.0)(next@16.3.3(@babel/core@7.29.7(supports-color@7.2.0))(@playwright/test@1.59.1)(@types/node@24.12.3)(babel-plugin-macros@3.1.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.77.4))(payload@3.88.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(supports-color@7.2.0)(typescript@6.0.3) + '@payloadcms/graphql': 3.89.0(graphql@16.14.2)(payload@3.89.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(typescript@6.0.3) + '@payloadcms/translations': 3.89.0 + '@payloadcms/ui': 3.89.0(@types/react@19.2.14)(monaco-editor@0.56.0)(next@16.3.3(@babel/core@7.29.7(supports-color@7.2.0))(@playwright/test@1.59.1)(@types/node@24.12.3)(babel-plugin-macros@3.1.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.77.4))(payload@3.89.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(supports-color@7.2.0)(typescript@6.0.3) busboy: 1.6.0 dequal: 2.0.3 file-type: 21.3.4(supports-color@7.2.0) @@ -6726,7 +6738,7 @@ snapshots: http-status: 2.1.0 next: 16.3.3(@babel/core@7.29.7(supports-color@7.2.0))(@playwright/test@1.59.1)(@types/node@24.12.3)(babel-plugin-macros@3.1.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.77.4) path-to-regexp: 6.3.0 - payload: 3.88.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3) + payload: 3.89.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3) qs-esm: 8.0.1 sass: 1.77.4 uuid: 13.0.2 @@ -6738,11 +6750,11 @@ snapshots: - supports-color - typescript - '@payloadcms/plugin-cloud-storage@3.88.0(@types/react@19.2.14)(monaco-editor@0.56.0)(next@16.3.3(@babel/core@7.29.7(supports-color@7.2.0))(@playwright/test@1.59.1)(@types/node@24.12.3)(babel-plugin-macros@3.1.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.77.4))(payload@3.88.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(supports-color@7.2.0)(typescript@6.0.3)': + '@payloadcms/plugin-cloud-storage@3.89.0(@types/react@19.2.14)(monaco-editor@0.56.0)(next@16.3.3(@babel/core@7.29.7(supports-color@7.2.0))(@playwright/test@1.59.1)(@types/node@24.12.3)(babel-plugin-macros@3.1.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.77.4))(payload@3.89.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(supports-color@7.2.0)(typescript@6.0.3)': dependencies: - '@payloadcms/ui': 3.88.0(@types/react@19.2.14)(monaco-editor@0.56.0)(next@16.3.3(@babel/core@7.29.7(supports-color@7.2.0))(@playwright/test@1.59.1)(@types/node@24.12.3)(babel-plugin-macros@3.1.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.77.4))(payload@3.88.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(supports-color@7.2.0)(typescript@6.0.3) + '@payloadcms/ui': 3.89.0(@types/react@19.2.14)(monaco-editor@0.56.0)(next@16.3.3(@babel/core@7.29.7(supports-color@7.2.0))(@playwright/test@1.59.1)(@types/node@24.12.3)(babel-plugin-macros@3.1.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.77.4))(payload@3.89.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(supports-color@7.2.0)(typescript@6.0.3) find-node-modules: 2.1.3 - payload: 3.88.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3) + payload: 3.89.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3) range-parser: 1.3.0 react: 19.2.6 react-dom: 19.2.6(react@19.2.6) @@ -6753,12 +6765,12 @@ snapshots: - supports-color - typescript - '@payloadcms/plugin-form-builder@3.88.0(@types/react@19.2.14)(monaco-editor@0.56.0)(next@16.3.3(@babel/core@7.29.7(supports-color@7.2.0))(@playwright/test@1.59.1)(@types/node@24.12.3)(babel-plugin-macros@3.1.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.77.4))(payload@3.88.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(supports-color@7.2.0)(typescript@6.0.3)': + '@payloadcms/plugin-form-builder@3.89.0(@types/react@19.2.14)(monaco-editor@0.56.0)(next@16.3.3(@babel/core@7.29.7(supports-color@7.2.0))(@playwright/test@1.59.1)(@types/node@24.12.3)(babel-plugin-macros@3.1.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.77.4))(payload@3.89.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(supports-color@7.2.0)(typescript@6.0.3)': dependencies: - '@payloadcms/translations': 3.88.0 - '@payloadcms/ui': 3.88.0(@types/react@19.2.14)(monaco-editor@0.56.0)(next@16.3.3(@babel/core@7.29.7(supports-color@7.2.0))(@playwright/test@1.59.1)(@types/node@24.12.3)(babel-plugin-macros@3.1.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.77.4))(payload@3.88.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(supports-color@7.2.0)(typescript@6.0.3) + '@payloadcms/translations': 3.89.0 + '@payloadcms/ui': 3.89.0(@types/react@19.2.14)(monaco-editor@0.56.0)(next@16.3.3(@babel/core@7.29.7(supports-color@7.2.0))(@playwright/test@1.59.1)(@types/node@24.12.3)(babel-plugin-macros@3.1.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.77.4))(payload@3.89.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(supports-color@7.2.0)(typescript@6.0.3) escape-html: 1.0.3 - payload: 3.88.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3) + payload: 3.89.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) transitivePeerDependencies: @@ -6768,24 +6780,24 @@ snapshots: - supports-color - typescript - '@payloadcms/plugin-mcp@3.88.0(next@16.3.3(@babel/core@7.29.7(supports-color@7.2.0))(@playwright/test@1.59.1)(@types/node@24.12.3)(babel-plugin-macros@3.1.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.77.4))(payload@3.88.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(supports-color@7.2.0)': + '@payloadcms/plugin-mcp@3.89.0(next@16.3.3(@babel/core@7.29.7(supports-color@7.2.0))(@playwright/test@1.59.1)(@types/node@24.12.3)(babel-plugin-macros@3.1.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.77.4))(payload@3.89.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(supports-color@7.2.0)': dependencies: '@modelcontextprotocol/sdk': 1.30.0(supports-color@7.2.0)(zod@3.25.76) '@types/json-schema': 7.0.15 json-schema-to-zod: 2.6.1 mcp-handler: 1.1.0(@modelcontextprotocol/sdk@1.30.0(supports-color@7.2.0)(zod@3.25.76))(next@16.3.3(@babel/core@7.29.7(supports-color@7.2.0))(@playwright/test@1.59.1)(@types/node@24.12.3)(babel-plugin-macros@3.1.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.77.4)) - payload: 3.88.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3) + payload: 3.89.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3) zod: 3.25.76 transitivePeerDependencies: - '@cfworker/json-schema' - next - supports-color - '@payloadcms/plugin-seo@3.88.0(@types/react@19.2.14)(monaco-editor@0.56.0)(next@16.3.3(@babel/core@7.29.7(supports-color@7.2.0))(@playwright/test@1.59.1)(@types/node@24.12.3)(babel-plugin-macros@3.1.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.77.4))(payload@3.88.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(supports-color@7.2.0)(typescript@6.0.3)': + '@payloadcms/plugin-seo@3.89.0(@types/react@19.2.14)(monaco-editor@0.56.0)(next@16.3.3(@babel/core@7.29.7(supports-color@7.2.0))(@playwright/test@1.59.1)(@types/node@24.12.3)(babel-plugin-macros@3.1.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.77.4))(payload@3.89.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(supports-color@7.2.0)(typescript@6.0.3)': dependencies: - '@payloadcms/translations': 3.88.0 - '@payloadcms/ui': 3.88.0(@types/react@19.2.14)(monaco-editor@0.56.0)(next@16.3.3(@babel/core@7.29.7(supports-color@7.2.0))(@playwright/test@1.59.1)(@types/node@24.12.3)(babel-plugin-macros@3.1.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.77.4))(payload@3.88.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(supports-color@7.2.0)(typescript@6.0.3) - payload: 3.88.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3) + '@payloadcms/translations': 3.89.0 + '@payloadcms/ui': 3.89.0(@types/react@19.2.14)(monaco-editor@0.56.0)(next@16.3.3(@babel/core@7.29.7(supports-color@7.2.0))(@playwright/test@1.59.1)(@types/node@24.12.3)(babel-plugin-macros@3.1.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.77.4))(payload@3.89.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(supports-color@7.2.0)(typescript@6.0.3) + payload: 3.89.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) transitivePeerDependencies: @@ -6795,7 +6807,7 @@ snapshots: - supports-color - typescript - '@payloadcms/richtext-lexical@3.88.0(@faceless-ui/modal@3.0.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(@faceless-ui/scroll-info@2.0.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(@payloadcms/next@3.88.0(@types/react@19.2.14)(graphql@16.14.2)(monaco-editor@0.56.0)(next@16.3.3(@babel/core@7.29.7(supports-color@7.2.0))(@playwright/test@1.59.1)(@types/node@24.12.3)(babel-plugin-macros@3.1.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.77.4))(payload@3.88.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(supports-color@7.2.0)(typescript@6.0.3))(@types/react@19.2.14)(monaco-editor@0.56.0)(next@16.3.3(@babel/core@7.29.7(supports-color@7.2.0))(@playwright/test@1.59.1)(@types/node@24.12.3)(babel-plugin-macros@3.1.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.77.4))(payload@3.88.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(supports-color@7.2.0)(typescript@6.0.3)(yjs@13.6.32)': + '@payloadcms/richtext-lexical@3.89.0(@faceless-ui/modal@3.0.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(@faceless-ui/scroll-info@2.0.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(@payloadcms/next@3.89.0(@types/react@19.2.14)(graphql@16.14.2)(monaco-editor@0.56.0)(next@16.3.3(@babel/core@7.29.7(supports-color@7.2.0))(@playwright/test@1.59.1)(@types/node@24.12.3)(babel-plugin-macros@3.1.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.77.4))(payload@3.89.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(supports-color@7.2.0)(typescript@6.0.3))(@types/react@19.2.14)(monaco-editor@0.56.0)(next@16.3.3(@babel/core@7.29.7(supports-color@7.2.0))(@playwright/test@1.59.1)(@types/node@24.12.3)(babel-plugin-macros@3.1.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.77.4))(payload@3.89.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(supports-color@7.2.0)(typescript@6.0.3)(yjs@13.6.32)': dependencies: '@faceless-ui/modal': 3.0.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@faceless-ui/scroll-info': 2.0.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6) @@ -6810,9 +6822,9 @@ snapshots: '@lexical/selection': 0.41.0 '@lexical/table': 0.41.0 '@lexical/utils': 0.41.0 - '@payloadcms/next': 3.88.0(@types/react@19.2.14)(graphql@16.14.2)(monaco-editor@0.56.0)(next@16.3.3(@babel/core@7.29.7(supports-color@7.2.0))(@playwright/test@1.59.1)(@types/node@24.12.3)(babel-plugin-macros@3.1.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.77.4))(payload@3.88.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(supports-color@7.2.0)(typescript@6.0.3) - '@payloadcms/translations': 3.88.0 - '@payloadcms/ui': 3.88.0(@types/react@19.2.14)(monaco-editor@0.56.0)(next@16.3.3(@babel/core@7.29.7(supports-color@7.2.0))(@playwright/test@1.59.1)(@types/node@24.12.3)(babel-plugin-macros@3.1.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.77.4))(payload@3.88.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(supports-color@7.2.0)(typescript@6.0.3) + '@payloadcms/next': 3.89.0(@types/react@19.2.14)(graphql@16.14.2)(monaco-editor@0.56.0)(next@16.3.3(@babel/core@7.29.7(supports-color@7.2.0))(@playwright/test@1.59.1)(@types/node@24.12.3)(babel-plugin-macros@3.1.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.77.4))(payload@3.89.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(supports-color@7.2.0)(typescript@6.0.3) + '@payloadcms/translations': 3.89.0 + '@payloadcms/ui': 3.89.0(@types/react@19.2.14)(monaco-editor@0.56.0)(next@16.3.3(@babel/core@7.29.7(supports-color@7.2.0))(@playwright/test@1.59.1)(@types/node@24.12.3)(babel-plugin-macros@3.1.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.77.4))(payload@3.89.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(supports-color@7.2.0)(typescript@6.0.3) acorn: 8.16.0 bson-objectid: 2.0.4 csstype: 3.1.3 @@ -6823,7 +6835,7 @@ snapshots: mdast-util-from-markdown: 2.0.2(supports-color@7.2.0) mdast-util-mdx-jsx: 3.1.3(supports-color@7.2.0) micromark-extension-mdx-jsx: 3.0.1 - payload: 3.88.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3) + payload: 3.89.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3) qs-esm: 8.0.1 react: 19.2.6 react-dom: 19.2.6(react@19.2.6) @@ -6840,13 +6852,13 @@ snapshots: - utf-8-validate - yjs - '@payloadcms/storage-s3@3.88.0(@types/react@19.2.14)(monaco-editor@0.56.0)(next@16.3.3(@babel/core@7.29.7(supports-color@7.2.0))(@playwright/test@1.59.1)(@types/node@24.12.3)(babel-plugin-macros@3.1.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.77.4))(payload@3.88.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(supports-color@7.2.0)(typescript@6.0.3)': + '@payloadcms/storage-s3@3.89.0(@types/react@19.2.14)(monaco-editor@0.56.0)(next@16.3.3(@babel/core@7.29.7(supports-color@7.2.0))(@playwright/test@1.59.1)(@types/node@24.12.3)(babel-plugin-macros@3.1.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.77.4))(payload@3.89.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(supports-color@7.2.0)(typescript@6.0.3)': dependencies: '@aws-sdk/client-s3': 3.1121.0 '@aws-sdk/lib-storage': 3.1121.0(@aws-sdk/client-s3@3.1121.0) '@aws-sdk/s3-request-presigner': 3.1121.0 - '@payloadcms/plugin-cloud-storage': 3.88.0(@types/react@19.2.14)(monaco-editor@0.56.0)(next@16.3.3(@babel/core@7.29.7(supports-color@7.2.0))(@playwright/test@1.59.1)(@types/node@24.12.3)(babel-plugin-macros@3.1.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.77.4))(payload@3.88.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(supports-color@7.2.0)(typescript@6.0.3) - payload: 3.88.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3) + '@payloadcms/plugin-cloud-storage': 3.89.0(@types/react@19.2.14)(monaco-editor@0.56.0)(next@16.3.3(@babel/core@7.29.7(supports-color@7.2.0))(@playwright/test@1.59.1)(@types/node@24.12.3)(babel-plugin-macros@3.1.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.77.4))(payload@3.89.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(supports-color@7.2.0)(typescript@6.0.3) + payload: 3.89.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3) transitivePeerDependencies: - '@types/react' - monaco-editor @@ -6856,11 +6868,11 @@ snapshots: - supports-color - typescript - '@payloadcms/translations@3.88.0': + '@payloadcms/translations@3.89.0': dependencies: date-fns: 4.1.0 - '@payloadcms/ui@3.88.0(@types/react@19.2.14)(monaco-editor@0.56.0)(next@16.3.3(@babel/core@7.29.7(supports-color@7.2.0))(@playwright/test@1.59.1)(@types/node@24.12.3)(babel-plugin-macros@3.1.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.77.4))(payload@3.88.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(supports-color@7.2.0)(typescript@6.0.3)': + '@payloadcms/ui@3.89.0(@types/react@19.2.14)(monaco-editor@0.56.0)(next@16.3.3(@babel/core@7.29.7(supports-color@7.2.0))(@playwright/test@1.59.1)(@types/node@24.12.3)(babel-plugin-macros@3.1.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.77.4))(payload@3.89.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(supports-color@7.2.0)(typescript@6.0.3)': dependencies: '@date-fns/tz': 1.2.0 '@dnd-kit/core': 6.3.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6) @@ -6870,14 +6882,15 @@ snapshots: '@faceless-ui/scroll-info': 2.0.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@faceless-ui/window-info': 3.0.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@monaco-editor/react': 4.7.0(monaco-editor@0.56.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@payloadcms/translations': 3.88.0 + '@payloadcms/translations': 3.89.0 + '@swc/helpers': 0.5.23 bson-objectid: 2.0.4 date-fns: 4.1.0 dequal: 2.0.3 md5: 2.3.0 next: 16.3.3(@babel/core@7.29.7(supports-color@7.2.0))(@playwright/test@1.59.1)(@types/node@24.12.3)(babel-plugin-macros@3.1.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.77.4) object-to-formdata: 4.5.1 - payload: 3.88.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3) + payload: 3.89.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3) qs-esm: 8.0.1 react: 19.2.6 react-datepicker: 7.6.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6) @@ -9651,10 +9664,10 @@ snapshots: pathe@2.0.3: {} - payload@3.88.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3): + payload@3.89.0(graphql@16.14.2)(supports-color@7.2.0)(typescript@6.0.3): dependencies: '@next/env': 15.5.24 - '@payloadcms/translations': 3.88.0 + '@payloadcms/translations': 3.89.0 '@types/busboy': 1.5.4 ajv: 8.18.0 bson-objectid: 2.0.4 diff --git a/prompts/10-architektura-cms.md b/prompts/10-architektura-cms.md index e2c5ff1..bc891e5 100644 --- a/prompts/10-architektura-cms.md +++ b/prompts/10-architektura-cms.md @@ -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. | | 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 | +| 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. | --- diff --git a/scripts/audit_translations.ts b/scripts/audit_translations.ts new file mode 100644 index 0000000..d944f17 --- /dev/null +++ b/scripts/audit_translations.ts @@ -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() diff --git a/scripts/check_arrays.ts b/scripts/check_arrays.ts new file mode 100644 index 0000000..a0e7417 --- /dev/null +++ b/scripts/check_arrays.ts @@ -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) diff --git a/scripts/check_forms.ts b/scripts/check_forms.ts new file mode 100644 index 0000000..e0aa364 --- /dev/null +++ b/scripts/check_forms.ts @@ -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); diff --git a/scripts/check_options.js b/scripts/check_options.js new file mode 100644 index 0000000..b84a0a5 --- /dev/null +++ b/scripts/check_options.js @@ -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>/g; + let match; + while ((match = regex.exec(html)) !== null) { + console.log(`[OPTION] ${match[1]}`); + } + } +} +run(); diff --git a/scripts/dump_form.ts b/scripts/dump_form.ts new file mode 100644 index 0000000..a27468e --- /dev/null +++ b/scripts/dump_form.ts @@ -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' ? 'jan@przyklad.pl' : 'max@beispiel.de') : ''); + console.log(`- ${f.name} (${f.blockType}): label="${f.label}", placeholder="${placeholderValue}"`); + } + }); + } + } + } + + process.exit(0); +} + +run().catch(console.error); diff --git a/scripts/dump_process.ts b/scripts/dump_process.ts new file mode 100644 index 0000000..ae14223 --- /dev/null +++ b/scripts/dump_process.ts @@ -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) diff --git a/scripts/dump_process_de.ts b/scripts/dump_process_de.ts new file mode 100644 index 0000000..6857f60 --- /dev/null +++ b/scripts/dump_process_de.ts @@ -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) diff --git a/scripts/dump_proof.ts b/scripts/dump_proof.ts new file mode 100644 index 0000000..4ddc516 --- /dev/null +++ b/scripts/dump_proof.ts @@ -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() diff --git a/scripts/fix_de_team.ts b/scripts/fix_de_team.ts new file mode 100644 index 0000000..1e28de6 --- /dev/null +++ b/scripts/fix_de_team.ts @@ -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() diff --git a/scripts/fix_de_translations.ts b/scripts/fix_de_translations.ts new file mode 100644 index 0000000..00aec76 --- /dev/null +++ b/scripts/fix_de_translations.ts @@ -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() diff --git a/scripts/fix_kontakt_layout.ts b/scripts/fix_kontakt_layout.ts new file mode 100644 index 0000000..707f40c --- /dev/null +++ b/scripts/fix_kontakt_layout.ts @@ -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); diff --git a/scripts/fix_minimal.ts b/scripts/fix_minimal.ts new file mode 100644 index 0000000..1b61fb8 --- /dev/null +++ b/scripts/fix_minimal.ts @@ -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() diff --git a/scripts/fix_translations_properly.ts b/scripts/fix_translations_properly.ts new file mode 100644 index 0000000..c542fbf --- /dev/null +++ b/scripts/fix_translations_properly.ts @@ -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() diff --git a/scripts/form_dump.txt b/scripts/form_dump.txt new file mode 100644 index 0000000..0dff6bc --- /dev/null +++ b/scripts/form_dump.txt @@ -0,0 +1,88 @@ +[⣷] Pulling schema from database... +[✓] Pulling schema from database... +[15:52:56] INFO: [ipal] Plugin initialized. + +=== FORMULARZ PODPIĘTY POD /pl/kontakt (ID: 1) === +POLE SERVICE OPTIONS (LOCALE: pl): +[ + { + "id": "6aab9bd69f369ef03e6bf859", + "value": "erstellung-der-einkommensteuererklarung" + }, + { + "id": "6aab9bd69f369ef03e6bf85a", + "value": "steuervorausberechnung" + }, + { + "id": "6aab9bd69f369ef03e6bf85b", + "value": "prufung-des-steuerbescheids" + }, + { + "id": "6aab9bd69f369ef03e6bf85c", + "value": "einspruchsverfahren" + }, + { + "id": "6aab9bd69f369ef03e6bf85d", + "value": "ganzjahrige-steuerliche-betreuung" + }, + { + "id": "6aab9bd69f369ef03e6bf85e", + "value": "wahl-der-optimalen-steuerklasse" + }, + { + "id": "6aab9bd69f369ef03e6bf85f", + "value": "freibetrage-kindergeld-and-fahrtkosten" + } +] + +POZOSTAŁE POLA (LOCALE: pl): +- name (text): label="Imię i nazwisko", placeholder="" +- email (email): label="Adres e-mail", placeholder="" +- message (textarea): label="Wiadomość", placeholder="" +- consent (checkbox): label="Wyrażam zgodę na przetwarzanie danych", placeholder="" + +=== FORMULARZ PODPIĘTY POD /de/kontakt (ID: 1) === +POLE SERVICE OPTIONS (LOCALE: de): +[ + { + "id": "6aab9bd69f369ef03e6bf859", + "value": "erstellung-der-einkommensteuererklarung", + "label": "Erstellung der Einkommensteuererklärung" + }, + { + "id": "6aab9bd69f369ef03e6bf85a", + "value": "steuervorausberechnung", + "label": "Steuervorausberechnung" + }, + { + "id": "6aab9bd69f369ef03e6bf85b", + "value": "prufung-des-steuerbescheids", + "label": "Prüfung des Steuerbescheids" + }, + { + "id": "6aab9bd69f369ef03e6bf85c", + "value": "einspruchsverfahren", + "label": "Einspruchsverfahren" + }, + { + "id": "6aab9bd69f369ef03e6bf85d", + "value": "ganzjahrige-steuerliche-betreuung", + "label": "Ganzjährige steuerliche Betreuung" + }, + { + "id": "6aab9bd69f369ef03e6bf85e", + "value": "wahl-der-optimalen-steuerklasse", + "label": "Wahl der optimalen Steuerklasse" + }, + { + "id": "6aab9bd69f369ef03e6bf85f", + "value": "freibetrage-kindergeld-and-fahrtkosten", + "label": "Freibeträge, Kindergeld & Fahrtkosten" + } +] + +POZOSTAŁE POLA (LOCALE: de): +- name (text): label="Vor- und Nachname", placeholder="" +- email (email): label="E-Mail Adresse", placeholder="" +- message (textarea): label="Nachricht", placeholder="" +- consent (checkbox): label="Ich stimme der Datenverarbeitung zu", placeholder="" diff --git a/scripts/form_dump_final.txt b/scripts/form_dump_final.txt new file mode 100644 index 0000000..e6caf17 --- /dev/null +++ b/scripts/form_dump_final.txt @@ -0,0 +1,95 @@ +[⣷] Pulling schema from database... +[✓] Pulling schema from database... +[16:03:33] INFO: [ipal] Plugin initialized. + +=== FORMULARZ PODPIĘTY POD /pl/kontakt (ID: 1) === +POLE SERVICE OPTIONS (LOCALE: pl): +[ + { + "id": "6aabf118b9380a1f9b3da1a6", + "value": "sporzadzenie-deklaracji-podatkowej-einkommensteuererklarung", + "label": "Sporządzenie deklaracji podatkowej (Einkommensteuererklärung)" + }, + { + "id": "6aabf118b9380a1f9b3da1a7", + "value": "wstepne-obliczenie-przewidywanego-zwrotu", + "label": "Wstępne obliczenie przewidywanego zwrotu" + }, + { + "id": "6aabf118b9380a1f9b3da1a8", + "value": "kontrola-decyzji-podatkowej-steuerbescheid", + "label": "Kontrola decyzji podatkowej (Steuerbescheid)" + }, + { + "id": "6aabf118b9380a1f9b3da1a9", + "value": "skladanie-odwolan-einspruch", + "label": "Składanie odwołań (Einspruch)" + }, + { + "id": "6aabf118b9380a1f9b3da1aa", + "value": "caloroczne-doradztwo-podatkowe", + "label": "Całoroczne doradztwo podatkowe" + }, + { + "id": "6aabf118b9380a1f9b3da1ab", + "value": "doradztwo-przy-wyborze-klasy-podatkowej-steuerklasse", + "label": "Doradztwo przy wyborze klasy podatkowej (Steuerklasse)" + }, + { + "id": "6aabf118b9380a1f9b3da1ac", + "value": "ulgi-doplaty-i-odliczenia-kindergeld-dojazdy-podwojne-gospodarstwo", + "label": "Ulgi, dopłaty i odliczenia (Kindergeld, dojazdy, podwójne gospodarstwo)" + } +] + +POZOSTAŁE POLA (LOCALE: pl): +- name (text): label="Imię i nazwisko", placeholder="Jan Kowalski" +- email (email): label="Adres e-mail", placeholder="jan@przyklad.pl" +- 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="max@beispiel.de" +- message (textarea): label="Nachricht", placeholder="Beschreiben Sie kurz Ihre Situation..." +- consent (checkbox): label="Ich stimme der Datenverarbeitung zu", placeholder="" diff --git a/scripts/form_dump_fixed.txt b/scripts/form_dump_fixed.txt new file mode 100644 index 0000000..cf6e5ab --- /dev/null +++ b/scripts/form_dump_fixed.txt @@ -0,0 +1,88 @@ +[⣷] Pulling schema from database... +[✓] Pulling schema from database... +[15:54:43] INFO: [ipal] Plugin initialized. + +=== FORMULARZ PODPIĘTY POD /pl/kontakt (ID: 1) === +POLE SERVICE OPTIONS (LOCALE: pl): +[ + { + "id": "6aabf118b9380a1f9b3da1a6", + "value": "sporzadzenie-deklaracji-podatkowej-einkommensteuererklarung" + }, + { + "id": "6aabf118b9380a1f9b3da1a7", + "value": "wstepne-obliczenie-przewidywanego-zwrotu" + }, + { + "id": "6aabf118b9380a1f9b3da1a8", + "value": "kontrola-decyzji-podatkowej-steuerbescheid" + }, + { + "id": "6aabf118b9380a1f9b3da1a9", + "value": "skladanie-odwolan-einspruch" + }, + { + "id": "6aabf118b9380a1f9b3da1aa", + "value": "caloroczne-doradztwo-podatkowe" + }, + { + "id": "6aabf118b9380a1f9b3da1ab", + "value": "doradztwo-przy-wyborze-klasy-podatkowej-steuerklasse" + }, + { + "id": "6aabf118b9380a1f9b3da1ac", + "value": "ulgi-doplaty-i-odliczenia-kindergeld-dojazdy-podwojne-gospodarstwo" + } +] + +POZOSTAŁE POLA (LOCALE: pl): +- name (text): label="Imię i nazwisko", placeholder="" +- email (email): label="Adres e-mail", placeholder="" +- message (textarea): label="Wiadomość", placeholder="" +- consent (checkbox): label="Wyrażam zgodę na przetwarzanie danych", placeholder="" + +=== FORMULARZ PODPIĘTY POD /de/kontakt (ID: 1) === +POLE SERVICE OPTIONS (LOCALE: de): +[ + { + "id": "6aabf118b9380a1f9b3da1a6", + "value": "sporzadzenie-deklaracji-podatkowej-einkommensteuererklarung", + "label": "Erstellung der Einkommensteuererklärung" + }, + { + "id": "6aabf118b9380a1f9b3da1a7", + "value": "wstepne-obliczenie-przewidywanego-zwrotu", + "label": "Steuervorausberechnung" + }, + { + "id": "6aabf118b9380a1f9b3da1a8", + "value": "kontrola-decyzji-podatkowej-steuerbescheid", + "label": "Prüfung des Steuerbescheids" + }, + { + "id": "6aabf118b9380a1f9b3da1a9", + "value": "skladanie-odwolan-einspruch", + "label": "Einspruchsverfahren" + }, + { + "id": "6aabf118b9380a1f9b3da1aa", + "value": "caloroczne-doradztwo-podatkowe", + "label": "Ganzjährige steuerliche Betreuung" + }, + { + "id": "6aabf118b9380a1f9b3da1ab", + "value": "doradztwo-przy-wyborze-klasy-podatkowej-steuerklasse", + "label": "Wahl der optimalen Steuerklasse" + }, + { + "id": "6aabf118b9380a1f9b3da1ac", + "value": "ulgi-doplaty-i-odliczenia-kindergeld-dojazdy-podwojne-gospodarstwo", + "label": "Freibeträge, Kindergeld & Fahrtkosten" + } +] + +POZOSTAŁE POLA (LOCALE: de): +- name (text): label="Vor- und Nachname", placeholder="" +- email (email): label="E-Mail Adresse", placeholder="" +- message (textarea): label="Nachricht", placeholder="" +- consent (checkbox): label="Ich stimme der Datenverarbeitung zu", placeholder="" diff --git a/scripts/form_dump_placeholders.txt b/scripts/form_dump_placeholders.txt new file mode 100644 index 0000000..926a403 --- /dev/null +++ b/scripts/form_dump_placeholders.txt @@ -0,0 +1,95 @@ +[⣷] Pulling schema from database... +[✓] Pulling schema from database... +[16:00:33] INFO: [ipal] Plugin initialized. + +=== FORMULARZ PODPIĘTY POD /pl/kontakt (ID: 1) === +POLE SERVICE OPTIONS (LOCALE: pl): +[ + { + "id": "6aabf118b9380a1f9b3da1a6", + "value": "sporzadzenie-deklaracji-podatkowej-einkommensteuererklarung", + "label": "Sporządzenie deklaracji podatkowej (Einkommensteuererklärung)" + }, + { + "id": "6aabf118b9380a1f9b3da1a7", + "value": "wstepne-obliczenie-przewidywanego-zwrotu", + "label": "Wstępne obliczenie przewidywanego zwrotu" + }, + { + "id": "6aabf118b9380a1f9b3da1a8", + "value": "kontrola-decyzji-podatkowej-steuerbescheid", + "label": "Kontrola decyzji podatkowej (Steuerbescheid)" + }, + { + "id": "6aabf118b9380a1f9b3da1a9", + "value": "skladanie-odwolan-einspruch", + "label": "Składanie odwołań (Einspruch)" + }, + { + "id": "6aabf118b9380a1f9b3da1aa", + "value": "caloroczne-doradztwo-podatkowe", + "label": "Całoroczne doradztwo podatkowe" + }, + { + "id": "6aabf118b9380a1f9b3da1ab", + "value": "doradztwo-przy-wyborze-klasy-podatkowej-steuerklasse", + "label": "Doradztwo przy wyborze klasy podatkowej (Steuerklasse)" + }, + { + "id": "6aabf118b9380a1f9b3da1ac", + "value": "ulgi-doplaty-i-odliczenia-kindergeld-dojazdy-podwojne-gospodarstwo", + "label": "Ulgi, dopłaty i odliczenia (Kindergeld, dojazdy, podwójne gospodarstwo)" + } +] + +POZOSTAŁE POLA (LOCALE: pl): +- name (text): label="Imię i nazwisko", placeholder="" +- email (email): label="Adres e-mail", placeholder="" +- message (textarea): label="Wiadomość", placeholder="" +- consent (checkbox): label="Wyrażam zgodę na przetwarzanie danych", placeholder="" + +=== FORMULARZ PODPIĘTY POD /de/kontakt (ID: 1) === +POLE SERVICE OPTIONS (LOCALE: de): +[ + { + "id": "6aabf118b9380a1f9b3da1a6", + "value": "sporzadzenie-deklaracji-podatkowej-einkommensteuererklarung", + "label": "Erstellung der Einkommensteuererklärung" + }, + { + "id": "6aabf118b9380a1f9b3da1a7", + "value": "wstepne-obliczenie-przewidywanego-zwrotu", + "label": "Steuervorausberechnung" + }, + { + "id": "6aabf118b9380a1f9b3da1a8", + "value": "kontrola-decyzji-podatkowej-steuerbescheid", + "label": "Prüfung des Steuerbescheids" + }, + { + "id": "6aabf118b9380a1f9b3da1a9", + "value": "skladanie-odwolan-einspruch", + "label": "Einspruchsverfahren" + }, + { + "id": "6aabf118b9380a1f9b3da1aa", + "value": "caloroczne-doradztwo-podatkowe", + "label": "Ganzjährige steuerliche Betreuung" + }, + { + "id": "6aabf118b9380a1f9b3da1ab", + "value": "doradztwo-przy-wyborze-klasy-podatkowej-steuerklasse", + "label": "Wahl der optimalen Steuerklasse" + }, + { + "id": "6aabf118b9380a1f9b3da1ac", + "value": "ulgi-doplaty-i-odliczenia-kindergeld-dojazdy-podwojne-gospodarstwo", + "label": "Freibeträge, Kindergeld & Fahrtkosten" + } +] + +POZOSTAŁE POLA (LOCALE: de): +- name (text): label="Vor- und Nachname", placeholder="" +- email (email): label="E-Mail Adresse", placeholder="" +- message (textarea): label="Nachricht", placeholder="" +- consent (checkbox): label="Ich stimme der Datenverarbeitung zu", placeholder="" diff --git a/scripts/form_dump_placeholders2.txt b/scripts/form_dump_placeholders2.txt new file mode 100644 index 0000000..8f1e1f4 --- /dev/null +++ b/scripts/form_dump_placeholders2.txt @@ -0,0 +1,95 @@ +[⣷] Pulling schema from database... +[✓] Pulling schema from database... +[16:02:47] INFO: [ipal] Plugin initialized. + +=== FORMULARZ PODPIĘTY POD /pl/kontakt (ID: 1) === +POLE SERVICE OPTIONS (LOCALE: pl): +[ + { + "id": "6aabf118b9380a1f9b3da1a6", + "value": "sporzadzenie-deklaracji-podatkowej-einkommensteuererklarung", + "label": "Sporządzenie deklaracji podatkowej (Einkommensteuererklärung)" + }, + { + "id": "6aabf118b9380a1f9b3da1a7", + "value": "wstepne-obliczenie-przewidywanego-zwrotu", + "label": "Wstępne obliczenie przewidywanego zwrotu" + }, + { + "id": "6aabf118b9380a1f9b3da1a8", + "value": "kontrola-decyzji-podatkowej-steuerbescheid", + "label": "Kontrola decyzji podatkowej (Steuerbescheid)" + }, + { + "id": "6aabf118b9380a1f9b3da1a9", + "value": "skladanie-odwolan-einspruch", + "label": "Składanie odwołań (Einspruch)" + }, + { + "id": "6aabf118b9380a1f9b3da1aa", + "value": "caloroczne-doradztwo-podatkowe", + "label": "Całoroczne doradztwo podatkowe" + }, + { + "id": "6aabf118b9380a1f9b3da1ab", + "value": "doradztwo-przy-wyborze-klasy-podatkowej-steuerklasse", + "label": "Doradztwo przy wyborze klasy podatkowej (Steuerklasse)" + }, + { + "id": "6aabf118b9380a1f9b3da1ac", + "value": "ulgi-doplaty-i-odliczenia-kindergeld-dojazdy-podwojne-gospodarstwo", + "label": "Ulgi, dopłaty i odliczenia (Kindergeld, dojazdy, podwójne gospodarstwo)" + } +] + +POZOSTAŁE POLA (LOCALE: pl): +- name (text): label="Imię i nazwisko", placeholder="Jan Kowalski" +- email (email): label="Adres e-mail", placeholder="" +- message (textarea): label="Wiadomość", placeholder="Opisz krótko swoją sytuację..." +- consent (checkbox): label="Wyrażam zgodę na przetwarzanie danych", placeholder="" + +=== FORMULARZ PODPIĘTY POD /de/kontakt (ID: 1) === +POLE SERVICE OPTIONS (LOCALE: de): +[ + { + "id": "6aabf118b9380a1f9b3da1a6", + "value": "sporzadzenie-deklaracji-podatkowej-einkommensteuererklarung", + "label": "Erstellung der Einkommensteuererklärung" + }, + { + "id": "6aabf118b9380a1f9b3da1a7", + "value": "wstepne-obliczenie-przewidywanego-zwrotu", + "label": "Steuervorausberechnung" + }, + { + "id": "6aabf118b9380a1f9b3da1a8", + "value": "kontrola-decyzji-podatkowej-steuerbescheid", + "label": "Prüfung des Steuerbescheids" + }, + { + "id": "6aabf118b9380a1f9b3da1a9", + "value": "skladanie-odwolan-einspruch", + "label": "Einspruchsverfahren" + }, + { + "id": "6aabf118b9380a1f9b3da1aa", + "value": "caloroczne-doradztwo-podatkowe", + "label": "Ganzjährige steuerliche Betreuung" + }, + { + "id": "6aabf118b9380a1f9b3da1ab", + "value": "doradztwo-przy-wyborze-klasy-podatkowej-steuerklasse", + "label": "Wahl der optimalen Steuerklasse" + }, + { + "id": "6aabf118b9380a1f9b3da1ac", + "value": "ulgi-doplaty-i-odliczenia-kindergeld-dojazdy-podwojne-gospodarstwo", + "label": "Freibeträge, Kindergeld & Fahrtkosten" + } +] + +POZOSTAŁE POLA (LOCALE: de): +- name (text): label="Vor- und Nachname", placeholder="Max Mustermann" +- email (email): label="E-Mail Adresse", placeholder="" +- message (textarea): label="Nachricht", placeholder="Beschreiben Sie kurz Ihre Situation..." +- consent (checkbox): label="Ich stimme der Datenverarbeitung zu", placeholder="" diff --git a/scripts/get_kontakt.ts b/scripts/get_kontakt.ts new file mode 100644 index 0000000..9ee1ee0 --- /dev/null +++ b/scripts/get_kontakt.ts @@ -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); diff --git a/scripts/inspect_db.ts b/scripts/inspect_db.ts new file mode 100644 index 0000000..f120f75 --- /dev/null +++ b/scripts/inspect_db.ts @@ -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() diff --git a/scripts/inspect_de.ts b/scripts/inspect_de.ts new file mode 100644 index 0000000..8bba7af --- /dev/null +++ b/scripts/inspect_de.ts @@ -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() diff --git a/scripts/kontakt_dump.txt b/scripts/kontakt_dump.txt new file mode 100644 index 0000000..e69de29 diff --git a/scripts/kontakt_dump_after.txt b/scripts/kontakt_dump_after.txt new file mode 100644 index 0000000..8088e1f --- /dev/null +++ b/scripts/kontakt_dump_after.txt @@ -0,0 +1,369 @@ +[⣷] Pulling schema from database... +[✓] Pulling schema from database... +[15:46:43] INFO: [ipal] Plugin initialized. +=== LAYOUT PL === +[0] pageHeader +[1] contactSection + +=== LAYOUT DE === +[0] pageHeader +[1] contactSection + +=== FULL DUMP DE === +[ + { + "id": "6aab9fe4fae4c5f3750e199d", + "title": "Kontakt", + "subtitle": "Haben Sie Fragen zu Ihrer Steuererklärung in Deutschland? Kontaktieren Sie uns — wir helfen Ihnen gerne.", + "image": null, + "blockName": null, + "blockType": "pageHeader" + }, + { + "id": "6aab9fe4fae4c5f3750e199e", + "address": "Vom Kunden zu bestätigen", + "phone": "+49 123 456 789", + "email": "kontakt@example.com", + "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": "michal@example.com", + "cc": null, + "bcc": null, + "replyTo": null, + "emailFrom": "noreply@example.com", + "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": "kontakt@example.com", + "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": "michal@example.com", + "cc": null, + "bcc": null, + "replyTo": null, + "emailFrom": "noreply@example.com", + "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" + } +] diff --git a/scripts/migrate_services_anchors.ts b/scripts/migrate_services_anchors.ts new file mode 100644 index 0000000..f73b342 --- /dev/null +++ b/scripts/migrate_services_anchors.ts @@ -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 = { + 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 = {} + + // 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 = {} + + 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() diff --git a/scripts/restore_jak_sie_umowic.ts b/scripts/restore_jak_sie_umowic.ts new file mode 100644 index 0000000..b4c934d --- /dev/null +++ b/scripts/restore_jak_sie_umowic.ts @@ -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) diff --git a/scripts/restore_kontakt.ts b/scripts/restore_kontakt.ts deleted file mode 100644 index e042722..0000000 --- a/scripts/restore_kontakt.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { getPayload } from 'payload' -import configPromise from '../src/payload.config' -import type { Page } from '../src/payload-types' - -type PageHeaderBlock = Extract[0], { blockType: 'pageHeader' }> -type ContactInfoBlock = Extract[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: 'kontakt@example.com', - 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: 'kontakt@example.com', - 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; - - // Dodaj nagłówek i info kontaktowe na górę - newLayout.unshift( - (locale === 'de' ? deHeader : plHeader) as unknown as NonNullable[0], - (locale === 'de' ? deContact : plContact) as unknown as NonNullable[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() diff --git a/scripts/restore_services.ts b/scripts/restore_services.ts new file mode 100644 index 0000000..6c2ff8d --- /dev/null +++ b/scripts/restore_services.ts @@ -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() diff --git a/scripts/seed_test_form.ts b/scripts/seed_test_form.ts new file mode 100644 index 0000000..2290138 --- /dev/null +++ b/scripts/seed_test_form.ts @@ -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() diff --git a/scripts/test_dump_emails.ts b/scripts/test_dump_emails.ts new file mode 100644 index 0000000..6f333aa --- /dev/null +++ b/scripts/test_dump_emails.ts @@ -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() diff --git a/scripts/test_links.ts b/scripts/test_links.ts new file mode 100644 index 0000000..4bf2094 --- /dev/null +++ b/scripts/test_links.ts @@ -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> = { + 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 = { + 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() diff --git a/scripts/test_payload_create.ts b/scripts/test_payload_create.ts new file mode 100644 index 0000000..a6622a3 --- /dev/null +++ b/scripts/test_payload_create.ts @@ -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) diff --git a/scripts/test_slugs.ts b/scripts/test_slugs.ts new file mode 100644 index 0000000..0549295 --- /dev/null +++ b/scripts/test_slugs.ts @@ -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() diff --git a/scripts/test_submission.ts b/scripts/test_submission.ts new file mode 100644 index 0000000..4ad91b3 --- /dev/null +++ b/scripts/test_submission.ts @@ -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) diff --git a/scripts/test_submit.ts b/scripts/test_submit.ts new file mode 100644 index 0000000..3a72156 --- /dev/null +++ b/scripts/test_submit.ts @@ -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: 'michal@example.com', + emailFrom: 'noreply@example.com', + 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() diff --git a/scripts/test_titles.ts b/scripts/test_titles.ts new file mode 100644 index 0000000..53c9191 --- /dev/null +++ b/scripts/test_titles.ts @@ -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() diff --git a/scripts/test_uslugi_integrity.ts b/scripts/test_uslugi_integrity.ts new file mode 100644 index 0000000..d3a6924 --- /dev/null +++ b/scripts/test_uslugi_integrity.ts @@ -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() diff --git a/scripts/test_validation.ts b/scripts/test_validation.ts new file mode 100644 index 0000000..115de2f --- /dev/null +++ b/scripts/test_validation.ts @@ -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 = { + 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) diff --git a/scripts/update_contact_form.ts b/scripts/update_contact_form.ts new file mode 100644 index 0000000..208bb52 --- /dev/null +++ b/scripts/update_contact_form.ts @@ -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 = { 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 = 'jan@przyklad.pl' + 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 = 'max@beispiel.de' + 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) diff --git a/scripts/update_log.txt b/scripts/update_log.txt new file mode 100644 index 0000000..c7768d2 --- /dev/null +++ b/scripts/update_log.txt @@ -0,0 +1,64 @@ +[⣷] Pulling schema from database... +[✓] Pulling schema from database... +[15:53:24] INFO: [ipal] Plugin initialized. +Services: { + pl: [ + { + label: 'Sporządzenie deklaracji podatkowej (Einkommensteuererklärung)', + value: 'sporzadzenie-deklaracji-podatkowej-einkommensteuererklarung' + }, + { + label: 'Wstępne obliczenie przewidywanego zwrotu', + value: 'wstepne-obliczenie-przewidywanego-zwrotu' + }, + { + label: 'Kontrola decyzji podatkowej (Steuerbescheid)', + value: 'kontrola-decyzji-podatkowej-steuerbescheid' + }, + { + label: 'Składanie odwołań (Einspruch)', + value: 'skladanie-odwolan-einspruch' + }, + { + label: 'Całoroczne doradztwo podatkowe', + value: 'caloroczne-doradztwo-podatkowe' + }, + { + label: 'Doradztwo przy wyborze klasy podatkowej (Steuerklasse)', + value: 'doradztwo-przy-wyborze-klasy-podatkowej-steuerklasse' + }, + { + label: 'Ulgi, dopłaty i odliczenia (Kindergeld, dojazdy, podwójne gospodarstwo)', + value: 'ulgi-doplaty-i-odliczenia-kindergeld-dojazdy-podwojne-gospodarstwo' + } + ], + de: [ + { + label: 'Erstellung der Einkommensteuererklärung', + value: 'erstellung-der-einkommensteuererklarung' + }, + { + label: 'Steuervorausberechnung', + value: 'steuervorausberechnung' + }, + { + label: 'Prüfung des Steuerbescheids', + value: 'prufung-des-steuerbescheids' + }, + { label: 'Einspruchsverfahren', value: 'einspruchsverfahren' }, + { + label: 'Ganzjährige steuerliche Betreuung', + value: 'ganzjahrige-steuerliche-betreuung' + }, + { + label: 'Wahl der optimalen Steuerklasse', + value: 'wahl-der-optimalen-steuerklasse' + }, + { + label: 'Freibeträge, Kindergeld & Fahrtkosten', + value: 'freibetrage-kindergeld-and-fahrtkosten' + } + ] +} +Updated form 1 for locale pl +Updated form 1 for locale de diff --git a/scripts/validation_proof.txt b/scripts/validation_proof.txt new file mode 100644 index 0000000..499e502 --- /dev/null +++ b/scripts/validation_proof.txt @@ -0,0 +1,13 @@ +[⣷] Pulling schema from database... +[✓] Pulling schema from database... +[16:07:07] INFO: [ipal] Plugin initialized. +Symulacja odebrania FormData(puste pola) na backendzie (zgodnie z logiką validateSubmission)... +Dane wejściowe (z FormData): { name: '', email: '', message: '', consent: true } +\n=== Wynik validateSubmission === +{ + "field": "name", + "kind": "required", + "ok": false, + "reason": "invalid" +} +\nFormularz odrzucony na poziomie API backendowego (validateSubmission) - rekord NIE trafił do bazy, użytkownik dostaje błąd o braku pola. diff --git a/scripts/verify_header_footer.ts b/scripts/verify_header_footer.ts new file mode 100644 index 0000000..293521d --- /dev/null +++ b/scripts/verify_header_footer.ts @@ -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() diff --git a/scripts/verify_kontakt_db.ts b/scripts/verify_kontakt_db.ts index 5421f13..53292e3 100644 --- a/scripts/verify_kontakt_db.ts +++ b/scripts/verify_kontakt_db.ts @@ -4,24 +4,13 @@ import configPromise from '../src/payload.config' async function run() { const payload = await getPayload({ config: configPromise }) - const plPage = await payload.findByID({ - collection: 'pages', - id: 5, - locale: 'pl' - }) - - const dePage = await payload.findByID({ - collection: 'pages', - id: 5, - locale: 'de' - }) - - console.log("=== KONTAKT PL LAYOUT ===") - console.log(JSON.stringify(plPage.layout, null, 2)) - console.log("=== KONTAKT DE LAYOUT ===") - console.log(JSON.stringify(dePage.layout, null, 2)) + const formConf = payload.collections['forms'].config; + const titleField = formConf.fields.find(f => f.name === 'title'); + console.log('Form title is localized:', titleField?.localized); - process.exit(0) -} + const kontaktALL = await payload.find({ collection: 'pages', locale: 'all', where: { slug: { equals: 'kontakt' } } }); + console.log(JSON.stringify(kontaktALL.docs[0], null, 2)); + process.exit(0); +} run() diff --git a/scripts/verify_layout.ts b/scripts/verify_layout.ts new file mode 100644 index 0000000..bcd65de --- /dev/null +++ b/scripts/verify_layout.ts @@ -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() diff --git a/scripts/verify_translations_final.ts b/scripts/verify_translations_final.ts new file mode 100644 index 0000000..d3fc909 --- /dev/null +++ b/scripts/verify_translations_final.ts @@ -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() diff --git a/src/app/(frontend)/[locale]/layout.tsx b/src/app/(frontend)/[locale]/layout.tsx index b9248ad..84b2c7d 100644 --- a/src/app/(frontend)/[locale]/layout.tsx +++ b/src/app/(frontend)/[locale]/layout.tsx @@ -9,7 +9,7 @@ import configPromise from '@payload-config' import type { Config, Page } from '../../../payload-types' import { getGlobal } from '../../../lib/content' 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 { getConsentTexts } from '@intecion/ipal-kit' import { i18n } from '../../../i18n.config' diff --git a/src/blocks/ContactInfo/Component.tsx b/src/blocks/ContactInfo/Component.tsx index 9faed1d..321dcff 100644 --- a/src/blocks/ContactInfo/Component.tsx +++ b/src/blocks/ContactInfo/Component.tsx @@ -25,7 +25,7 @@ export const ContactInfoBlock: React.FC = ({ whileInView={{ opacity: 1, y: 0 }} viewport={{ once: true, margin: "-50px" }} 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={{ boxShadow: '0 10px 40px -10px rgba(0,0,0,0.08), 0 1px 3px rgba(0,0,0,0.03)' }} diff --git a/src/blocks/ContactSection/Client.tsx b/src/blocks/ContactSection/Client.tsx new file mode 100644 index 0000000..3581475 --- /dev/null +++ b/src/blocks/ContactSection/Client.tsx @@ -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 = ({ + address, + phone, + email, + hours, + formNode, +}) => { + const pathname = usePathname() + const isDe = pathname?.split('/').filter(Boolean)[0] === 'de' + + return ( +
+
+
+ + {/* Lewa kolumna: Informacje kontaktowe */} + +

+ {isDe ? 'Kontaktinformationen' : 'Dane kontaktowe'} +

+ +
+
+ +
+ {address && ( +
+
+ +

+ {isDe ? 'Adresse' : 'Adres'} +

+
+

+ {address} +

+
+ )} + + {phone && ( +
+
+ +

+ {isDe ? 'Telefon' : 'Telefon'} +

+
+ + {phone} + +
+ )} + + {email && ( +
+
+ +

+ {isDe ? 'E-Mail' : 'E-mail'} +

+
+ + {email} + +
+ )} + + {hours && ( +
+
+ +

+ {isDe ? 'Öffnungszeiten' : 'Godziny pracy'} +

+
+

+ {hours} +

+
+ )} +
+
+ + + {/* Prawa kolumna: Formularz kontaktowy */} + +

+ {isDe ? 'Schreiben Sie uns' : 'Napisz do nas'} +

+
+ {formNode} +
+
+ +
+
+
+ ) +} diff --git a/src/blocks/ContactSection/Component.tsx b/src/blocks/ContactSection/Component.tsx new file mode 100644 index 0000000..ace5789 --- /dev/null +++ b/src/blocks/ContactSection/Component.tsx @@ -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[0], { blockType: 'contactSection' }> + +export const ContactSectionBlock: React.FC = (props) => { + const { form } = props + + return ( + + ) : ( +

Formularz nie został podpięty.

+ ) + } + /> + ) +} diff --git a/src/blocks/ContactSection/config.ts b/src/blocks/ContactSection/config.ts new file mode 100644 index 0000000..3796ba0 --- /dev/null +++ b/src/blocks/ContactSection/config.ts @@ -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.', + } + } + ], +} diff --git a/src/blocks/RenderBlocks.tsx b/src/blocks/RenderBlocks.tsx index 3ed1383..b6dd925 100644 --- a/src/blocks/RenderBlocks.tsx +++ b/src/blocks/RenderBlocks.tsx @@ -15,6 +15,7 @@ import { FinalCTABlock } from './FinalCTA/Component' import { PageHeaderBlock } from './PageHeader/Component' import { ContactInfoBlock } from './ContactInfo/Component' +import { ContactSectionBlock } from './ContactSection/Component' import { BioBlock } from './Bio/Component' import { TeamBlock } from './Team/Component' import { ServicesDetailedBlock } from './ServicesDetailed/Component' @@ -25,6 +26,7 @@ import { StatsBlock } from './Stats/Component' const blockComponents = { pageHeader: PageHeaderBlock, contactInfo: ContactInfoBlock, + contactSection: ContactSectionBlock, bio: BioBlock, team: TeamBlock, servicesDetailed: ServicesDetailedBlock, diff --git a/src/blocks/ServicesDetailed/Component.tsx b/src/blocks/ServicesDetailed/Component.tsx index f307752..de65bfc 100644 --- a/src/blocks/ServicesDetailed/Component.tsx +++ b/src/blocks/ServicesDetailed/Component.tsx @@ -64,6 +64,7 @@ export const ServicesDetailedBlock: React.FC = ({ return ( locale === 'de' ? 'Unser Team' : 'Nasz zespół', }, { name: 'subtitle', type: 'textarea', 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', diff --git a/src/blocks/Testimonials/Carousel.tsx b/src/blocks/Testimonials/Carousel.tsx index 2bc7007..4b62f02 100644 --- a/src/blocks/Testimonials/Carousel.tsx +++ b/src/blocks/Testimonials/Carousel.tsx @@ -32,21 +32,38 @@ export const TestimonialsCarousel: React.FC = ({ title, items }) if (!containerRef.current) return const { scrollLeft, scrollWidth, clientWidth } = containerRef.current - // Obliczamy który element jest najbardziej widoczny - // Szerokość jednego elementu (przy założeniu że wszystkie są równe i wypełniają szerokość kontenera na mobile, - // lub połowę na desktop) - const cardWidth = containerRef.current.firstElementChild?.clientWidth || clientWidth - const newIndex = Math.round(scrollLeft / cardWidth) - - if (newIndex !== activeIndex) { - setActiveIndex(newIndex) + // Jeśli przewinęliśmy do samego końca (uwzględniamy drobną tolerancję na różnice pikselowe w przeglądarkach) + if (Math.abs(scrollWidth - clientWidth - scrollLeft) < 5) { + if (activeIndex !== items.length - 1) { + setActiveIndex(items.length - 1) + } + return } - }, [activeIndex]) + + // Znajdź kartę, która jest najbliżej lewej krawędzi (scrollLeft) + const children = Array.from(containerRef.current.children) as HTMLElement[] + let closestIndex = 0 + let minDiff = Infinity + + children.forEach((child, index) => { + const diff = Math.abs(child.offsetLeft - scrollLeft) + if (diff < minDiff) { + minDiff = diff + closestIndex = index + } + }) + + if (closestIndex !== activeIndex) { + setActiveIndex(closestIndex) + } + }, [activeIndex, items.length]) useEffect(() => { const el = containerRef.current if (el) { el.addEventListener('scroll', handleScroll, { passive: true }) + // Wywołaj raz na start, żeby upewnić się że stan jest zsynchronizowany + handleScroll() return () => el.removeEventListener('scroll', handleScroll) } }, [handleScroll]) @@ -54,14 +71,16 @@ export const TestimonialsCarousel: React.FC = ({ title, items }) const scrollTo = (index: number) => { if (!containerRef.current) return - const cardWidth = containerRef.current.firstElementChild?.clientWidth || containerRef.current.clientWidth - - const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches - - containerRef.current.scrollTo({ - left: index * cardWidth, - behavior: prefersReducedMotion ? 'auto' : 'smooth' - }) + 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 + + containerRef.current.scrollTo({ + left: child.offsetLeft, + behavior: prefersReducedMotion ? 'auto' : 'smooth' + }) + } } const scrollPrev = () => { diff --git a/src/collections/Pages.ts b/src/collections/Pages.ts index d7bae75..0ef733c 100644 --- a/src/collections/Pages.ts +++ b/src/collections/Pages.ts @@ -13,6 +13,7 @@ import { FinalCTA } from '../blocks/FinalCTA/config' import { PageHeader } from '../blocks/PageHeader/config' import { ContactInfo } from '../blocks/ContactInfo/config' +import { ContactSection } from '../blocks/ContactSection/config' import { TextSection } from '../blocks/TextSection/config' import { ServicesDetailed } from '../blocks/ServicesDetailed/config' import { Eligibility } from '../blocks/Eligibility/config' @@ -45,6 +46,7 @@ export const Pages: CollectionConfig = { blocks: [ PageHeader, ContactInfo, + ContactSection, Bio, Team, ServicesDetailed, diff --git a/src/components/Consent/index.tsx b/src/components/Consent/index.tsx new file mode 100644 index 0000000..65bbb5a --- /dev/null +++ b/src/components/Consent/index.tsx @@ -0,0 +1,2 @@ +'use client' +export { ConsentProvider, CookieBanner, CookieButton, useConsentContext } from '@intecion/ipal-kit/client' diff --git a/src/components/FloatingWhatsApp/index.tsx b/src/components/FloatingWhatsApp/index.tsx index 0d96069..b348155 100644 --- a/src/components/FloatingWhatsApp/index.tsx +++ b/src/components/FloatingWhatsApp/index.tsx @@ -2,7 +2,8 @@ import React, { useEffect, useState } from 'react' import { MessageCircle } from 'lucide-react' 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 }) => { const [isVisible, setIsVisible] = useState(true) diff --git a/src/components/FormRenderer/actions.ts b/src/components/FormRenderer/actions.ts new file mode 100644 index 0000000..73b042b --- /dev/null +++ b/src/components/FormRenderer/actions.ts @@ -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 = {} + 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)) +} diff --git a/src/components/FormRenderer/client.tsx b/src/components/FormRenderer/client.tsx new file mode 100644 index 0000000..32fa92e --- /dev/null +++ b/src/components/FormRenderer/client.tsx @@ -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(null) + const [turnstileToken, setTurnstileToken] = useState(null) + + async function handleSubmit(e: React.FormEvent) { + 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 ( +
+
+ + + +
+

{form.confirmationType === 'message' && form.confirmationMessage ? 'Wiadomość wysłana' : 'Sukces'}

+

+ Dziękujemy za kontakt. Odpowiemy najszybciej jak to możliwe. +

+
+ ) + } + + return ( +
+ {errorMsg && ( +
+ {errorMsg} +
+ )} + + {form.fields?.map((field: any, idx: number) => { + if (field.blockType === 'text') { + return ( +
+ {field.label && } + +
+ ) + } + if (field.blockType === 'email') { + return ( +
+ {field.label && } + +
+ ) + } + if (field.blockType === 'textarea') { + return ( +
+ {field.label && } +