fix: replace hardcoded content with CMS data

- Phone number: 10 locations now use CMS site-settings contact.phone
- ContactForm: service dropdown options from CMS services
- FAQ categories: display names derived from CMS services
- Footer: Top Fachbereiche column dynamic from CMS services
- SEO metadata: fachbereiche, faq, kontakt use generateMetadata()
- HomeCTA: converted to async server component, fetches settings
- Added phoneToHref() helper to payload-helpers.ts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
CCS Admin 2026-02-21 02:31:27 +00:00
parent 1aad09cc0f
commit 0e5c9808a8
13 changed files with 190 additions and 102 deletions

View file

@ -4,7 +4,8 @@ import { Container } from "@/components/ui/Container"
import { Button } from "@/components/ui/Button"
import { RichTextRenderer } from "@/components/ui/RichTextRenderer"
import { getLucideIcon } from "@/lib/icon-map"
import { getServices, getServiceBySlug } from "@/lib/api"
import { getServices, getServiceBySlug, getSiteSettings } from "@/lib/api"
import { phoneToHref } from "@/lib/payload-helpers"
import { Phone, Mail, ArrowRight, Check } from "lucide-react"
export async function generateStaticParams() {
@ -32,14 +33,15 @@ export default async function ServiceDetailPage({
params: Promise<{ slug: string }>
}) {
const { slug } = await params
const service = await getServiceBySlug(slug)
const [service, settings] = await Promise.all([
getServiceBySlug(slug),
getSiteSettings(),
])
if (!service) notFound()
const Icon = getLucideIcon(service.icon)
const shortTitle = service.title.replace(/^Zweitmeinung\s+/, "")
const categoryName = typeof service.category === "object" && service.category
? service.category.name
: null
const phone = settings?.contact?.phone || "0800 80 44 100"
return (
<>
@ -60,7 +62,7 @@ export default async function ServiceDetailPage({
{service.shortDescription}
</p>
<div className="flex flex-wrap justify-center gap-4">
<Button href="tel:+4980080441000" variant="gold" size="lg">
<Button href={phoneToHref(phone)} variant="gold" size="lg">
<Phone className="h-4 w-4" />
Jetzt beraten lassen
</Button>
@ -197,9 +199,9 @@ export default async function ServiceDetailPage({
Einschätzung im Bereich {shortTitle}.
</p>
<div className="flex flex-wrap justify-center gap-4">
<Button href="tel:+4980080441000" variant="gold" size="lg">
<Button href={phoneToHref(phone)} variant="gold" size="lg">
<Phone className="h-4 w-4" />
+49 800 80 44 100
{phone}
</Button>
<Button href="/kontakt" variant="secondary" size="lg">
<Mail className="h-4 w-4" />

View file

@ -3,12 +3,17 @@ import Link from "next/link"
import { ArrowRight, Phone } from "lucide-react"
import { Container } from "@/components/ui/Container"
import { Button } from "@/components/ui/Button"
import { getServices } from "@/lib/api"
import { getServices, getSiteSettings } from "@/lib/api"
import { getLucideIcon } from "@/lib/icon-map"
import { phoneToHref } from "@/lib/payload-helpers"
export const metadata: Metadata = {
export async function generateMetadata(): Promise<Metadata> {
const services = await getServices()
const names = services.map((s) => s.title.replace(/^Zweitmeinung\s+/, "")).join(", ")
return {
title: "Fachbereiche",
description: "Medizinische Zweitmeinung in 6 Fachbereichen: Intensivmedizin, Kardiologie, Onkologie, Nephrologie, Gallenblase und Schilddrüse.",
description: `Medizinische Zweitmeinung in ${services.length} Fachbereichen: ${names}.`,
}
}
const colorMap: Record<string, string> = {
@ -21,7 +26,11 @@ const colorMap: Record<string, string> = {
}
export default async function FachbereichePage() {
const services = await getServices()
const [services, settings] = await Promise.all([
getServices(),
getSiteSettings(),
])
const phone = settings?.contact?.phone || "0800 80 44 100"
return (
<>
@ -99,9 +108,9 @@ export default async function FachbereichePage() {
Rufen Sie uns an wir helfen Ihnen, den richtigen Fachbereich zu
finden und beraten Sie unverbindlich.
</p>
<Button href="tel:+4980080441000" variant="gold" size="lg">
<Button href={phoneToHref(phone)} variant="gold" size="lg">
<Phone className="h-4 w-4" />
0800 80 44 100 (kostenlos)
{phone} (kostenlos)
</Button>
</div>
</Container>

View file

@ -3,32 +3,39 @@ import { Container } from "@/components/ui/Container"
import { Button } from "@/components/ui/Button"
import { FAQClient } from "@/components/faq/FAQClient"
import { Phone, Mail, HelpCircle } from "lucide-react"
import { getFaqs } from "@/lib/api"
import { getFaqs, getServices, getSiteSettings } from "@/lib/api"
import { richTextToPlainText } from "@/components/ui/RichTextRenderer"
import { phoneToHref } from "@/lib/payload-helpers"
export const metadata: Metadata = {
export async function generateMetadata(): Promise<Metadata> {
const services = await getServices()
const names = services.map((s) => s.title.replace(/^Zweitmeinung\s+/, "")).join(", ")
return {
title: "FAQ - Häufige Fragen",
description:
"Antworten auf die wichtigsten Fragen zur medizinischen Zweitmeinung. Von Onkologie über Kardiologie bis Intensivmedizin.",
}
const categoryNames: Record<string, string> = {
allgemein: "Allgemeine Fragen",
intensivmedizin: "Intensivmedizin",
kardiologie: "Kardiologie",
onkologie: "Onkologie",
nephrologie: "Nephrologie",
gallenblase: "Gallenblase",
schilddruese: "Schilddrüse",
description: `Antworten auf die wichtigsten Fragen zur medizinischen Zweitmeinung. ${names}.`,
}
}
export default async function FAQPage() {
const faqs = await getFaqs()
const [faqs, services, settings] = await Promise.all([
getFaqs(),
getServices(),
getSiteSettings(),
])
const phone = settings?.contact?.phone || "0800 80 44 100"
// Build category names from services (slug suffix → title without "Zweitmeinung ")
const serviceCategoryNames: Record<string, string> = { allgemein: "Allgemeine Fragen" }
for (const s of services) {
const suffix = s.slug.replace(/^zweitmeinung-/, "")
serviceCategoryNames[suffix] = s.title.replace(/^Zweitmeinung\s+/, "")
}
// Derive categories from data
const categorySlugs = [...new Set(faqs.map((f) => f.category).filter((c): c is string => !!c))]
const categories = categorySlugs.map((slug: string) => ({
name: categoryNames[slug] || slug,
name: serviceCategoryNames[slug] || slug,
slug,
}))
@ -75,7 +82,7 @@ export default async function FAQPage() {
kostenlose Erstberatung.
</p>
<div className="flex flex-wrap justify-center gap-4">
<Button href="tel:+4980080441000" variant="gold" size="lg">
<Button href={phoneToHref(phone)} variant="gold" size="lg">
<Phone className="h-4 w-4" />
Jetzt anrufen
</Button>

View file

@ -2,15 +2,22 @@ import type { Metadata } from "next"
import { Container } from "@/components/ui/Container"
import { ContactForm } from "@/components/contact/ContactForm"
import { Phone, Mail, MapPin, Clock, Printer } from "lucide-react"
import { getSiteSettings } from "@/lib/api"
import { getSiteSettings, getServices } from "@/lib/api"
export const metadata: Metadata = {
export async function generateMetadata(): Promise<Metadata> {
const settings = await getSiteSettings()
const phone = settings?.contact?.phone || "0800 80 44 100"
return {
title: "Kontakt",
description: "Kontaktieren Sie uns für eine kostenlose Erstberatung. Telefon: 0800 80 44 100.",
description: `Kontaktieren Sie uns für eine kostenlose Erstberatung. Telefon: ${phone}.`,
}
}
export default async function KontaktPage() {
const settings = await getSiteSettings()
const [settings, services] = await Promise.all([
getSiteSettings(),
getServices(),
])
const phone = settings?.contact?.phone || "0800 80 44 100"
const email = settings?.contact?.email || "kontakt@zweitmeinu.ng"
@ -18,7 +25,12 @@ export default async function KontaktPage() {
const street = settings?.address?.street || "Hans-Böckler-Str. 19"
const zip = settings?.address?.zip || "46236"
const city = settings?.address?.city || "Bottrop"
const phoneHref = `tel:${phone.replace(/[\s/]/g, "")}`
const phoneHref = `tel:+49${phone.replace(/[\s/()-]/g, "").substring(1)}`
const serviceOptions = services.map((s) => ({
value: s.slug.replace(/^zweitmeinung-/, ""),
label: s.title.replace(/^Zweitmeinung\s+/, ""),
}))
return (
<>
@ -109,7 +121,7 @@ export default async function KontaktPage() {
<h2 className="text-xl font-bold text-navy mb-6">
Nachricht senden
</h2>
<ContactForm />
<ContactForm serviceOptions={serviceOptions} />
</div>
</div>
</div>

View file

@ -1,6 +1,7 @@
import type { Metadata } from "next"
import { TopBar, Header, Footer, EmergencyBanner } from "@/components/layout"
import { getNavigation, getSiteSettings, getSocialLinks, getServices } from "@/lib/api"
import { phoneToHref } from "@/lib/payload-helpers"
import "./globals.css"
export const metadata: Metadata = {
@ -32,12 +33,20 @@ export default async function RootLayout({
getServices(),
])
const phone = settings?.contact?.phone || "0800 80 44 100"
const email = settings?.contact?.email || "kontakt@zweitmeinu.ng"
const headerServices = services.map((s) => ({
title: s.title,
slug: s.slug,
icon: s.icon ?? null,
}))
const footerServices = services.map((s) => ({
title: s.title.replace(/^Zweitmeinung\s+/, ""),
slug: s.slug,
}))
return (
<html lang="de">
<body className="font-body antialiased">
@ -49,12 +58,12 @@ export default async function RootLayout({
</a>
<div className="flex min-h-screen flex-col">
<TopBar settings={settings} />
<Header mainMenu={navigation?.mainMenu ?? null} services={headerServices} />
<Header mainMenu={navigation?.mainMenu ?? null} services={headerServices} phone={phone} email={email} />
<main id="main-content" className="flex-1">
{children}
</main>
<EmergencyBanner settings={settings} />
<Footer socialLinks={socialLinks} settings={settings} />
<Footer socialLinks={socialLinks} settings={settings} services={footerServices} />
</div>
</body>
</html>

View file

@ -1,6 +1,8 @@
import type { Metadata } from "next"
import { Container } from "@/components/ui/Container"
import { Button } from "@/components/ui/Button"
import { getSiteSettings } from "@/lib/api"
import { phoneToHref } from "@/lib/payload-helpers"
import {
Phone, FileText, UserCheck, ClipboardCheck,
MessageSquare, HeartHandshake, Shield, Clock,
@ -28,7 +30,10 @@ const whyCards = [
{ title: "Persönlich", description: "Case Management und persönliche Betreuung von Anfang an.", icon: Users },
]
export default function SoFunktionierts() {
export default async function SoFunktionierts() {
const settings = await getSiteSettings()
const phone = settings?.contact?.phone || "0800 80 44 100"
return (
<>
{/* Hero */}
@ -104,9 +109,9 @@ export default function SoFunktionierts() {
fundierte, unabhängige Einschätzung.
</p>
<div className="flex flex-wrap justify-center gap-4">
<Button href="tel:+4980080441000" variant="gold" size="lg">
<Button href={phoneToHref(phone)} variant="gold" size="lg">
<Phone className="h-4 w-4" />
0800 80 44 100
{phone}
</Button>
<Button href="/kontakt" variant="secondary" size="lg">
Kontaktformular

View file

@ -1,6 +1,8 @@
import type { Metadata } from "next"
import { Container } from "@/components/ui/Container"
import { Button } from "@/components/ui/Button"
import { getSiteSettings } from "@/lib/api"
import { phoneToHref } from "@/lib/payload-helpers"
import {
Shield, Award, Users, Heart, Phone,
CheckCircle, Building, Globe,
@ -18,7 +20,10 @@ const qualityCards = [
{ title: "Netzwerk", description: "Über 1000 Expert:innen aus über 50 Fachbereichen deutschlandweit.", icon: Globe },
]
export default function UeberUnsPage() {
export default async function UeberUnsPage() {
const settings = await getSiteSettings()
const phone = settings?.contact?.phone || "0800 80 44 100"
return (
<>
{/* Hero */}
@ -99,7 +104,7 @@ export default function UeberUnsPage() {
</div>
<div className="flex items-center gap-2 text-sm">
<CheckCircle className="h-4 w-4 text-gold shrink-0" />
<span className="text-text-muted">Kostenlose Servicehotline: 0800 80 44 100</span>
<span className="text-text-muted">Kostenlose Servicehotline: {phone}</span>
</div>
</div>
</div>
@ -117,9 +122,9 @@ export default function UeberUnsPage() {
<p className="text-text-muted mb-8">
Wir beraten Sie gerne unverbindlich und kostenfrei.
</p>
<Button href="tel:+4980080441000" variant="gold" size="lg">
<Button href={phoneToHref(phone)} variant="gold" size="lg">
<Phone className="h-4 w-4" />
0800 80 44 100 (kostenlos)
{phone} (kostenlos)
</Button>
</div>
</Container>

View file

@ -5,7 +5,11 @@ import { Send, Loader2, CheckCircle } from "lucide-react"
const PAYLOAD_URL = process.env.NEXT_PUBLIC_PAYLOAD_URL || "https://cms.c2sgmbh.de"
export function ContactForm() {
interface ContactFormProps {
serviceOptions?: Array<{ value: string; label: string }>
}
export function ContactForm({ serviceOptions = [] }: ContactFormProps) {
const [form, setForm] = useState({
name: "",
email: "",
@ -52,6 +56,17 @@ export function ContactForm() {
}
}
const options = serviceOptions.length > 0
? serviceOptions
: [
{ value: "intensivmedizin", label: "Intensivmedizin" },
{ value: "kardiologie", label: "Kardiologie" },
{ value: "onkologie", label: "Onkologie" },
{ value: "nephrologie", label: "Nephrologie" },
{ value: "gallenblase", label: "Gallenblase" },
{ value: "schilddruese", label: "Schilddrüse" },
]
if (status === "success") {
return (
<div className="text-center py-12">
@ -129,12 +144,9 @@ export function ContactForm() {
className="w-full px-4 py-2.5 rounded-lg border border-border bg-bg focus:outline-none focus:ring-2 focus:ring-primary/30 focus:border-primary text-sm"
>
<option value="">Bitte wählen...</option>
<option value="intensivmedizin">Intensivmedizin</option>
<option value="kardiologie">Kardiologie</option>
<option value="onkologie">Onkologie</option>
<option value="nephrologie">Nephrologie</option>
<option value="gallenblase">Gallenblase</option>
<option value="schilddruese">Schilddrüse</option>
{options.map((opt) => (
<option key={opt.value} value={opt.value}>{opt.label}</option>
))}
<option value="sonstiges">Sonstiges</option>
</select>
</div>

View file

@ -1,8 +1,13 @@
import { Phone, Mail } from "lucide-react"
import { Container } from "@/components/ui/Container"
import { Button } from "@/components/ui/Button"
import { getSiteSettings } from "@/lib/api"
import { phoneToHref } from "@/lib/payload-helpers"
export async function HomeCTA() {
const settings = await getSiteSettings()
const phone = settings?.contact?.phone || "0800 80 44 100"
export function HomeCTA() {
return (
<section className="py-20 bg-white">
<Container size="md">
@ -15,7 +20,7 @@ export function HomeCTA() {
Sicherheit, die Sie verdienen.
</h2>
<div className="flex flex-wrap justify-center gap-4 mt-8">
<Button href="tel:+4980080441000" variant="gold" size="lg">
<Button href={phoneToHref(phone)} variant="gold" size="lg">
<Phone className="h-4 w-4" />
Jetzt kostenlos beraten lassen
</Button>

View file

@ -6,20 +6,24 @@ import { socialLinksToMap } from "@/lib/payload-helpers"
interface FooterProps {
socialLinks?: SocialLink[]
settings?: SiteSetting | null
services?: Array<{ title: string; slug: string }>
}
const footerColumns = [
{
title: "Top Fachbereiche",
links: [
export function Footer({ socialLinks, settings, services = [] }: FooterProps) {
const socialMap = socialLinksToMap(socialLinks)
const copyright = settings?.footer?.copyrightText ||
`\u00A9 ${new Date().getFullYear()} complex care solutions GmbH. Alle Rechte vorbehalten.`
const fachbereicheLinks = services.length > 0
? services.map((s) => ({ label: s.title, href: `/fachbereiche/${s.slug}` }))
: [
{ label: "Kardiologie", href: "/fachbereiche/zweitmeinung-kardiologie" },
{ label: "Onkologie", href: "/fachbereiche/zweitmeinung-onkologie" },
{ label: "Intensivmedizin", href: "/fachbereiche/zweitmeinung-intensivmedizin" },
{ label: "Nephrologie", href: "/fachbereiche/zweitmeinung-nephrologie" },
{ label: "Gallenblase", href: "/fachbereiche/zweitmeinung-gallenblase" },
{ label: "Schilddrüse", href: "/fachbereiche/zweitmeinung-schilddruese" },
],
},
]
const footerColumns = [
{ title: "Top Fachbereiche", links: fachbereicheLinks },
{
title: "Services",
links: [
@ -44,12 +48,7 @@ const footerColumns = [
{ label: "Datenschutz", href: "/datenschutz" },
],
},
]
export function Footer({ socialLinks, settings }: FooterProps) {
const socialMap = socialLinksToMap(socialLinks)
const copyright = settings?.footer?.copyrightText ||
`\u00A9 ${new Date().getFullYear()} complex care solutions GmbH. Alle Rechte vorbehalten.`
]
return (
<footer className="bg-navy text-white">

View file

@ -9,6 +9,8 @@ import type { Navigation } from "@c2s/payload-contracts/types"
interface HeaderProps {
mainMenu: Navigation["mainMenu"] | null
services?: Array<{ title: string; slug: string; icon: string | null }>
phone?: string
email?: string
}
const emojiMap: Record<string, string> = {
@ -28,7 +30,7 @@ const navLinks = [
{ label: "Kontakt", href: "/kontakt" },
]
export function Header({ mainMenu, services = [] }: HeaderProps) {
export function Header({ mainMenu, services = [], phone, email }: HeaderProps) {
const [mobileOpen, setMobileOpen] = useState(false)
const [megaOpen, setMegaOpen] = useState(false)
const [scrolled, setScrolled] = useState(false)
@ -45,6 +47,10 @@ export function Header({ mainMenu, services = [] }: HeaderProps) {
icon: emojiMap[s.icon || ""] || "📋",
}))
const phoneHref = phone
? `tel:+49${phone.replace(/[\s/()-]/g, "").substring(1)}`
: "tel:+4980080441000"
return (
<>
<header
@ -128,7 +134,7 @@ export function Header({ mainMenu, services = [] }: HeaderProps) {
{/* Emergency CTA + Mobile Toggle */}
<div className="flex items-center gap-3">
<a
href="tel:+4980080441000"
href={phoneHref}
className="hidden sm:flex items-center gap-2 bg-gold hover:bg-gold-hover text-navy-dark font-bold text-sm px-4 py-2 rounded-lg transition-colors animate-pulse-gold"
>
<Phone className="h-4 w-4" />
@ -151,6 +157,8 @@ export function Header({ mainMenu, services = [] }: HeaderProps) {
onClose={() => setMobileOpen(false)}
services={megaServices}
navLinks={navLinks}
phone={phone}
email={email}
/>
</>
)

View file

@ -9,9 +9,11 @@ interface MobileMenuProps {
onClose: () => void
services: Array<{ name: string; slug: string; icon: string }>
navLinks: Array<{ label: string; href: string }>
phone?: string
email?: string
}
export function MobileMenu({ open, onClose, services, navLinks }: MobileMenuProps) {
export function MobileMenu({ open, onClose, services, navLinks, phone, email }: MobileMenuProps) {
useEffect(() => {
if (open) document.body.style.overflow = "hidden"
else document.body.style.overflow = ""
@ -20,6 +22,11 @@ export function MobileMenu({ open, onClose, services, navLinks }: MobileMenuProp
if (!open) return null
const phoneHref = phone
? `tel:+49${phone.replace(/[\s/()-]/g, "").substring(1)}`
: "tel:+4980080441000"
const emailAddr = email || "kontakt@zweitmeinu.ng"
return (
<div className="fixed inset-0 z-[100]">
{/* Backdrop */}
@ -77,14 +84,14 @@ export function MobileMenu({ open, onClose, services, navLinks }: MobileMenuProp
{/* Emergency CTA */}
<div className="p-4 border-t">
<a
href="tel:+4980080441000"
href={phoneHref}
className="flex items-center justify-center gap-2 w-full bg-gold hover:bg-gold-hover text-navy-dark font-bold py-3 rounded-xl transition-colors"
>
<Phone className="h-5 w-5" />
Notfall-Zweitmeinung
</a>
<a
href="mailto:kontakt@zweitmeinu.ng"
href={`mailto:${emailAddr}`}
className="flex items-center justify-center gap-2 w-full mt-3 border border-navy text-navy font-medium py-3 rounded-xl hover:bg-navy hover:text-white transition-colors"
>
<Mail className="h-5 w-5" />

View file

@ -48,3 +48,11 @@ export function socialLinksToMap(
}
return map
}
export function phoneToHref(phone: string | null | undefined): string {
if (!phone) return "tel:+4980080441000"
const digits = phone.replace(/[\s/()-]/g, "")
if (digits.startsWith("+")) return `tel:${digits}`
if (digits.startsWith("0")) return `tel:+49${digits.substring(1)}`
return `tel:${digits}`
}