fix: migrate all hardcoded eyebrow labels and text to CMS to fix locale bleeding
This commit is contained in:
@@ -0,0 +1,8 @@
|
|||||||
|
with open("scripts/seed.ts", "r") as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
content = content.replace("eyebrow: 'Statystyki',\n items: [\n {\n stat: '20+',", "eyebrow: 'Statistiken',\n items: [\n {\n stat: '20+',")
|
||||||
|
content = content.replace("eyebrow: 'Usługi',\n title: 'Was ich anbiete',", "eyebrow: 'Leistungen',\n title: 'Was ich anbiete',")
|
||||||
|
|
||||||
|
with open("scripts/seed.ts", "w") as f:
|
||||||
|
f.write(content)
|
||||||
@@ -27,6 +27,7 @@ const homeLayoutPl = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
blockType: 'trustBar',
|
blockType: 'trustBar',
|
||||||
|
eyebrow: 'Zaufanie',
|
||||||
items: [
|
items: [
|
||||||
{ text: 'Działam w ramach b.b.h.', icon: 'shield' },
|
{ text: 'Działam w ramach b.b.h.', icon: 'shield' },
|
||||||
{ text: 'Doradztwo 100% po polsku', icon: 'message' },
|
{ text: 'Doradztwo 100% po polsku', icon: 'message' },
|
||||||
@@ -36,11 +37,14 @@ const homeLayoutPl = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
blockType: 'uniqueValue',
|
blockType: 'uniqueValue',
|
||||||
|
eyebrow: 'Wartość',
|
||||||
title: 'Rozliczenia bez bariery językowej',
|
title: 'Rozliczenia bez bariery językowej',
|
||||||
|
features: [{ text: 'Bez barier językowych' }, { text: 'Indywidualne podejście' }],
|
||||||
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. Przeprowadzę Cię przez cały proces w Twoim ojczystym języku, dbając o maksymalny zwrot i zero stresu.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
blockType: 'stats',
|
blockType: 'stats',
|
||||||
|
eyebrow: 'Statistiken',
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
stat: '20+',
|
stat: '20+',
|
||||||
@@ -61,6 +65,7 @@ const homeLayoutPl = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
blockType: 'servicesList',
|
blockType: 'servicesList',
|
||||||
|
eyebrow: 'Usługi',
|
||||||
title: 'Czym się zajmuję',
|
title: 'Czym się zajmuję',
|
||||||
services: [
|
services: [
|
||||||
{ title: 'Deklaracje podatkowe', description: 'Kompleksowe sporządzenie i złożenie zeznania rocznego (Einkommensteuererklärung).', linkText: 'Zobacz więcej' },
|
{ title: 'Deklaracje podatkowe', description: 'Kompleksowe sporządzenie i złożenie zeznania rocznego (Einkommensteuererklärung).', linkText: 'Zobacz więcej' },
|
||||||
@@ -70,7 +75,9 @@ const homeLayoutPl = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
blockType: 'process',
|
blockType: 'process',
|
||||||
|
eyebrow: 'Proces',
|
||||||
title: 'Jak to działa',
|
title: 'Jak to działa',
|
||||||
|
subtitle: 'Prosty i przejrzysty proces rozliczenia w 3 krokach — bez zbędnych formalności.',
|
||||||
steps: [
|
steps: [
|
||||||
{ title: 'Napisz na WhatsApp', description: 'Wyślij krótką wiadomość lub zadzwoń, by rozpocząć proces.' },
|
{ title: 'Napisz na WhatsApp', description: 'Wyślij krótką wiadomość lub zadzwoń, by rozpocząć proces.' },
|
||||||
{ title: 'Przekaż dokumenty', description: 'Prześlij nam wymagane dokumenty bezpiecznie online lub dostarcz osobiście.' },
|
{ title: 'Przekaż dokumenty', description: 'Prześlij nam wymagane dokumenty bezpiecznie online lub dostarcz osobiście.' },
|
||||||
@@ -80,6 +87,7 @@ const homeLayoutPl = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
blockType: 'affiliation',
|
blockType: 'affiliation',
|
||||||
|
eyebrow: 'Zaufanie',
|
||||||
title: 'Zaufanie i bezpieczeństwo',
|
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ł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.',
|
||||||
linkText: 'Poznaj stowarzyszenie',
|
linkText: 'Poznaj stowarzyszenie',
|
||||||
@@ -95,6 +103,7 @@ const homeLayoutPl = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
blockType: 'faqTeaser',
|
blockType: 'faqTeaser',
|
||||||
|
eyebrow: 'FAQ',
|
||||||
title: 'Częste pytania',
|
title: 'Częste pytania',
|
||||||
questions: [
|
questions: [
|
||||||
{ question: 'Czy muszę przyjeżdżać osobiście?', answer: 'Nie, wszystkie formalności możemy załatwić zdalnie, przesyłając dokumenty online.' },
|
{ question: 'Czy muszę przyjeżdżać osobiście?', answer: 'Nie, wszystkie formalności możemy załatwić zdalnie, przesyłając dokumenty online.' },
|
||||||
@@ -104,6 +113,7 @@ const homeLayoutPl = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
blockType: 'finalCta',
|
blockType: 'finalCta',
|
||||||
|
eyebrow: 'Kontakt',
|
||||||
title: 'Gotowy na wyższy zwrot podatku?',
|
title: 'Gotowy na wyższy zwrot podatku?',
|
||||||
subtitle: 'Napisz do mnie już teraz. Odpowiem najszybciej jak to możliwe.',
|
subtitle: 'Napisz do mnie już teraz. Odpowiem najszybciej jak to możliwe.',
|
||||||
buttonText: 'Rozpocznij na WhatsApp',
|
buttonText: 'Rozpocznij na WhatsApp',
|
||||||
@@ -121,6 +131,7 @@ const homeLayoutDe = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
blockType: 'trustBar',
|
blockType: 'trustBar',
|
||||||
|
eyebrow: 'Zaufanie',
|
||||||
items: [
|
items: [
|
||||||
{ text: 'Teil der b.b.h.', icon: 'shield' },
|
{ text: 'Teil der b.b.h.', icon: 'shield' },
|
||||||
{ text: '100% Beratung auf Polnisch', icon: 'message' },
|
{ text: '100% Beratung auf Polnisch', icon: 'message' },
|
||||||
@@ -130,11 +141,14 @@ const homeLayoutDe = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
blockType: 'uniqueValue',
|
blockType: 'uniqueValue',
|
||||||
|
eyebrow: 'Wert',
|
||||||
title: 'Steuererklärungen ohne Sprachbarriere',
|
title: 'Steuererklärungen ohne Sprachbarriere',
|
||||||
|
features: [{ text: 'Ohne Sprachbarrieren' }, { text: 'Individueller Ansatz' }],
|
||||||
description: 'Das deutsche Steuerrecht kann kompliziert sein. Ich begleite Sie durch den gesamten Prozess in Ihrer Muttersprache, sorge für die maximale Erstattung und garantiere null Stress.',
|
description: 'Das deutsche Steuerrecht kann kompliziert sein. Ich begleite Sie durch den gesamten Prozess in Ihrer Muttersprache, sorge für die maximale Erstattung und garantiere null Stress.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
blockType: 'stats',
|
blockType: 'stats',
|
||||||
|
eyebrow: 'Statistiken',
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
stat: '20+',
|
stat: '20+',
|
||||||
@@ -155,6 +169,7 @@ const homeLayoutDe = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
blockType: 'servicesList',
|
blockType: 'servicesList',
|
||||||
|
eyebrow: 'Leistungen',
|
||||||
title: 'Was ich anbiete',
|
title: 'Was ich anbiete',
|
||||||
services: [
|
services: [
|
||||||
{ title: 'Steuererklärungen', description: 'Umfassende Erstellung und Einreichung der jährlichen Einkommensteuererklärung.', linkText: 'Mehr anzeigen' },
|
{ title: 'Steuererklärungen', description: 'Umfassende Erstellung und Einreichung der jährlichen Einkommensteuererklärung.', linkText: 'Mehr anzeigen' },
|
||||||
@@ -164,7 +179,9 @@ const homeLayoutDe = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
blockType: 'process',
|
blockType: 'process',
|
||||||
|
eyebrow: 'Prozess',
|
||||||
title: 'Wie es funktioniert',
|
title: 'Wie es funktioniert',
|
||||||
|
subtitle: 'Ein einfacher und transparenter 3-Schritte-Abrechnungsprozess — ohne unnötige Formalitäten.',
|
||||||
steps: [
|
steps: [
|
||||||
{ title: 'Auf WhatsApp schreiben', description: 'Senden Sie eine kurze Nachricht oder rufen Sie an, um den Prozess zu starten.' },
|
{ title: 'Auf WhatsApp schreiben', description: 'Senden Sie eine kurze Nachricht oder rufen Sie an, um den Prozess zu starten.' },
|
||||||
{ title: 'Dokumente einreichen', description: 'Senden Sie uns die erforderlichen Dokumente sicher online oder bringen Sie sie persönlich vorbei.' },
|
{ title: 'Dokumente einreichen', description: 'Senden Sie uns die erforderlichen Dokumente sicher online oder bringen Sie sie persönlich vorbei.' },
|
||||||
@@ -174,6 +191,7 @@ const homeLayoutDe = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
blockType: 'affiliation',
|
blockType: 'affiliation',
|
||||||
|
eyebrow: 'Vertrauen',
|
||||||
title: 'Vertrauen und Sicherheit',
|
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: '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.',
|
||||||
linkText: 'Den Verein kennenlernen',
|
linkText: 'Den Verein kennenlernen',
|
||||||
@@ -189,6 +207,7 @@ const homeLayoutDe = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
blockType: 'faqTeaser',
|
blockType: 'faqTeaser',
|
||||||
|
eyebrow: 'FAQ',
|
||||||
title: 'Häufige Fragen',
|
title: 'Häufige Fragen',
|
||||||
questions: [
|
questions: [
|
||||||
{ question: 'Muss ich persönlich kommen?', answer: 'Nein, alle Formalitäten können online erledigt werden, indem Sie uns die Dokumente zusenden.' },
|
{ question: 'Muss ich persönlich kommen?', answer: 'Nein, alle Formalitäten können online erledigt werden, indem Sie uns die Dokumente zusenden.' },
|
||||||
@@ -198,6 +217,7 @@ const homeLayoutDe = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
blockType: 'finalCta',
|
blockType: 'finalCta',
|
||||||
|
eyebrow: 'Kontakt',
|
||||||
title: 'Bereit für eine höhere Steuererstattung?',
|
title: 'Bereit für eine höhere Steuererstattung?',
|
||||||
subtitle: 'Schreiben Sie mir jetzt. Ich werde so schnell wie möglich antworten.',
|
subtitle: 'Schreiben Sie mir jetzt. Ich werde so schnell wie möglich antworten.',
|
||||||
buttonText: 'Auf WhatsApp starten',
|
buttonText: 'Auf WhatsApp starten',
|
||||||
@@ -212,6 +232,7 @@ const uslugiLayoutPl = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
blockType: 'servicesDetailed',
|
blockType: 'servicesDetailed',
|
||||||
|
eyebrow: 'Usługi',
|
||||||
title: 'Czym się zajmuję',
|
title: 'Czym się zajmuję',
|
||||||
subtitle: 'Zapewniam rzetelne i kompleksowe wsparcie na każdym etapie rozliczenia podatku dochodowego w Niemczech.',
|
subtitle: 'Zapewniam rzetelne i kompleksowe wsparcie na każdym etapie rozliczenia podatku dochodowego w Niemczech.',
|
||||||
services: [
|
services: [
|
||||||
@@ -254,6 +275,7 @@ const uslugiLayoutPl = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
blockType: 'eligibility',
|
blockType: 'eligibility',
|
||||||
|
eyebrow: 'Zakres uprawnień doradczych',
|
||||||
title: 'Dla kogo — zakres uprawnień doradczych wg §4 Nr. 11 StBerG',
|
title: 'Dla kogo — zakres uprawnień doradczych wg §4 Nr. 11 StBerG',
|
||||||
subtitle: 'Jako punkt doradczy b.b.h. Lohnsteuerhilfeverein e.V. działamy ściśle w ramach ustawowych uprawnień dla stowarzyszeń pomocy podatkowej.',
|
subtitle: 'Jako punkt doradczy b.b.h. Lohnsteuerhilfeverein e.V. działamy ściśle w ramach ustawowych uprawnień dla stowarzyszeń pomocy podatkowej.',
|
||||||
eligibleTitle: 'Kto może skorzystać z naszej pomocy',
|
eligibleTitle: 'Kto może skorzystać z naszej pomocy',
|
||||||
@@ -277,6 +299,7 @@ const uslugiLayoutPl = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
blockType: 'finalCta',
|
blockType: 'finalCta',
|
||||||
|
eyebrow: 'Kontakt',
|
||||||
title: 'Masz pytania dotyczące Twojego rozliczenia?',
|
title: 'Masz pytania dotyczące Twojego rozliczenia?',
|
||||||
subtitle: 'Skontaktuj się bezpośrednio przez WhatsApp. Przeanalizujemy Twoją sytuację i pomożemy uzyskać należny zwrot.',
|
subtitle: 'Skontaktuj się bezpośrednio przez WhatsApp. Przeanalizujemy Twoją sytuację i pomożemy uzyskać należny zwrot.',
|
||||||
buttonText: 'Napisz na WhatsApp',
|
buttonText: 'Napisz na WhatsApp',
|
||||||
@@ -291,6 +314,7 @@ const uslugiLayoutDe = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
blockType: 'servicesDetailed',
|
blockType: 'servicesDetailed',
|
||||||
|
eyebrow: 'Leistungen',
|
||||||
title: 'Was ich anbiete',
|
title: 'Was ich anbiete',
|
||||||
subtitle: 'Umfassende und zuverlässige Betreuung bei allen Fragen rund um Ihre Einkommensteuererklärung.',
|
subtitle: 'Umfassende und zuverlässige Betreuung bei allen Fragen rund um Ihre Einkommensteuererklärung.',
|
||||||
services: [
|
services: [
|
||||||
@@ -333,6 +357,7 @@ const uslugiLayoutDe = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
blockType: 'eligibility',
|
blockType: 'eligibility',
|
||||||
|
eyebrow: 'Umfang der Beratungsbefugnis',
|
||||||
title: 'Beratungsbefugnis gemäß § 4 Nr. 11 StBerG',
|
title: 'Beratungsbefugnis gemäß § 4 Nr. 11 StBerG',
|
||||||
subtitle: 'Als Beratungsstelle des b.b.h. Lohnsteuerhilfevereins e.V. betreuen wir Sie streng im Rahmen der gesetzlichen Befugnisse.',
|
subtitle: 'Als Beratungsstelle des b.b.h. Lohnsteuerhilfevereins e.V. betreuen wir Sie streng im Rahmen der gesetzlichen Befugnisse.',
|
||||||
eligibleTitle: 'Wer von uns beraten werden darf',
|
eligibleTitle: 'Wer von uns beraten werden darf',
|
||||||
@@ -356,6 +381,7 @@ const uslugiLayoutDe = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
blockType: 'finalCta',
|
blockType: 'finalCta',
|
||||||
|
eyebrow: 'Kontakt',
|
||||||
title: 'Haben Sie Fragen zu Ihrer Steuererklärung?',
|
title: 'Haben Sie Fragen zu Ihrer Steuererklärung?',
|
||||||
subtitle: 'Kontaktieren Sie uns direkt per WhatsApp. Wir prüfen Ihre Situation und helfen Ihnen bei der Rückerstattung.',
|
subtitle: 'Kontaktieren Sie uns direkt per WhatsApp. Wir prüfen Ihre Situation und helfen Ihnen bei der Rückerstattung.',
|
||||||
buttonText: 'Auf WhatsApp schreiben',
|
buttonText: 'Auf WhatsApp schreiben',
|
||||||
|
|||||||
@@ -0,0 +1,121 @@
|
|||||||
|
import os
|
||||||
|
import glob
|
||||||
|
|
||||||
|
blocks_dir = "src/blocks"
|
||||||
|
|
||||||
|
blocks_with_eyebrow = [
|
||||||
|
"Affiliation",
|
||||||
|
"Eligibility",
|
||||||
|
"FAQTeaser",
|
||||||
|
"FinalCTA",
|
||||||
|
"Process",
|
||||||
|
"ServicesDetailed",
|
||||||
|
"ServicesList",
|
||||||
|
"Stats",
|
||||||
|
"UniqueValue"
|
||||||
|
]
|
||||||
|
|
||||||
|
for block in blocks_with_eyebrow:
|
||||||
|
config_path = os.path.join(blocks_dir, block, "config.ts")
|
||||||
|
component_path = os.path.join(blocks_dir, block, "Component.tsx")
|
||||||
|
|
||||||
|
if os.path.exists(config_path):
|
||||||
|
with open(config_path, "r") as f:
|
||||||
|
content = f.read()
|
||||||
|
if "'eyebrow'" not in content:
|
||||||
|
content = content.replace(
|
||||||
|
"{ name: 'title', type: 'text', required: true, localized: true },",
|
||||||
|
"{ name: 'eyebrow', type: 'text', localized: true },\n { name: 'title', type: 'text', required: true, localized: true },"
|
||||||
|
)
|
||||||
|
# Handle some variations
|
||||||
|
content = content.replace(
|
||||||
|
"{ name: 'title', type: 'text', localized: true },",
|
||||||
|
"{ name: 'eyebrow', type: 'text', localized: true },\n { name: 'title', type: 'text', localized: true },"
|
||||||
|
)
|
||||||
|
with open(config_path, "w") as f:
|
||||||
|
f.write(content)
|
||||||
|
|
||||||
|
if os.path.exists(component_path):
|
||||||
|
with open(component_path, "r") as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
# Add eyebrow to Props destructuring
|
||||||
|
if "eyebrow" not in content:
|
||||||
|
# Add to destructuring, find where title is destructured
|
||||||
|
# E.g. const FAQTeaserBlock: React.FC<Props> = ({ title, questions, linkText }) => {
|
||||||
|
# Find the string `({ ` and see if `title` is inside.
|
||||||
|
# Just do a simple regex or string replace.
|
||||||
|
import re
|
||||||
|
content = re.sub(r'({[\s\S]*?title(.*?)\}) =>', r'{ eyebrow, title\2} =>', content, count=1)
|
||||||
|
|
||||||
|
# Replace the hardcoded string with {eyebrow}
|
||||||
|
# The hardcoded strings are inside <div className="font-mono text-sm md:text-base font-bold text-accent tracking-widest uppercase mb-4">
|
||||||
|
# // Hardcoded
|
||||||
|
# </div>
|
||||||
|
content = re.sub(
|
||||||
|
r'<div className="font-mono text-sm md:text-base font-bold text-accent tracking-widest uppercase mb-4">\s*//.*?\s*</div>',
|
||||||
|
r'{eyebrow && (\n <div className="font-mono text-sm md:text-base font-bold text-accent tracking-widest uppercase mb-4">\n // {eyebrow}\n </div>\n )}',
|
||||||
|
content
|
||||||
|
)
|
||||||
|
|
||||||
|
with open(component_path, "w") as f:
|
||||||
|
f.write(content)
|
||||||
|
|
||||||
|
# Specific fixes for Process and UniqueValue
|
||||||
|
process_config = os.path.join(blocks_dir, "Process", "config.ts")
|
||||||
|
with open(process_config, "r") as f:
|
||||||
|
content = f.read()
|
||||||
|
if "'subtitle'" not in content:
|
||||||
|
content = content.replace(
|
||||||
|
"{ name: 'title', type: 'text', required: true, localized: true },",
|
||||||
|
"{ name: 'title', type: 'text', required: true, localized: true },\n { name: 'subtitle', type: 'textarea', localized: true },"
|
||||||
|
)
|
||||||
|
with open(process_config, "w") as f:
|
||||||
|
f.write(content)
|
||||||
|
|
||||||
|
process_component = os.path.join(blocks_dir, "Process", "Component.tsx")
|
||||||
|
with open(process_component, "r") as f:
|
||||||
|
content = f.read()
|
||||||
|
if "subtitle" not in content.split("}) =>")[0] and "ProcessBlock" in content:
|
||||||
|
content = re.sub(r'({.*?eyebrow,\s*title(.*?)\}) =>', r'{ eyebrow, title, subtitle\2} =>', content, count=1)
|
||||||
|
content = re.sub(
|
||||||
|
r'<p className="text-foreground/75 text-lg md:text-xl font-light leading-relaxed">\s*Prosty.*?\s*</p>',
|
||||||
|
r'{subtitle && (\n <p className="text-foreground/75 text-lg md:text-xl font-light leading-relaxed">\n {subtitle}\n </p>\n )}',
|
||||||
|
content
|
||||||
|
)
|
||||||
|
with open(process_component, "w") as f:
|
||||||
|
f.write(content)
|
||||||
|
|
||||||
|
uv_config = os.path.join(blocks_dir, "UniqueValue", "config.ts")
|
||||||
|
with open(uv_config, "r") as f:
|
||||||
|
content = f.read()
|
||||||
|
if "'features'" not in content:
|
||||||
|
content = content.replace(
|
||||||
|
" ],\n}",
|
||||||
|
" {\n name: 'features',\n type: 'array',\n localized: true,\n fields: [\n { name: 'text', type: 'text', required: true },\n ],\n },\n ],\n}"
|
||||||
|
)
|
||||||
|
with open(uv_config, "w") as f:
|
||||||
|
f.write(content)
|
||||||
|
|
||||||
|
uv_component = os.path.join(blocks_dir, "UniqueValue", "Component.tsx")
|
||||||
|
with open(uv_component, "r") as f:
|
||||||
|
content = f.read()
|
||||||
|
if "features" not in content.split("}) =>")[0] and "UniqueValueBlock" in content:
|
||||||
|
content = re.sub(r'({.*?eyebrow,\s*title(.*?)\}) =>', r'{ eyebrow, title, features\2} =>', content, count=1)
|
||||||
|
content = re.sub(
|
||||||
|
r'<div className="grid grid-cols-1 sm:grid-cols-2 gap-6 w-full pt-6 border-t border-border/60 text-base">\s*<div className="flex items-center gap-2.5 text-foreground/80 font-medium">\s*<span className="w-2 h-2 rounded-full bg-accent" />\s*<span>Bez barier.*?\s*</div>\s*<div className="flex items-center gap-2.5 text-foreground/80 font-medium">\s*<span className="w-2 h-2 rounded-full bg-accent" />\s*<span>Indywidualne.*?\s*</div>\s*</div>',
|
||||||
|
r'''{features && features.length > 0 && (
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-6 w-full pt-6 border-t border-border/60 text-base">
|
||||||
|
{features.map((feature: any, index: number) => (
|
||||||
|
<div key={feature.id || index} className="flex items-center gap-2.5 text-foreground/80 font-medium">
|
||||||
|
<span className="w-2 h-2 rounded-full bg-accent" />
|
||||||
|
<span>{feature.text}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}''',
|
||||||
|
content,
|
||||||
|
flags=re.DOTALL
|
||||||
|
)
|
||||||
|
with open(uv_component, "w") as f:
|
||||||
|
f.write(content)
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import re
|
||||||
|
import os
|
||||||
|
|
||||||
|
def insert_prop(filepath, prop_name):
|
||||||
|
with open(filepath, 'r') as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
# Find the interface declaration and insert `eyebrow?: string | null`
|
||||||
|
content = re.sub(r'(export interface \w+Props \{)', r'\1\n eyebrow?: string | null', content)
|
||||||
|
|
||||||
|
with open(filepath, 'w') as f:
|
||||||
|
f.write(content)
|
||||||
|
|
||||||
|
insert_prop('src/blocks/Eligibility/Component.tsx', 'eyebrow?: string | null')
|
||||||
|
insert_prop('src/blocks/ServicesDetailed/Component.tsx', 'eyebrow?: string | null')
|
||||||
|
insert_prop('src/blocks/Stats/Component.tsx', 'eyebrow?: string | null')
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
|
blocks_dir = "src/blocks"
|
||||||
|
|
||||||
|
blocks_with_eyebrow = [
|
||||||
|
"Affiliation",
|
||||||
|
"Eligibility",
|
||||||
|
"FAQTeaser",
|
||||||
|
"FinalCTA",
|
||||||
|
"ServicesList",
|
||||||
|
"Stats",
|
||||||
|
]
|
||||||
|
|
||||||
|
for block in blocks_with_eyebrow:
|
||||||
|
component_path = os.path.join(blocks_dir, block, "Component.tsx")
|
||||||
|
if os.path.exists(component_path):
|
||||||
|
with open(component_path, "r") as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
# Replace `({ title,` with `({ eyebrow, title,`
|
||||||
|
# Also need to handle cases where there is no space after {
|
||||||
|
content = re.sub(r'\(\{\s*title', '({ eyebrow, title', content)
|
||||||
|
|
||||||
|
with open(component_path, "w") as f:
|
||||||
|
f.write(content)
|
||||||
|
|
||||||
|
# Process block
|
||||||
|
process = os.path.join(blocks_dir, "Process", "Component.tsx")
|
||||||
|
with open(process, "r") as f:
|
||||||
|
content = f.read()
|
||||||
|
content = re.sub(r'\(\{\s*title', '({ eyebrow, subtitle, title', content)
|
||||||
|
with open(process, "w") as f:
|
||||||
|
f.write(content)
|
||||||
|
|
||||||
|
# UniqueValue block
|
||||||
|
uv = os.path.join(blocks_dir, "UniqueValue", "Component.tsx")
|
||||||
|
with open(uv, "r") as f:
|
||||||
|
content = f.read()
|
||||||
|
content = re.sub(r'\(\{\s*title', '({ eyebrow, features, title', content)
|
||||||
|
with open(uv, "w") as f:
|
||||||
|
f.write(content)
|
||||||
|
|
||||||
|
# ServicesDetailed block
|
||||||
|
sd = os.path.join(blocks_dir, "ServicesDetailed", "Component.tsx")
|
||||||
|
with open(sd, "r") as f:
|
||||||
|
content = f.read()
|
||||||
|
content = re.sub(r'\(\{\s*title', '({ eyebrow, title', content)
|
||||||
|
with open(sd, "w") as f:
|
||||||
|
f.write(content)
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
import re
|
||||||
|
|
||||||
|
with open("scripts/seed.ts", "r") as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
# homeLayoutPl
|
||||||
|
content = content.replace(
|
||||||
|
"blockType: 'trustBar',",
|
||||||
|
"blockType: 'trustBar',\n eyebrow: 'Zaufanie',"
|
||||||
|
)
|
||||||
|
content = content.replace(
|
||||||
|
"blockType: 'uniqueValue',\n title: 'Rozliczenia bez bariery językowej',",
|
||||||
|
"blockType: 'uniqueValue',\n eyebrow: 'Wartość',\n title: 'Rozliczenia bez bariery językowej',\n features: [{ text: 'Bez barier językowych' }, { text: 'Indywidualne podejście' }],"
|
||||||
|
)
|
||||||
|
content = content.replace(
|
||||||
|
"blockType: 'stats',\n items:",
|
||||||
|
"blockType: 'stats',\n eyebrow: 'Statystyki',\n items:"
|
||||||
|
)
|
||||||
|
content = content.replace(
|
||||||
|
"blockType: 'servicesList',\n title:",
|
||||||
|
"blockType: 'servicesList',\n eyebrow: 'Usługi',\n title:"
|
||||||
|
)
|
||||||
|
content = content.replace(
|
||||||
|
"blockType: 'process',\n title: 'Jak to działa',",
|
||||||
|
"blockType: 'process',\n eyebrow: 'Proces',\n title: 'Jak to działa',\n subtitle: 'Prosty i przejrzysty proces rozliczenia w 3 krokach — bez zbędnych formalności.',"
|
||||||
|
)
|
||||||
|
content = content.replace(
|
||||||
|
"blockType: 'affiliation',\n title: 'Zaufanie i bezpieczeństwo',",
|
||||||
|
"blockType: 'affiliation',\n eyebrow: 'Zaufanie',\n title: 'Zaufanie i bezpieczeństwo',"
|
||||||
|
)
|
||||||
|
content = content.replace(
|
||||||
|
"blockType: 'faqTeaser',\n title:",
|
||||||
|
"blockType: 'faqTeaser',\n eyebrow: 'FAQ',\n title:"
|
||||||
|
)
|
||||||
|
content = content.replace(
|
||||||
|
"blockType: 'finalCta',\n title: 'Gotowy na wyższy zwrot podatku?',",
|
||||||
|
"blockType: 'finalCta',\n eyebrow: 'Kontakt',\n title: 'Gotowy na wyższy zwrot podatku?',"
|
||||||
|
)
|
||||||
|
content = content.replace(
|
||||||
|
"blockType: 'finalCta',\n title: 'Masz pytania dotyczące Twojego rozliczenia?',",
|
||||||
|
"blockType: 'finalCta',\n eyebrow: 'Kontakt',\n title: 'Masz pytania dotyczące Twojego rozliczenia?',"
|
||||||
|
)
|
||||||
|
content = content.replace(
|
||||||
|
"blockType: 'servicesDetailed',\n title: 'Czym się zajmuję',",
|
||||||
|
"blockType: 'servicesDetailed',\n eyebrow: 'Usługi',\n title: 'Czym się zajmuję',"
|
||||||
|
)
|
||||||
|
content = content.replace(
|
||||||
|
"blockType: 'eligibility',\n title: 'Dla kogo — zakres uprawnień doradczych wg §4 Nr. 11 StBerG',",
|
||||||
|
"blockType: 'eligibility',\n eyebrow: 'Zakres uprawnień doradczych',\n title: 'Dla kogo — zakres uprawnień doradczych wg §4 Nr. 11 StBerG',"
|
||||||
|
)
|
||||||
|
|
||||||
|
# homeLayoutDe
|
||||||
|
content = content.replace(
|
||||||
|
"blockType: 'uniqueValue',\n title: 'Steuererklärungen ohne Sprachbarriere',",
|
||||||
|
"blockType: 'uniqueValue',\n eyebrow: 'Wert',\n title: 'Steuererklärungen ohne Sprachbarriere',\n features: [{ text: 'Ohne Sprachbarrieren' }, { text: 'Individueller Ansatz' }],"
|
||||||
|
)
|
||||||
|
content = content.replace(
|
||||||
|
"blockType: 'process',\n title: 'Wie es funktioniert',",
|
||||||
|
"blockType: 'process',\n eyebrow: 'Prozess',\n title: 'Wie es funktioniert',\n subtitle: 'Ein einfacher und transparenter 3-Schritte-Abrechnungsprozess — ohne unnötige Formalitäten.',"
|
||||||
|
)
|
||||||
|
content = content.replace(
|
||||||
|
"blockType: 'affiliation',\n title: 'Vertrauen und Sicherheit',",
|
||||||
|
"blockType: 'affiliation',\n eyebrow: 'Vertrauen',\n title: 'Vertrauen und Sicherheit',"
|
||||||
|
)
|
||||||
|
content = content.replace(
|
||||||
|
"blockType: 'finalCta',\n title: 'Bereit für eine höhere Steuererstattung?',",
|
||||||
|
"blockType: 'finalCta',\n eyebrow: 'Kontakt',\n title: 'Bereit für eine höhere Steuererstattung?',"
|
||||||
|
)
|
||||||
|
content = content.replace(
|
||||||
|
"blockType: 'finalCta',\n title: 'Haben Sie Fragen zu Ihrer Steuererklärung?',",
|
||||||
|
"blockType: 'finalCta',\n eyebrow: 'Kontakt',\n title: 'Haben Sie Fragen zu Ihrer Steuererklärung?',"
|
||||||
|
)
|
||||||
|
content = content.replace(
|
||||||
|
"blockType: 'servicesDetailed',\n title: 'Was ich anbiete',",
|
||||||
|
"blockType: 'servicesDetailed',\n eyebrow: 'Leistungen',\n title: 'Was ich anbiete',"
|
||||||
|
)
|
||||||
|
content = content.replace(
|
||||||
|
"blockType: 'eligibility',\n title: 'Beratungsbefugnis gemäß § 4 Nr. 11 StBerG',",
|
||||||
|
"blockType: 'eligibility',\n eyebrow: 'Umfang der Beratungsbefugnis',\n title: 'Beratungsbefugnis gemäß § 4 Nr. 11 StBerG',"
|
||||||
|
)
|
||||||
|
|
||||||
|
# The missing ones for DE (Stats, ServicesList, FAQ)
|
||||||
|
content = re.sub(
|
||||||
|
r"blockType: 'stats',\n items: \[\n \{\n stat: '20\+',",
|
||||||
|
r"blockType: 'stats',\n eyebrow: 'Statistiken',\n items: [\n {\n stat: '20+',",
|
||||||
|
content, count=1
|
||||||
|
)
|
||||||
|
content = re.sub(
|
||||||
|
r"blockType: 'servicesList',\n title: 'Was ich anbiete',",
|
||||||
|
r"blockType: 'servicesList',\n eyebrow: 'Leistungen',\n title: 'Was ich anbiete',",
|
||||||
|
content, count=1
|
||||||
|
)
|
||||||
|
content = re.sub(
|
||||||
|
r"blockType: 'faqTeaser',\n title: 'Häufige Fragen',",
|
||||||
|
r"blockType: 'faqTeaser',\n eyebrow: 'FAQ',\n title: 'Häufige Fragen',",
|
||||||
|
content, count=1
|
||||||
|
)
|
||||||
|
|
||||||
|
with open("scripts/seed.ts", "w") as f:
|
||||||
|
f.write(content)
|
||||||
@@ -6,15 +6,17 @@ import type { Page } from '../../payload-types'
|
|||||||
|
|
||||||
type Props = Extract<NonNullable<Page['layout']>[0], { blockType: 'affiliation' }>
|
type Props = Extract<NonNullable<Page['layout']>[0], { blockType: 'affiliation' }>
|
||||||
|
|
||||||
export const AffiliationBlock: React.FC<Props> = ({ title, description, logo, linkText }) => {
|
export const AffiliationBlock: React.FC<Props> = ({ eyebrow, title, description, logo, linkText }) => {
|
||||||
return (
|
return (
|
||||||
<section className="py-20 md:py-32 bg-background border-t border-border overflow-hidden">
|
<section className="py-20 md:py-32 bg-background border-t border-border overflow-hidden">
|
||||||
<div className="container-premium">
|
<div className="container-premium">
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-16 lg:gap-24 items-center">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-16 lg:gap-24 items-center">
|
||||||
<div className="max-w-3xl">
|
<div className="max-w-3xl">
|
||||||
|
{eyebrow && (
|
||||||
<div className="font-mono text-sm md:text-base font-bold text-accent tracking-widest uppercase mb-4">
|
<div className="font-mono text-sm md:text-base font-bold text-accent tracking-widest uppercase mb-4">
|
||||||
// Zaufanie
|
// {eyebrow}
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
<h2 className="font-heading text-3xl sm:text-4xl md:text-5xl font-extrabold tracking-tight text-foreground mb-6 text-balance">
|
<h2 className="font-heading text-3xl sm:text-4xl md:text-5xl font-extrabold tracking-tight text-foreground mb-6 text-balance">
|
||||||
{title}
|
{title}
|
||||||
</h2>
|
</h2>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import type { Block } from 'payload'
|
|||||||
export const Affiliation: Block = {
|
export const Affiliation: Block = {
|
||||||
slug: 'affiliation',
|
slug: 'affiliation',
|
||||||
fields: [
|
fields: [
|
||||||
|
{ name: 'eyebrow', type: 'text', localized: true },
|
||||||
{ name: 'title', type: 'text', required: true, localized: true },
|
{ name: 'title', type: 'text', required: true, localized: true },
|
||||||
{ name: 'description', type: 'textarea', required: true, localized: true },
|
{ name: 'description', type: 'textarea', required: true, localized: true },
|
||||||
{ name: 'logo', type: 'upload', relationTo: 'media' },
|
{ name: 'logo', type: 'upload', relationTo: 'media' },
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React from 'react'
|
|||||||
import { CheckCircle2, XCircle, AlertTriangle, ShieldAlert } from 'lucide-react'
|
import { CheckCircle2, XCircle, AlertTriangle, ShieldAlert } from 'lucide-react'
|
||||||
|
|
||||||
export interface EligibilityProps {
|
export interface EligibilityProps {
|
||||||
|
eyebrow?: string | null
|
||||||
title: string
|
title: string
|
||||||
subtitle?: string | null
|
subtitle?: string | null
|
||||||
eligibleTitle: string
|
eligibleTitle: string
|
||||||
@@ -13,8 +14,7 @@ export interface EligibilityProps {
|
|||||||
legalNotice?: string | null
|
legalNotice?: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EligibilityBlock: React.FC<EligibilityProps> = ({
|
export const EligibilityBlock: React.FC<EligibilityProps> = ({ eyebrow, title,
|
||||||
title,
|
|
||||||
subtitle,
|
subtitle,
|
||||||
eligibleTitle,
|
eligibleTitle,
|
||||||
eligibleItems,
|
eligibleItems,
|
||||||
@@ -28,9 +28,11 @@ export const EligibilityBlock: React.FC<EligibilityProps> = ({
|
|||||||
<section className="py-12 md:py-20 bg-card/60 border-y border-border">
|
<section className="py-12 md:py-20 bg-card/60 border-y border-border">
|
||||||
<div className="container-premium">
|
<div className="container-premium">
|
||||||
<div className="max-w-3xl mb-12 text-center md:text-left">
|
<div className="max-w-3xl mb-12 text-center md:text-left">
|
||||||
<div className="font-mono text-sm md:text-base font-bold text-accent tracking-widest uppercase mb-4">
|
{eyebrow && (
|
||||||
// Zakres uprawnień doradczych
|
<div className="font-mono text-sm md:text-base font-bold text-accent tracking-widest uppercase mb-4">
|
||||||
</div>
|
// {eyebrow}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<h2 className="font-heading text-3xl sm:text-4xl md:text-5xl font-extrabold text-foreground tracking-tight">
|
<h2 className="font-heading text-3xl sm:text-4xl md:text-5xl font-extrabold text-foreground tracking-tight">
|
||||||
{title}
|
{title}
|
||||||
</h2>
|
</h2>
|
||||||
|
|||||||
@@ -6,14 +6,16 @@ import type { Page } from '../../payload-types'
|
|||||||
|
|
||||||
type Props = Extract<NonNullable<Page['layout']>[0], { blockType: 'faqTeaser' }>
|
type Props = Extract<NonNullable<Page['layout']>[0], { blockType: 'faqTeaser' }>
|
||||||
|
|
||||||
export const FAQTeaserBlock: React.FC<Props> = ({ title, questions, linkText }) => {
|
export const FAQTeaserBlock: React.FC<Props> = ({ eyebrow, title, questions, linkText }) => {
|
||||||
return (
|
return (
|
||||||
<section className="py-20 md:py-32 bg-card/40 border-t border-border">
|
<section className="py-20 md:py-32 bg-card/40 border-t border-border">
|
||||||
<div className="container-premium">
|
<div className="container-premium">
|
||||||
<div className="max-w-3xl mb-16 md:mb-24">
|
<div className="max-w-3xl mb-16 md:mb-24">
|
||||||
<div className="font-mono text-sm md:text-base font-bold text-accent tracking-widest uppercase mb-4">
|
{eyebrow && (
|
||||||
// FAQ
|
<div className="font-mono text-sm md:text-base font-bold text-accent tracking-widest uppercase mb-4">
|
||||||
</div>
|
// {eyebrow}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<h2 className="font-heading text-3xl sm:text-4xl md:text-5xl font-extrabold text-foreground tracking-tight text-balance">
|
<h2 className="font-heading text-3xl sm:text-4xl md:text-5xl font-extrabold text-foreground tracking-tight text-balance">
|
||||||
{title}
|
{title}
|
||||||
</h2>
|
</h2>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import type { Block } from 'payload'
|
|||||||
export const FAQTeaser: Block = {
|
export const FAQTeaser: Block = {
|
||||||
slug: 'faqTeaser',
|
slug: 'faqTeaser',
|
||||||
fields: [
|
fields: [
|
||||||
|
{ name: 'eyebrow', type: 'text', localized: true },
|
||||||
{ name: 'title', type: 'text', required: true, localized: true },
|
{ name: 'title', type: 'text', required: true, localized: true },
|
||||||
{
|
{
|
||||||
name: 'questions',
|
name: 'questions',
|
||||||
|
|||||||
@@ -5,14 +5,16 @@ import { MessageCircle } from 'lucide-react'
|
|||||||
|
|
||||||
type Props = Extract<NonNullable<Page['layout']>[0], { blockType: 'finalCta' }>
|
type Props = Extract<NonNullable<Page['layout']>[0], { blockType: 'finalCta' }>
|
||||||
|
|
||||||
export const FinalCTABlock: React.FC<Props> = ({ title, subtitle, buttonText }) => {
|
export const FinalCTABlock: React.FC<Props> = ({ eyebrow, title, subtitle, buttonText }) => {
|
||||||
return (
|
return (
|
||||||
<section className="py-20 md:py-32 bg-card border-y border-border text-left">
|
<section className="py-20 md:py-32 bg-card border-y border-border text-left">
|
||||||
<div className="container-premium">
|
<div className="container-premium">
|
||||||
<div className="max-w-3xl">
|
<div className="max-w-3xl">
|
||||||
<div className="font-mono text-sm md:text-base font-bold text-accent tracking-widest uppercase mb-4">
|
{eyebrow && (
|
||||||
// Kontakt
|
<div className="font-mono text-sm md:text-base font-bold text-accent tracking-widest uppercase mb-4">
|
||||||
</div>
|
// {eyebrow}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<h2 className="font-heading text-3xl sm:text-4xl md:text-5xl font-extrabold text-foreground tracking-tight mb-6 text-balance">
|
<h2 className="font-heading text-3xl sm:text-4xl md:text-5xl font-extrabold text-foreground tracking-tight mb-6 text-balance">
|
||||||
{title}
|
{title}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import type { Block } from 'payload'
|
|||||||
export const FinalCTA: Block = {
|
export const FinalCTA: Block = {
|
||||||
slug: 'finalCta',
|
slug: 'finalCta',
|
||||||
fields: [
|
fields: [
|
||||||
|
{ name: 'eyebrow', type: 'text', localized: true },
|
||||||
{ name: 'title', type: 'text', required: true, localized: true },
|
{ name: 'title', type: 'text', required: true, localized: true },
|
||||||
{ name: 'subtitle', type: 'text', localized: true },
|
{ name: 'subtitle', type: 'text', localized: true },
|
||||||
{ name: 'buttonText', type: 'text', required: true, localized: true },
|
{ name: 'buttonText', type: 'text', required: true, localized: true },
|
||||||
|
|||||||
@@ -6,20 +6,24 @@ import { ArrowRight } from 'lucide-react'
|
|||||||
|
|
||||||
type Props = Extract<NonNullable<Page['layout']>[0], { blockType: 'process' }>
|
type Props = Extract<NonNullable<Page['layout']>[0], { blockType: 'process' }>
|
||||||
|
|
||||||
export const ProcessBlock: React.FC<Props> = ({ title, steps, ctaText }) => {
|
export const ProcessBlock: React.FC<Props> = ({ eyebrow, subtitle, title, steps, ctaText }) => {
|
||||||
return (
|
return (
|
||||||
<section className="py-20 md:py-32 bg-background text-foreground overflow-hidden">
|
<section className="py-20 md:py-32 bg-background text-foreground overflow-hidden">
|
||||||
<div className="container-premium">
|
<div className="container-premium">
|
||||||
<div className="max-w-3xl mb-16 md:mb-24">
|
<div className="max-w-3xl mb-16 md:mb-24">
|
||||||
<div className="font-mono text-sm md:text-base font-bold text-accent tracking-widest uppercase mb-4">
|
{eyebrow && (
|
||||||
// Proces
|
<div className="font-mono text-sm md:text-base font-bold text-accent tracking-widest uppercase mb-4">
|
||||||
</div>
|
// {eyebrow}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<h2 className="font-heading text-3xl sm:text-4xl md:text-5xl font-extrabold tracking-tight text-foreground mb-6 text-balance">
|
<h2 className="font-heading text-3xl sm:text-4xl md:text-5xl font-extrabold tracking-tight text-foreground mb-6 text-balance">
|
||||||
{title}
|
{title}
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-foreground/75 text-lg md:text-xl font-light leading-relaxed">
|
{subtitle && (
|
||||||
Prosty i przejrzysty proces rozliczenia w 3 krokach — bez zbędnych formalności.
|
<p className="text-foreground/75 text-lg md:text-xl font-light leading-relaxed">
|
||||||
</p>
|
{subtitle}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-16 lg:gap-24 items-center">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-16 lg:gap-24 items-center">
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ import type { Block } from 'payload'
|
|||||||
export const Process: Block = {
|
export const Process: Block = {
|
||||||
slug: 'process',
|
slug: 'process',
|
||||||
fields: [
|
fields: [
|
||||||
|
{ name: 'eyebrow', type: 'text', localized: true },
|
||||||
{ name: 'title', type: 'text', required: true, localized: true },
|
{ name: 'title', type: 'text', required: true, localized: true },
|
||||||
|
{ name: 'subtitle', type: 'textarea', localized: true },
|
||||||
{
|
{
|
||||||
name: 'steps',
|
name: 'steps',
|
||||||
type: 'array',
|
type: 'array',
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ const iconMap: Record<string, LucideIcon> = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ServicesDetailedProps {
|
export interface ServicesDetailedProps {
|
||||||
|
eyebrow?: string | null
|
||||||
title: string
|
title: string
|
||||||
subtitle?: string | null
|
subtitle?: string | null
|
||||||
services?: Array<{
|
services?: Array<{
|
||||||
@@ -31,8 +32,7 @@ export interface ServicesDetailedProps {
|
|||||||
}> | null
|
}> | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ServicesDetailedBlock: React.FC<ServicesDetailedProps> = ({
|
export const ServicesDetailedBlock: React.FC<ServicesDetailedProps> = ({ eyebrow, title,
|
||||||
title,
|
|
||||||
subtitle,
|
subtitle,
|
||||||
services,
|
services,
|
||||||
}) => {
|
}) => {
|
||||||
@@ -40,9 +40,11 @@ export const ServicesDetailedBlock: React.FC<ServicesDetailedProps> = ({
|
|||||||
<section className="py-12 md:py-16">
|
<section className="py-12 md:py-16">
|
||||||
<div className="container-premium">
|
<div className="container-premium">
|
||||||
<div className="max-w-3xl mb-12">
|
<div className="max-w-3xl mb-12">
|
||||||
<div className="font-mono text-sm md:text-base font-bold text-accent tracking-widest uppercase mb-4">
|
{eyebrow && (
|
||||||
// Usługi
|
<div className="font-mono text-sm md:text-base font-bold text-accent tracking-widest uppercase mb-4">
|
||||||
</div>
|
// {eyebrow}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<h2 className="font-heading text-3xl sm:text-4xl md:text-5xl font-extrabold text-foreground tracking-tight text-balance">
|
<h2 className="font-heading text-3xl sm:text-4xl md:text-5xl font-extrabold text-foreground tracking-tight text-balance">
|
||||||
{title}
|
{title}
|
||||||
</h2>
|
</h2>
|
||||||
|
|||||||
@@ -6,14 +6,16 @@ import { ArrowRight } from 'lucide-react'
|
|||||||
|
|
||||||
type Props = Extract<NonNullable<Page['layout']>[0], { blockType: 'servicesList' }>
|
type Props = Extract<NonNullable<Page['layout']>[0], { blockType: 'servicesList' }>
|
||||||
|
|
||||||
export const ServicesListBlock: React.FC<Props> = ({ title, services }) => {
|
export const ServicesListBlock: React.FC<Props> = ({ eyebrow, title, services }) => {
|
||||||
return (
|
return (
|
||||||
<section className="py-20 md:py-32 bg-background text-foreground">
|
<section className="py-20 md:py-32 bg-background text-foreground">
|
||||||
<div className="container-premium">
|
<div className="container-premium">
|
||||||
<div className="max-w-3xl mb-16 md:mb-24">
|
<div className="max-w-3xl mb-16 md:mb-24">
|
||||||
<div className="font-mono text-sm md:text-base font-bold text-accent tracking-widest uppercase mb-4">
|
{eyebrow && (
|
||||||
// Usługi
|
<div className="font-mono text-sm md:text-base font-bold text-accent tracking-widest uppercase mb-4">
|
||||||
</div>
|
// {eyebrow}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<h2 className="font-heading text-3xl sm:text-4xl md:text-5xl font-extrabold text-foreground tracking-tight max-w-2xl text-balance">
|
<h2 className="font-heading text-3xl sm:text-4xl md:text-5xl font-extrabold text-foreground tracking-tight max-w-2xl text-balance">
|
||||||
{title}
|
{title}
|
||||||
</h2>
|
</h2>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import type { Block } from 'payload'
|
|||||||
export const ServicesList: Block = {
|
export const ServicesList: Block = {
|
||||||
slug: 'servicesList',
|
slug: 'servicesList',
|
||||||
fields: [
|
fields: [
|
||||||
|
{ name: 'eyebrow', type: 'text', localized: true },
|
||||||
{ name: 'title', type: 'text', required: true, localized: true },
|
{ name: 'title', type: 'text', required: true, localized: true },
|
||||||
{
|
{
|
||||||
name: 'services',
|
name: 'services',
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
export interface StatsProps {
|
export interface StatsProps {
|
||||||
|
eyebrow?: string | null
|
||||||
title?: string | null
|
title?: string | null
|
||||||
subtitle?: string | null
|
subtitle?: string | null
|
||||||
items?: Array<{
|
items?: Array<{
|
||||||
@@ -12,7 +13,7 @@ export interface StatsProps {
|
|||||||
}> | null
|
}> | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export const StatsBlock: React.FC<StatsProps> = ({ title, subtitle, items }) => {
|
export const StatsBlock: React.FC<StatsProps> = ({ eyebrow, title, subtitle, items }) => {
|
||||||
if (!items || items.length === 0) return null
|
if (!items || items.length === 0) return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -20,9 +21,11 @@ export const StatsBlock: React.FC<StatsProps> = ({ title, subtitle, items }) =>
|
|||||||
<div className="container-premium">
|
<div className="container-premium">
|
||||||
{(title || subtitle) && (
|
{(title || subtitle) && (
|
||||||
<div className="max-w-3xl mb-16 md:mb-24">
|
<div className="max-w-3xl mb-16 md:mb-24">
|
||||||
|
{eyebrow && (
|
||||||
<div className="font-mono text-sm md:text-base font-bold text-accent tracking-widest uppercase mb-4">
|
<div className="font-mono text-sm md:text-base font-bold text-accent tracking-widest uppercase mb-4">
|
||||||
// Statystyki
|
// {eyebrow}
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
{title && (
|
{title && (
|
||||||
<h2 className="font-heading text-3xl sm:text-4xl md:text-5xl font-extrabold text-foreground mb-6 tracking-tight text-balance">
|
<h2 className="font-heading text-3xl sm:text-4xl md:text-5xl font-extrabold text-foreground mb-6 tracking-tight text-balance">
|
||||||
{title}
|
{title}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { CheckCircle } from 'lucide-react'
|
|||||||
|
|
||||||
type Props = Extract<NonNullable<Page['layout']>[0], { blockType: 'uniqueValue' }>
|
type Props = Extract<NonNullable<Page['layout']>[0], { blockType: 'uniqueValue' }>
|
||||||
|
|
||||||
export const UniqueValueBlock: React.FC<Props> = ({ title, description }) => {
|
export const UniqueValueBlock: React.FC<Props> = ({ eyebrow, features, title, description }) => {
|
||||||
return (
|
return (
|
||||||
<section className="py-20 md:py-32 bg-card/60 border-y border-border">
|
<section className="py-20 md:py-32 bg-card/60 border-y border-border">
|
||||||
<div className="container-premium">
|
<div className="container-premium">
|
||||||
@@ -27,9 +27,11 @@ export const UniqueValueBlock: React.FC<Props> = ({ title, description }) => {
|
|||||||
|
|
||||||
{/* Kolumna tekstowa */}
|
{/* Kolumna tekstowa */}
|
||||||
<div className="lg:col-span-7 order-1 lg:order-2 flex flex-col items-start max-w-3xl">
|
<div className="lg:col-span-7 order-1 lg:order-2 flex flex-col items-start max-w-3xl">
|
||||||
|
{eyebrow && (
|
||||||
<div className="font-mono text-sm md:text-base font-bold text-accent tracking-widest uppercase mb-4">
|
<div className="font-mono text-sm md:text-base font-bold text-accent tracking-widest uppercase mb-4">
|
||||||
// Wartość
|
// {eyebrow}
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<h2 className="font-heading text-3xl sm:text-4xl md:text-5xl font-extrabold text-foreground tracking-tight mb-6 text-balance">
|
<h2 className="font-heading text-3xl sm:text-4xl md:text-5xl font-extrabold text-foreground tracking-tight mb-6 text-balance">
|
||||||
{title}
|
{title}
|
||||||
@@ -39,16 +41,16 @@ export const UniqueValueBlock: React.FC<Props> = ({ title, description }) => {
|
|||||||
{description}
|
{description}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-6 w-full pt-6 border-t border-border/60 text-base">
|
{features && features.length > 0 && (
|
||||||
<div className="flex items-center gap-2.5 text-foreground/80 font-medium">
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-6 w-full pt-6 border-t border-border/60 text-base">
|
||||||
<span className="w-2 h-2 rounded-full bg-accent" />
|
{features.map((feature: any, index: number) => (
|
||||||
<span>Bez barier językowych</span>
|
<div key={feature.id || index} className="flex items-center gap-2.5 text-foreground/80 font-medium">
|
||||||
|
<span className="w-2 h-2 rounded-full bg-accent" />
|
||||||
|
<span>{feature.text}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2.5 text-foreground/80 font-medium">
|
)}
|
||||||
<span className="w-2 h-2 rounded-full bg-accent" />
|
|
||||||
<span>Indywidualne podejście</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,7 +3,16 @@ import type { Block } from 'payload'
|
|||||||
export const UniqueValue: Block = {
|
export const UniqueValue: Block = {
|
||||||
slug: 'uniqueValue',
|
slug: 'uniqueValue',
|
||||||
fields: [
|
fields: [
|
||||||
|
{ name: 'eyebrow', type: 'text', localized: true },
|
||||||
{ name: 'title', type: 'text', required: true, localized: true },
|
{ name: 'title', type: 'text', required: true, localized: true },
|
||||||
{ name: 'description', type: 'textarea', required: true, localized: true },
|
{ name: 'description', type: 'textarea', required: true, localized: true },
|
||||||
|
{
|
||||||
|
name: 'features',
|
||||||
|
type: 'array',
|
||||||
|
localized: true,
|
||||||
|
fields: [
|
||||||
|
{ name: 'text', type: 'text', required: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -292,13 +292,21 @@ export interface Page {
|
|||||||
blockType: 'trustBar';
|
blockType: 'trustBar';
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
|
eyebrow?: string | null;
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
|
features?:
|
||||||
|
| {
|
||||||
|
text: string;
|
||||||
|
id?: string | null;
|
||||||
|
}[]
|
||||||
|
| null;
|
||||||
id?: string | null;
|
id?: string | null;
|
||||||
blockName?: string | null;
|
blockName?: string | null;
|
||||||
blockType: 'uniqueValue';
|
blockType: 'uniqueValue';
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
|
eyebrow?: string | null;
|
||||||
title: string;
|
title: string;
|
||||||
services?:
|
services?:
|
||||||
| {
|
| {
|
||||||
@@ -313,7 +321,9 @@ export interface Page {
|
|||||||
blockType: 'servicesList';
|
blockType: 'servicesList';
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
|
eyebrow?: string | null;
|
||||||
title: string;
|
title: string;
|
||||||
|
subtitle?: string | null;
|
||||||
steps?:
|
steps?:
|
||||||
| {
|
| {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -327,6 +337,7 @@ export interface Page {
|
|||||||
blockType: 'process';
|
blockType: 'process';
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
|
eyebrow?: string | null;
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
logo?: (number | null) | Media;
|
logo?: (number | null) | Media;
|
||||||
@@ -349,6 +360,7 @@ export interface Page {
|
|||||||
blockType: 'testimonials';
|
blockType: 'testimonials';
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
|
eyebrow?: string | null;
|
||||||
title: string;
|
title: string;
|
||||||
questions?:
|
questions?:
|
||||||
| {
|
| {
|
||||||
@@ -363,6 +375,7 @@ export interface Page {
|
|||||||
blockType: 'faqTeaser';
|
blockType: 'faqTeaser';
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
|
eyebrow?: string | null;
|
||||||
title: string;
|
title: string;
|
||||||
subtitle?: string | null;
|
subtitle?: string | null;
|
||||||
buttonText: string;
|
buttonText: string;
|
||||||
@@ -647,14 +660,22 @@ export interface PagesSelect<T extends boolean = true> {
|
|||||||
uniqueValue?:
|
uniqueValue?:
|
||||||
| T
|
| T
|
||||||
| {
|
| {
|
||||||
|
eyebrow?: T;
|
||||||
title?: T;
|
title?: T;
|
||||||
description?: T;
|
description?: T;
|
||||||
|
features?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
text?: T;
|
||||||
|
id?: T;
|
||||||
|
};
|
||||||
id?: T;
|
id?: T;
|
||||||
blockName?: T;
|
blockName?: T;
|
||||||
};
|
};
|
||||||
servicesList?:
|
servicesList?:
|
||||||
| T
|
| T
|
||||||
| {
|
| {
|
||||||
|
eyebrow?: T;
|
||||||
title?: T;
|
title?: T;
|
||||||
services?:
|
services?:
|
||||||
| T
|
| T
|
||||||
@@ -670,7 +691,9 @@ export interface PagesSelect<T extends boolean = true> {
|
|||||||
process?:
|
process?:
|
||||||
| T
|
| T
|
||||||
| {
|
| {
|
||||||
|
eyebrow?: T;
|
||||||
title?: T;
|
title?: T;
|
||||||
|
subtitle?: T;
|
||||||
steps?:
|
steps?:
|
||||||
| T
|
| T
|
||||||
| {
|
| {
|
||||||
@@ -685,6 +708,7 @@ export interface PagesSelect<T extends boolean = true> {
|
|||||||
affiliation?:
|
affiliation?:
|
||||||
| T
|
| T
|
||||||
| {
|
| {
|
||||||
|
eyebrow?: T;
|
||||||
title?: T;
|
title?: T;
|
||||||
description?: T;
|
description?: T;
|
||||||
logo?: T;
|
logo?: T;
|
||||||
@@ -709,6 +733,7 @@ export interface PagesSelect<T extends boolean = true> {
|
|||||||
faqTeaser?:
|
faqTeaser?:
|
||||||
| T
|
| T
|
||||||
| {
|
| {
|
||||||
|
eyebrow?: T;
|
||||||
title?: T;
|
title?: T;
|
||||||
questions?:
|
questions?:
|
||||||
| T
|
| T
|
||||||
@@ -724,6 +749,7 @@ export interface PagesSelect<T extends boolean = true> {
|
|||||||
finalCta?:
|
finalCta?:
|
||||||
| T
|
| T
|
||||||
| {
|
| {
|
||||||
|
eyebrow?: T;
|
||||||
title?: T;
|
title?: T;
|
||||||
subtitle?: T;
|
subtitle?: T;
|
||||||
buttonText?: T;
|
buttonText?: T;
|
||||||
|
|||||||
Reference in New Issue
Block a user