fix(ui): improve header navigation contrast on dark theme and add frontend not-found route
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
import { getPayload } from 'payload'
|
||||
import configPromise from '../src/payload.config'
|
||||
|
||||
async function run() {
|
||||
console.log('Connecting to Payload Local API...')
|
||||
const payload = await getPayload({ config: configPromise })
|
||||
|
||||
console.log('1. DELETING FAQ PAGE (ID 5)...')
|
||||
try {
|
||||
await payload.delete({
|
||||
collection: 'pages',
|
||||
id: 5,
|
||||
})
|
||||
console.log('FAQ page deleted.')
|
||||
} catch (err: any) {
|
||||
if (err.message?.includes('Not Found')) {
|
||||
console.log('FAQ page already deleted.')
|
||||
} else {
|
||||
console.log('Error deleting FAQ page (might be already deleted or foreign key constraint):', err.message)
|
||||
}
|
||||
}
|
||||
|
||||
console.log('2. UPDATING HEADER GLOBAL...')
|
||||
for (const locale of ['pl', 'de']) {
|
||||
const header = await payload.findGlobal({ slug: 'header', locale: locale as any })
|
||||
if (header.navigation) {
|
||||
const newNav = header.navigation.filter((item: any) => {
|
||||
const pageId = typeof item.page === 'object' && item.page !== null ? item.page.id : item.page
|
||||
return pageId !== 5 && pageId !== 1
|
||||
})
|
||||
await payload.updateGlobal({
|
||||
slug: 'header',
|
||||
locale: locale as any,
|
||||
data: { navigation: newNav },
|
||||
})
|
||||
console.log(`Updated Header navigation for ${locale}`)
|
||||
}
|
||||
}
|
||||
|
||||
console.log('3. UPDATING FOOTER GLOBAL...')
|
||||
for (const locale of ['pl', 'de']) {
|
||||
const footer = await payload.findGlobal({ slug: 'footer', locale: locale as any })
|
||||
if (footer.navigation) {
|
||||
const newNav = footer.navigation.filter((item: any) => {
|
||||
const pageId = typeof item.page === 'object' && item.page !== null ? item.page.id : item.page
|
||||
return pageId !== 5 && pageId !== 1
|
||||
})
|
||||
await payload.updateGlobal({
|
||||
slug: 'footer',
|
||||
locale: locale as any,
|
||||
data: { navigation: newNav },
|
||||
})
|
||||
console.log(`Updated Footer navigation for ${locale}`)
|
||||
}
|
||||
}
|
||||
|
||||
console.log('4. UPDATING FAQ TEASER ON HOME PAGE...')
|
||||
const homePl = await payload.findByID({ collection: 'pages', id: 1, locale: 'pl' })
|
||||
if (homePl && homePl.layout) {
|
||||
const layoutPl = homePl.layout.map((block: any) => {
|
||||
if (block.blockType === 'faqTeaser') {
|
||||
return {
|
||||
...block,
|
||||
title: 'Częste pytania',
|
||||
questions: [
|
||||
{
|
||||
question: 'Czy muszę przyjeżdżać osobiście do biura?',
|
||||
answer: 'Nie, wszystkie formalności możemy załatwić w 100% zdalnie. Dokumenty przesyłasz online przez bezpieczne kanały, a konsultacje prowadzimy telefonicznie lub przez WhatsApp.'
|
||||
},
|
||||
{
|
||||
question: 'Do kiedy muszę złożyć deklarację podatkową w Niemczech?',
|
||||
answer: 'Standardowy termin to 31 sierpnia kolejnego roku, jednak korzystając z usług doradcy podatkowego, zyskujesz dodatkowy czas – nawet do końca lutego lub maja następnego roku (w zależności od ustaleń na dany rok).'
|
||||
},
|
||||
{
|
||||
question: 'Jak długo czeka się na zwrot podatku z Finanzamt?',
|
||||
answer: 'Czas oczekiwania na decyzję i przelew zależy od urzędu skarbowego (Finanzamt) – najczęściej wynosi on od 3 do 8 tygodni po złożeniu kompletnej deklaracji.'
|
||||
},
|
||||
{
|
||||
question: 'Czy załatwiacie również zasiłek Kindergeld?',
|
||||
answer: 'Tak, pomagamy w formalnościach związanych z pozyskaniem zasiłku rodzinnego Kindergeld dla pracowników zatrudnionych na terenie Niemiec.'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
return block
|
||||
})
|
||||
|
||||
await payload.update({
|
||||
collection: 'pages',
|
||||
id: 1,
|
||||
locale: 'pl',
|
||||
data: { layout: layoutPl },
|
||||
})
|
||||
console.log('Updated Home Page FAQ (PL)')
|
||||
}
|
||||
|
||||
const homeDe = await payload.findByID({ collection: 'pages', id: 1, locale: 'de' })
|
||||
if (homeDe && homeDe.layout) {
|
||||
const layoutDe = homeDe.layout.map((block: any) => {
|
||||
if (block.blockType === 'faqTeaser') {
|
||||
return {
|
||||
...block,
|
||||
title: 'Häufig gestellte Fragen (FAQ)',
|
||||
questions: [
|
||||
{
|
||||
question: 'Muss ich persönlich ins Büro kommen?',
|
||||
answer: 'Nein, wir können alle Formalitäten zu 100 % online erledigen. Sie übermitteln Dokumente sicher online, und wir beraten Sie telefonisch oder per WhatsApp.'
|
||||
},
|
||||
{
|
||||
question: 'Bis wann muss ich die Steuererklärung in Deutschland einreichen?',
|
||||
answer: 'Die Standardfrist ist der 31. August des Folgejahres. Wenn Sie jedoch die Dienste eines Steuerberaters in Anspruch nehmen, verlängert sich diese Frist (oft bis Ende Februar oder Mai des übernächsten Jahres, je nach Vorgaben).'
|
||||
},
|
||||
{
|
||||
question: 'Wie lange dauert es, bis das Finanzamt die Erstattung überweist?',
|
||||
answer: 'Die Bearbeitungszeit hängt vom jeweiligen Finanzamt ab und beträgt in der Regel zwischen 3 und 8 Wochen nach Einreichung einer vollständigen Steuererklärung.'
|
||||
},
|
||||
{
|
||||
question: 'Unterstützen Sie auch beim Kindergeld?',
|
||||
answer: 'Ja, wir helfen Ihnen gerne bei allen Formalitäten rund um die Beantragung von Kindergeld für in Deutschland Beschäftigte.'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
return block
|
||||
})
|
||||
|
||||
await payload.update({
|
||||
collection: 'pages',
|
||||
id: 1,
|
||||
locale: 'de',
|
||||
data: { layout: layoutDe },
|
||||
})
|
||||
console.log('Updated Home Page FAQ (DE)')
|
||||
}
|
||||
|
||||
console.log('Setup completed successfully!')
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
try {
|
||||
await run()
|
||||
} catch (err) {
|
||||
console.error('ERROR during setup:', err)
|
||||
process.exit(1)
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import Link from 'next/link'
|
||||
import React from 'react'
|
||||
|
||||
export default function NotFound() {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center min-h-[60vh] text-center px-4">
|
||||
<h1 className="font-heading text-6xl font-bold text-foreground mb-4">404</h1>
|
||||
<h2 className="text-2xl font-semibold text-foreground/80 mb-6">
|
||||
Strona nie została znaleziona
|
||||
</h2>
|
||||
<p className="text-foreground/60 max-w-md mb-8">
|
||||
Przepraszamy, ale strona, której szukasz nie istnieje, została usunięta lub zmieniono jej adres.
|
||||
</p>
|
||||
<Link
|
||||
href="/"
|
||||
className="inline-flex items-center justify-center bg-accent hover:bg-accent-hover text-white font-medium px-6 py-3 rounded-[var(--radius-button)] transition-colors"
|
||||
>
|
||||
Wróć na stronę główną
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -28,11 +28,7 @@ export const EligibilityBlock: React.FC<EligibilityProps> = ({ eyebrow, title,
|
||||
<section className="py-12 md:py-20 bg-card/60 border-y border-border">
|
||||
<div className="container-premium">
|
||||
<div className="max-w-3xl mb-12">
|
||||
{eyebrow && (
|
||||
<div className="font-mono text-sm md:text-base font-bold text-accent tracking-widest uppercase mb-4">
|
||||
// {eyebrow}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<h2 className="font-heading text-3xl sm:text-4xl md:text-5xl font-extrabold text-foreground tracking-tight">
|
||||
{title}
|
||||
</h2>
|
||||
|
||||
@@ -1,41 +1,68 @@
|
||||
'use client'
|
||||
import React from 'react'
|
||||
|
||||
import Link from 'next/link'
|
||||
import React, { useState } from 'react'
|
||||
import { motion, AnimatePresence } from 'framer-motion'
|
||||
import { ChevronDown } from 'lucide-react'
|
||||
import type { Page } from '../../payload-types'
|
||||
|
||||
type Props = Extract<NonNullable<Page['layout']>[0], { blockType: 'faqTeaser' }>
|
||||
|
||||
export const FAQTeaserBlock: React.FC<Props> = ({ eyebrow, title, questions, linkText }) => {
|
||||
return (
|
||||
<section className="py-20 md:py-32 bg-card/40 border-t border-border">
|
||||
<div className="container-premium">
|
||||
<div className="max-w-3xl mb-16 md:mb-24">
|
||||
export const FAQTeaserBlock: React.FC<Props> = ({ title, questions }) => {
|
||||
const [openIndex, setOpenIndex] = useState<number | null>(0)
|
||||
|
||||
const toggleQuestion = (index: number) => {
|
||||
setOpenIndex(openIndex === index ? null : index)
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="py-20 md:py-32 bg-card/40 border-t border-border" id="faq">
|
||||
<div className="container-premium max-w-4xl">
|
||||
<div className="mb-12 md:mb-16 text-center">
|
||||
<h2 className="font-heading text-3xl sm:text-4xl md:text-5xl font-extrabold text-foreground tracking-tight text-balance">
|
||||
{title}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6 mb-12">
|
||||
{questions?.map((q: NonNullable<Props['questions']>[0], index: number) => (
|
||||
<div className="space-y-4">
|
||||
{questions?.map((q: NonNullable<Props['questions']>[0], index: number) => {
|
||||
const isOpen = openIndex === index
|
||||
|
||||
return (
|
||||
<div
|
||||
key={q.id || index}
|
||||
className="py-6 border-b border-border/50 first:border-t"
|
||||
className="bg-background border border-border overflow-hidden shadow-sm hover:shadow-md transition-shadow"
|
||||
>
|
||||
<h3 className="font-heading text-xl sm:text-2xl font-bold text-foreground mb-3">{q.question}</h3>
|
||||
<p className="text-foreground/75 leading-relaxed">{q.answer}</p>
|
||||
<button
|
||||
onClick={() => toggleQuestion(index)}
|
||||
className="w-full flex items-center justify-between p-6 text-left hover:bg-card/50 transition-colors focus:outline-none"
|
||||
aria-expanded={isOpen}
|
||||
>
|
||||
<h3 className="font-heading text-lg sm:text-xl font-bold text-foreground pr-8">
|
||||
{q.question}
|
||||
</h3>
|
||||
<div className="w-8 h-8 rounded-full bg-accent/10 flex items-center justify-center shrink-0">
|
||||
<ChevronDown
|
||||
className={`w-5 h-5 text-accent transition-transform duration-300 ${isOpen ? 'rotate-180' : ''}`}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{linkText && (
|
||||
<div className="mt-12">
|
||||
<Link href="/faq" className="inline-block text-accent font-semibold hover:text-accent-hover underline underline-offset-4">
|
||||
{linkText}
|
||||
</Link>
|
||||
</button>
|
||||
<AnimatePresence initial={false}>
|
||||
{isOpen && (
|
||||
<motion.div
|
||||
initial={{ height: 0, opacity: 0 }}
|
||||
animate={{ height: 'auto', opacity: 1 }}
|
||||
exit={{ height: 0, opacity: 0 }}
|
||||
transition={{ duration: 0.3, ease: 'easeInOut' }}
|
||||
>
|
||||
<div className="p-6 pt-0 text-foreground/75 leading-relaxed border-t border-border/10">
|
||||
{q.answer}
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
|
||||
@@ -3,7 +3,6 @@ import type { Block } from 'payload'
|
||||
export const FAQTeaser: Block = {
|
||||
slug: 'faqTeaser',
|
||||
fields: [
|
||||
{ name: 'eyebrow', type: 'text', localized: true },
|
||||
{ name: 'title', type: 'text', required: true, localized: true },
|
||||
{
|
||||
name: 'questions',
|
||||
@@ -15,6 +14,5 @@ export const FAQTeaser: Block = {
|
||||
{ name: 'answer', type: 'textarea', required: true },
|
||||
],
|
||||
},
|
||||
{ name: 'linkText', type: 'text', localized: true },
|
||||
],
|
||||
}
|
||||
|
||||
@@ -74,14 +74,14 @@ export const ServicesDetailedBlock: React.FC<Props> = ({
|
||||
isFirst
|
||||
? 'bg-foreground text-background border-0 shadow-md p-8 md:p-10'
|
||||
: 'bg-card text-foreground border border-border shadow-xs p-6 md:p-8'
|
||||
} rounded-none min-h-[240px] flex flex-col justify-between`}
|
||||
} rounded-none min-h-[240px] flex flex-col justify-between overflow-hidden`}
|
||||
>
|
||||
<div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="text-accent mb-6">
|
||||
<Icon size={isFirst ? 32 : 28} />
|
||||
</div>
|
||||
<h3
|
||||
className={`font-heading font-bold mb-3 leading-snug ${
|
||||
className={`font-heading font-bold mb-3 leading-snug break-words hyphens-auto ${
|
||||
isFirst
|
||||
? 'text-2xl sm:text-3xl text-background'
|
||||
: 'text-xl sm:text-2xl text-foreground'
|
||||
|
||||
@@ -44,7 +44,7 @@ export function Header({ navItems = [] }: { navItems?: any[] }) {
|
||||
<span className="font-heading font-bold text-lg leading-tight tracking-tight text-foreground">
|
||||
Finanz- & Lohnbuchhaltung
|
||||
</span>
|
||||
<span className="text-xs text-foreground/60 uppercase tracking-widest font-medium">
|
||||
<span className="text-xs text-foreground/75 uppercase tracking-widest font-medium">
|
||||
Dipl.-Ing. Janusz Kędzierski
|
||||
</span>
|
||||
</div>
|
||||
@@ -57,7 +57,7 @@ export function Header({ navItems = [] }: { navItems?: any[] }) {
|
||||
key={index}
|
||||
page={page}
|
||||
currentLocaleCode={currentLocale.code}
|
||||
className="text-sm font-medium text-foreground/80 hover:text-accent transition-colors"
|
||||
className="text-sm font-medium text-foreground/90 hover:text-accent transition-colors"
|
||||
/>
|
||||
))}
|
||||
</nav>
|
||||
|
||||
@@ -413,7 +413,6 @@ export interface Page {
|
||||
blockType: 'testimonialsGrid';
|
||||
}
|
||||
| {
|
||||
eyebrow?: string | null;
|
||||
title: string;
|
||||
questions?:
|
||||
| {
|
||||
@@ -422,7 +421,6 @@ export interface Page {
|
||||
id?: string | null;
|
||||
}[]
|
||||
| null;
|
||||
linkText?: string | null;
|
||||
id?: string | null;
|
||||
blockName?: string | null;
|
||||
blockType: 'faqTeaser';
|
||||
@@ -848,7 +846,6 @@ export interface PagesSelect<T extends boolean = true> {
|
||||
faqTeaser?:
|
||||
| T
|
||||
| {
|
||||
eyebrow?: T;
|
||||
title?: T;
|
||||
questions?:
|
||||
| T
|
||||
@@ -857,7 +854,6 @@ export interface PagesSelect<T extends boolean = true> {
|
||||
answer?: T;
|
||||
id?: T;
|
||||
};
|
||||
linkText?: T;
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user