feat: complete UI rebuild, responsive fixes, dark mode UI, and Team block overhaul

This commit is contained in:
MichalC
2026-09-16 23:33:17 +02:00
parent c2ae27a83b
commit 7cc68b9f36
30 changed files with 964 additions and 339 deletions
+24 -24
View File
@@ -142,7 +142,7 @@
{
"id": "0be1dd17cae558b75684137c",
"title": "Kontakt",
"subtitle": "Masz pytania dotyczące rozliczenia podatkowego w Niemczech? Skontaktuj się ze mną — chętnie pomogę.",
"subtitle": "Masz pytania dotyczące rozliczenia podatkowego w Niemczech? Skontaktuj się z nami — chętnie pomożemy.",
"blockName": null,
"blockType": "pageHeader"
},
@@ -167,7 +167,7 @@
{
"id": "5c16dbfeb63e8be48f313eb0",
"title": "Kontakt",
"subtitle": "Haben Sie Fragen zu Ihrer Steuererklärung in Deutschland? Kontaktieren Sie mich — ich helfe Ihnen gerne.",
"subtitle": "Haben Sie Fragen zu Ihrer Steuererklärung in Deutschland? Kontaktieren Sie uns — wir helfen Ihnen gerne.",
"blockName": null,
"blockType": "pageHeader"
},
@@ -286,7 +286,7 @@
"id": "6a993b67a0e23e2a75a4cc85",
"eyebrow": null,
"title": "Gotowy na rozliczenie podatku?",
"subtitle": "Skontaktuj się ze mną już dziś. Przeanalizuję Twoją sytuację i pomogę uzyskać maksymalny zwrot z niemieckiego urzędu skarbowego.",
"subtitle": "Skontaktuj się z nami już dziś. Przeanalizujemy Twoją sytuację i pomożemy uzyskać maksymalny zwrot z niemieckiego urzędu skarbowego.",
"buttonText": "Napisz na WhatsApp",
"blockName": null,
"blockType": "finalCta"
@@ -361,7 +361,7 @@
"id": "6a993b67a0e23e2a75a4cc8e",
"eyebrow": null,
"title": "Bereit für Ihre Steuererklärung?",
"subtitle": "Kontaktieren Sie mich noch heute. Ich analysiere Ihre Situation und unterstütze Sie bei der optimalen Erstattung.",
"subtitle": "Kontaktieren Sie uns noch heute. Wir analysieren Ihre Situation und unterstützen Sie bei der optimalen Erstattung.",
"buttonText": "Auf WhatsApp schreiben",
"blockName": null,
"blockType": "finalCta"
@@ -389,7 +389,7 @@
"id": "6a995ca5ea2f1a36632134e0",
"eyebrow": null,
"title": "Rozliczenie w 3 prostych krokach",
"subtitle": "Zapewniam pełne wsparcie i jasną komunikację na każdym etapie.",
"subtitle": "Zapewniamy pełne wsparcie i jasną komunikację na każdym etapie.",
"ctaText": null,
"ctaUrl": null,
"image": 1,
@@ -398,7 +398,7 @@
{
"id": "6a995ca5ea2f1a36632134dd",
"title": "Napisz na WhatsApp",
"description": "Skontaktuj się ze mną bezpośrednio przez WhatsApp lub formularz poniżej, aby rozpocząć i ustalić szczegóły Twojej sytuacji podatkowej."
"description": "Skontaktuj się z nami bezpośrednio przez WhatsApp lub formularz poniżej, aby rozpocząć i ustalić szczegóły Twojej sytuacji podatkowej."
},
{
"id": "6a995ca5ea2f1a36632134de",
@@ -408,7 +408,7 @@
{
"id": "6a995ca5ea2f1a36632134df",
"title": "Odbierz gotowe rozliczenie do podpisu",
"description": "Przygotowuję kompletną deklarację, wyliczam przewidywany zwrot z urzędu skarbowego i przesyłam gotowy dokument do Twojej akceptacji i podpisu."
"description": "Przygotowujemy kompletną deklarację, wyliczamy przewidywany zwrot z urzędu skarbowego i przesyłamy gotowy dokument do Twojej akceptacji i podpisu."
}
],
"blockType": "process"
@@ -426,7 +426,7 @@
"id": "6a995ca5ea2f1a36632134e2",
"eyebrow": null,
"title": "Chcesz szybko odzyskać podatek?",
"subtitle": "Nie zwlekaj z rozliczeniem. Napisz teraz, a zajmę się formalnościami od początku do końca.",
"subtitle": "Nie zwlekaj z rozliczeniem. Napisz teraz, a zajmiemy się formalnościami od początku do końca.",
"buttonText": "Napisz na WhatsApp",
"blockName": null,
"blockType": "finalCta"
@@ -460,7 +460,7 @@
{
"id": "6a995ca5ea2f1a36632134e4",
"title": "WhatsApp kontaktieren",
"description": "Schreiben Sie mir direkt über WhatsApp oder das untenstehende Formular, um Ihre Anfrage und steuerliche Situation zu besprechen."
"description": "Schreiben Sie uns direkt über WhatsApp oder das untenstehende Formular, um Ihre Anfrage und steuerliche Situation zu besprechen."
},
{
"id": "6a995ca5ea2f1a36632134e5",
@@ -470,7 +470,7 @@
{
"id": "6a995ca5ea2f1a36632134e6",
"title": "Fertige Erklärung zur Unterschrift",
"description": "Ich erstelle Ihre vollständige Erklärung, berechne die voraussichtliche Erstattung und übermittle Ihnen die Unterlagen zur Freigabe."
"description": "Wir erstellen Ihre vollständige Erklärung, berechnen die voraussichtliche Erstattung und übermitteln Ihnen die Unterlagen zur Freigabe."
}
],
"blockType": "process"
@@ -488,7 +488,7 @@
"id": "6a995ca5ea2f1a36632134e9",
"eyebrow": null,
"title": "Möchten Sie Ihre Steuererstattung sichern?",
"subtitle": "Zögern Sie nicht mit Ihrer Steuererklärung. Kontaktieren Sie mich jetzt für eine schnelle Bearbeitung.",
"subtitle": "Zögern Sie nicht mit Ihrer Steuererklärung. Kontaktieren Sie uns jetzt für eine schnelle Bearbeitung.",
"buttonText": "Auf WhatsApp schreiben",
"blockName": null,
"blockType": "finalCta"
@@ -514,8 +514,8 @@
},
{
"id": "6a988b8714359f1c1b30efd0",
"title": "Czym się zajmuję",
"subtitle": "Zapewniam rzetelne i kompleksowe wsparcie na każdym etapie rozliczenia podatku dochodowego w Niemczech.",
"title": "Czym się zajmujemy",
"subtitle": "Zapewniamy rzetelne i kompleksowe wsparcie na każdym etapie rozliczenia podatku dochodowego w Niemczech.",
"blockName": null,
"services": [
{
@@ -642,7 +642,7 @@
},
{
"id": "6a988b8714359f1c1b30efe4",
"title": "Was ich anbiete",
"title": "Was wir anbieten",
"subtitle": "Umfassende und zuverlässige Betreuung bei allen Fragen rund um Ihre Einkommensteuererklärung.",
"blockName": null,
"services": [
@@ -789,7 +789,7 @@
"items": [
{
"id": "6a988b8614359f1c1b30ef8d",
"text": "Działam w ramach b.b.h.",
"text": "Działamy w ramach b.b.h.",
"icon": "shield"
},
{
@@ -814,7 +814,7 @@
"id": "6a9917f7eb2fb5197987f4ba",
"eyebrow": null,
"title": "Rozliczenia bez bariery językowej",
"description": "Niemieckie prawo podatkowe bywa skomplikowane. Przeprowadzę Cię przez cały proces w Twoim ojczystym języku, dbając o maksymalny zwrot i zero stresu.",
"description": "Niemieckie prawo podatkowe bywa skomplikowane. Przeprowadzimy Cię przez cały proces w Twoim ojczystym języku, dbając o maksymalny zwrot i zero stresu.",
"image": 1,
"blockName": null,
"features": [
@@ -859,7 +859,7 @@
{
"id": "6a988b8614359f1c1b30ef9c",
"eyebrow": "Usługi",
"title": "Czym się zajmuję",
"title": "Czym się zajmujemy",
"blockName": null,
"services": [
{
@@ -915,7 +915,7 @@
"id": "6a988b8614359f1c1b30efa1",
"eyebrow": "Zaufanie",
"title": "Zaufanie i bezpieczeństwo",
"description": "Działam w ramach b.b.h. Lohnsteuerhilfeverein e.V. (Beratungsstelle). Oferujemy profesjonalną pomoc w sprawach podatku dochodowego w ramach uprawnień wynikających z §4 Nr. 11 StBerG.",
"description": "Działamy w ramach b.b.h. Lohnsteuerhilfeverein e.V. (Beratungsstelle). Oferujemy profesjonalną pomoc w sprawach podatku dochodowego w ramach uprawnień wynikających z §4 Nr. 11 StBerG.",
"logo": null,
"linkText": "Poznaj stowarzyszenie",
"linkUrl": "/pl/o-nas",
@@ -998,7 +998,7 @@
"id": "6a988b8614359f1c1b30efa9",
"eyebrow": "Kontakt",
"title": "Gotowy na wyższy zwrot podatku?",
"subtitle": "Napisz do mnie już teraz. Odpowiem najszybciej jak to możliwe.",
"subtitle": "Napisz do nas już teraz. Odpowiemy najszybciej jak to możliwe.",
"buttonText": "Rozpocznij na WhatsApp",
"blockName": null,
"blockType": "finalCta"
@@ -1107,7 +1107,7 @@
{
"id": "6a988b8714359f1c1b30efba",
"eyebrow": "Leistungen",
"title": "Was ich anbiete",
"title": "Was wir anbieten",
"blockName": null,
"services": [
{
@@ -1163,7 +1163,7 @@
"id": "6a988b8714359f1c1b30efbf",
"eyebrow": "Vertrauen",
"title": "Vertrauen und Sicherheit",
"description": "Ich bin im Rahmen des b.b.h. Lohnsteuerhilfevereins e.V. (Beratungsstelle) tätig. Wir bieten professionelle Hilfe in Lohnsteuersachen im Rahmen der Befugnisse nach §4 Nr. 11 StBerG.",
"description": "Wir sind im Rahmen des b.b.h. Lohnsteuerhilfevereins e.V. (Beratungsstelle) tätig. Wir bieten professionelle Hilfe in Lohnsteuersachen im Rahmen der Befugnisse nach §4 Nr. 11 StBerG.",
"logo": null,
"linkText": "Den Verein kennenlernen",
"linkUrl": "/de/uber-uns",
@@ -1241,7 +1241,7 @@
"id": "6a988b8714359f1c1b30efc7",
"eyebrow": "Kontakt",
"title": "Bereit für eine höhere Steuererstattung?",
"subtitle": "Schreiben Sie mir jetzt. Ich werde so schnell wie möglich antworten.",
"subtitle": "Schreiben Sie uns jetzt. Wir werden so schnell wie möglich antworten.",
"buttonText": "Auf WhatsApp starten",
"blockName": null,
"blockType": "finalCta"
@@ -1355,7 +1355,7 @@
}
],
"association": {
"title": "Działam w ramach b.b.h. Lohnsteuerhilfeverein e.V.",
"title": "Działamy w ramach b.b.h. Lohnsteuerhilfeverein e.V.",
"logo": null,
"link": "https://bbh-lohnsteuerhilfe.de"
},
@@ -1402,7 +1402,7 @@
}
],
"association": {
"title": "Ich bin im Rahmen des b.b.h. Lohnsteuerhilfevereins e.V. tätig.",
"title": "Wir sind im Rahmen des b.b.h. Lohnsteuerhilfevereins e.V. tätig.",
"logo": null,
"link": "https://bbh-lohnsteuerhilfe.de"
},
+53
View File
@@ -0,0 +1,53 @@
import fs from 'fs'
import path from 'path'
const snapshotPath = path.join(process.cwd(), 'scripts', 'content-snapshot.json')
const snapshot = JSON.parse(fs.readFileSync(snapshotPath, 'utf-8'))
const firstPersonRegexPL = /(^|[\s.,?!:;])(zapewniam|przeprowadzę|przeanalizuję|pomogę|pomagam|odpowiem|mnie|ze mną|moim|mój|zajmuję|działam|przygotowuję|przesyłam|wyliczam|zajmę)([\s.,?!:;]|$)/i
const firstPersonRegexDE = /(^|[\s.,?!:;])(ich|mich|mir|mein|meine|meinen|helfe|kümmere|begleite|anbiete)([\s.,?!:;]|$)/i
const matches: any[] = []
function checkObject(obj: any, contextInfo: any) {
if (Array.isArray(obj)) {
obj.forEach((item, index) => checkObject(item, { ...contextInfo, index }))
} else if (obj !== null && typeof obj === 'object') {
// Pomiń blok Bio
if (obj.blockType === 'bio') {
return
}
for (const [key, value] of Object.entries(obj)) {
if (typeof value === 'string') {
const isMatchPL = contextInfo.locale === 'pl' && firstPersonRegexPL.test(value)
const isMatchDE = contextInfo.locale === 'de' && firstPersonRegexDE.test(value)
if (isMatchPL || isMatchDE) {
matches.push({
pageSlug: contextInfo.pageSlug,
blockType: obj.blockType || 'unknown',
field: key,
locale: contextInfo.locale,
text: value
})
}
} else {
checkObject(value, { ...contextInfo, field: key })
}
}
}
}
for (const page of snapshot.pages) {
if (page.pl) checkObject(page.pl.layout, { pageSlug: page.pl.slug || 'home', locale: 'pl' })
if (page.de) checkObject(page.de.layout, { pageSlug: page.de.slug || 'home', locale: 'de' })
}
for (const [slug, data] of Object.entries(snapshot.globals)) {
const typedData = data as any;
if (typedData.pl) checkObject(typedData.pl, { pageSlug: `global:${slug}`, locale: 'pl' })
if (typedData.de) checkObject(typedData.de, { pageSlug: `global:${slug}`, locale: 'de' })
}
console.log(JSON.stringify(matches, null, 2))
+139
View File
@@ -0,0 +1,139 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
import fs from 'fs'
import path from 'path'
const replacements: { old: string, new: string }[] = [
// PL
{ old: 'Przeprowadzę Cię', new: 'Przeprowadzimy Cię' },
{ old: 'Czym się zajmuję', new: 'Czym się zajmujemy' },
{ old: 'Działam w ramach', new: 'Działamy w ramach' },
{ old: 'Napisz do mnie', new: 'Napisz do nas' },
{ old: 'Odpowiem najszybciej', new: 'Odpowiemy najszybciej' },
{ old: 'Zapewniam rzetelne', new: 'Zapewniamy rzetelne' },
{ old: 'Zapewniam pełne', new: 'Zapewniamy pełne' },
{ old: 'Skontaktuj się ze mną', new: 'Skontaktuj się z nami' },
{ old: 'Przygotowuję kompletną', new: 'Przygotowujemy kompletną' },
{ old: 'wyliczam przewidywany', new: 'wyliczamy przewidywany' },
{ old: 'przesyłam gotowy', new: 'przesyłamy gotowy' },
{ old: 'a zajmę się', new: 'a zajmiemy się' },
{ old: 'Przeanalizuję Twoją', new: 'Przeanalizujemy Twoją' },
{ old: 'pomogę uzyskać', new: 'pomożemy uzyskać' },
{ old: 'chętnie pomogę', new: 'chętnie pomożemy' },
// DE
{ old: 'Was ich anbiete', new: 'Was wir anbieten' },
{ old: 'Ich bin im Rahmen', new: 'Wir sind im Rahmen' },
{ old: 'Schreiben Sie mir jetzt', new: 'Schreiben Sie uns jetzt' },
{ old: 'Ich werde so schnell', new: 'Wir werden so schnell' },
{ old: 'Schreiben Sie mir direkt', new: 'Schreiben Sie uns direkt' },
{ old: 'Ich erstelle Ihre', new: 'Wir erstellen Ihre' },
{ old: 'berechne die', new: 'berechnen die' },
{ old: 'übermittle Ihnen', new: 'übermitteln Ihnen' },
{ old: 'Kontaktieren Sie mich jetzt', new: 'Kontaktieren Sie uns jetzt' },
{ old: 'Kontaktieren Sie mich noch', new: 'Kontaktieren Sie uns noch' },
{ old: 'Ich analysiere Ihre', new: 'Wir analysieren Ihre' },
{ old: 'und unterstütze Sie', new: 'und unterstützen Sie' },
{ old: 'Kontaktieren Sie mich — ich helfe', new: 'Kontaktieren Sie uns — wir helfen' },
]
function applyReplacements(text: string): string {
let result = text
for (const rep of replacements) {
result = result.replace(new RegExp(rep.old, 'g'), rep.new)
}
return result
}
function processObject(obj: any): any {
if (Array.isArray(obj)) {
return obj.map(item => processObject(item))
}
if (obj !== null && typeof obj === 'object') {
if (obj.blockType === 'bio') {
return obj // pomiń Bio
}
const newObj: any = {}
for (const [key, value] of Object.entries(obj)) {
if (typeof value === 'string') {
newObj[key] = applyReplacements(value)
} else {
newObj[key] = processObject(value)
}
}
return newObj
}
return obj
}
async function run() {
console.log('Rozpoczynam migrację z pierwszej na pierwszą osobę liczby mnogiej...')
const payload = await getPayload({ config: configPromise })
// 1. Aktualizacja zawartości w bazie
console.log('--- Aktualizowanie bazy danych ---')
for (const locale of ['pl', 'de'] as const) {
// Pages
const pages = await payload.find({
collection: 'pages',
locale,
limit: 100
})
for (const page of pages.docs) {
if (!page.layout) continue
const newLayout = processObject(page.layout)
// Optymalizacja: sprawdźmy czy layout się zmienił po stringify
if (JSON.stringify(page.layout) !== JSON.stringify(newLayout)) {
console.log(`[${locale}] Aktualizacja strony: ${page.title} (ID: ${page.id})`)
await payload.update({
collection: 'pages',
id: page.id,
locale,
data: { layout: newLayout }
})
}
}
// Globals (footer)
const footer = await payload.findGlobal({
slug: 'footer',
locale
})
if (footer) {
const newFooter = processObject(footer)
if (JSON.stringify(footer) !== JSON.stringify(newFooter)) {
console.log(`[${locale}] Aktualizacja stopki (footer)`)
await payload.updateGlobal({
slug: 'footer',
locale,
data: newFooter
})
}
}
}
// 2. Aktualizacja w content-snapshot.json
console.log('--- Aktualizowanie pliku content-snapshot.json ---')
const snapshotPath = path.join(process.cwd(), 'scripts', 'content-snapshot.json')
const snapshotText = fs.readFileSync(snapshotPath, 'utf-8')
// Przetwórz po prostu cały JSON rekursywnie. Zabezpieczamy blok "bio" w processObject
const snapshotJson = JSON.parse(snapshotText)
const newSnapshotJson = processObject(snapshotJson)
if (JSON.stringify(snapshotJson) !== JSON.stringify(newSnapshotJson)) {
fs.writeFileSync(snapshotPath, JSON.stringify(newSnapshotJson, null, 2), 'utf-8')
console.log('Zaktualizowano plik content-snapshot.json')
} else {
console.log('Brak zmian w content-snapshot.json (być może zostało już zrobione)')
}
console.log('Zakończono pomyślnie.')
process.exit(0)
}
run()
+86
View File
@@ -0,0 +1,86 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
import type { Page } from '../src/payload-types'
type PageHeaderBlock = Extract<NonNullable<Page['layout']>[0], { blockType: 'pageHeader' }>
type ContactInfoBlock = Extract<NonNullable<Page['layout']>[0], { blockType: 'contactInfo' }>
const plHeader: PageHeaderBlock = {
blockType: 'pageHeader',
title: 'Kontakt',
subtitle: 'Masz pytania dotyczące rozliczenia podatkowego w Niemczech? Skontaktuj się ze mną — chętnie pomogę.',
}
const deHeader: PageHeaderBlock = {
blockType: 'pageHeader',
title: 'Kontakt',
subtitle: 'Haben Sie Fragen zu Ihrer Steuererklärung in Deutschland? Kontaktieren Sie mich — ich helfe Ihnen gerne.',
}
const plContact: ContactInfoBlock = {
blockType: 'contactInfo',
address: 'DO POTWIERDZENIA PRZEZ KLIENTA',
phone: '+49 123 456 789',
email: '[email protected]',
hours: 'Pon-Pt: 9:00 - 17:00\nPo wcześniejszym umówieniu',
}
const deContact: ContactInfoBlock = {
blockType: 'contactInfo',
address: 'DO POTWIERDZENIA PRZEZ KLIENTA',
phone: '+49 123 456 789',
email: '[email protected]',
hours: 'Mo-Fr: 9:00 - 17:00\nNach vorheriger Terminvereinbarung',
}
async function run() {
console.log('Rozpoczynam odzyskiwanie strony Kontakt (ID 5)...')
const payload = await getPayload({ config: configPromise })
for (const locale of ['pl', 'de'] as const) {
try {
const page = await payload.findByID({
collection: 'pages',
id: 5,
locale: locale
})
const hasPageHeader = page.layout?.some(block => block.blockType === 'pageHeader')
const hasContactInfo = page.layout?.some(block => block.blockType === 'contactInfo')
if (hasPageHeader && hasContactInfo) {
console.log(`[${locale}] Strona Kontakt posiada już bloki pageHeader i contactInfo. Pomijam.`)
continue;
}
console.log(`[${locale}] Przywracam utracone bloki...`)
// Jeśli strona miała jakieś bloki, zachowaj je (z wyjątkiem ewentualnych starych/uszkodzonych wersji tych dwóch)
let newLayout = (page.layout ? page.layout.filter(b => b.blockType !== 'pageHeader' && b.blockType !== 'contactInfo') : []) as NonNullable<Page['layout']>;
// Dodaj nagłówek i info kontaktowe na górę
newLayout.unshift(
(locale === 'de' ? deHeader : plHeader) as unknown as NonNullable<Page['layout']>[0],
(locale === 'de' ? deContact : plContact) as unknown as NonNullable<Page['layout']>[0]
);
await payload.update({
collection: 'pages',
id: 5,
locale: locale,
data: {
layout: newLayout
}
})
console.log(`[${locale}] Zaktualizowano pomyślnie.`)
} catch (error) {
console.error(`[${locale}] Błąd podczas aktualizacji:`, error)
}
}
console.log('Zakończono.')
process.exit(0)
}
run()
+27
View File
@@ -0,0 +1,27 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
async function run() {
const payload = await getPayload({ config: configPromise })
const plPage = await payload.findByID({
collection: 'pages',
id: 5,
locale: 'pl'
})
const dePage = await payload.findByID({
collection: 'pages',
id: 5,
locale: 'de'
})
console.log("=== KONTAKT PL LAYOUT ===")
console.log(JSON.stringify(plPage.layout, null, 2))
console.log("=== KONTAKT DE LAYOUT ===")
console.log(JSON.stringify(dePage.layout, null, 2))
process.exit(0)
}
run()