mirror of
https://github.com/complexcaresolutions/frontend.zweitmeinu.ng.git
synced 2026-03-17 15:03:48 +00:00
feat: convert 5 static pages to CMS-driven blocks
Replace hardcoded motivation, so-funktionierts, ueber-uns, impressum, and datenschutz pages with CMS-driven content via BlockRenderer. Add block components: HeroBlock, TextBlock, CardGridBlock, CTABlock, ProcessStepsBlock, QuoteBlock, HtmlEmbedBlock. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
6371a2eee6
commit
728de20157
14 changed files with 410 additions and 504 deletions
|
|
@ -1,27 +1,16 @@
|
|||
import type { Metadata } from "next"
|
||||
import { Container } from "@/components/ui/Container"
|
||||
import { getPage } from "@/lib/api"
|
||||
import { BlockRenderer } from "@/components/blocks/BlockRenderer"
|
||||
import { notFound } from "next/navigation"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Datenschutzerklärung",
|
||||
description: "DSGVO-konforme Datenschutzerklärung von zweitmeinu.ng.",
|
||||
}
|
||||
|
||||
export default function DatenschutzPage() {
|
||||
return (
|
||||
<section className="py-20 bg-white">
|
||||
<Container size="md">
|
||||
<h1 className="text-3xl font-bold text-navy mb-8">
|
||||
Datenschutzerklärung
|
||||
</h1>
|
||||
<iframe
|
||||
src="https://app.alfright.eu/ext/dps/alfright_schutzteam/9f315103c43245bcb0806dd56c2be757?lang=de-de&headercolor=%23131F64&headerfont=Arial&headersize=21px&subheadersize=18px&fontcolor=%23333333&textfont=Arial&textsize=14px&background=%23ffffff&linkcolor=%23337ab7"
|
||||
title="Datenschutzerklärung"
|
||||
width="100%"
|
||||
height={5000}
|
||||
style={{ border: 0 }}
|
||||
loading="lazy"
|
||||
/>
|
||||
</Container>
|
||||
</section>
|
||||
)
|
||||
export default async function DatenschutzPage() {
|
||||
const page = await getPage("datenschutz")
|
||||
if (!page) notFound()
|
||||
|
||||
return <BlockRenderer blocks={(page.layout as any[]) || []} />
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,96 +1,16 @@
|
|||
import type { Metadata } from "next"
|
||||
import { Container } from "@/components/ui/Container"
|
||||
import { getPage } from "@/lib/api"
|
||||
import { BlockRenderer } from "@/components/blocks/BlockRenderer"
|
||||
import { notFound } from "next/navigation"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Impressum",
|
||||
description: "Impressum der complex care solutions GmbH.",
|
||||
}
|
||||
|
||||
export default function ImpressumPage() {
|
||||
return (
|
||||
<section className="py-20 bg-white">
|
||||
<Container size="sm">
|
||||
<article className="prose prose-gray max-w-none">
|
||||
<h1 className="text-3xl font-bold text-navy mb-8">Impressum</h1>
|
||||
export default async function ImpressumPage() {
|
||||
const page = await getPage("impressum")
|
||||
if (!page) notFound()
|
||||
|
||||
<p>
|
||||
<strong>complex care solutions GmbH</strong>
|
||||
<br />
|
||||
Hans-Böckler-Str. 19
|
||||
<br />
|
||||
46236 Bottrop
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Handelsregister: HRB 15753
|
||||
<br />
|
||||
Registergericht: Gelsenkirchen
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<strong>Vertreten durch:</strong>
|
||||
<br />
|
||||
Martin Porwoll
|
||||
</p>
|
||||
|
||||
<h2 className="text-xl font-bold text-navy mt-8 mb-4">Kontakt</h2>
|
||||
<p>
|
||||
Telefon: 0800 80 44 100
|
||||
<br />
|
||||
Telefax: 0800 80 44 190
|
||||
<br />
|
||||
E-Mail: kontakt@complexcaresolutions.de
|
||||
</p>
|
||||
|
||||
<h2 className="text-xl font-bold text-navy mt-8 mb-4">
|
||||
Umsatzsteuer-ID
|
||||
</h2>
|
||||
<p>
|
||||
Umsatzsteuer-Identifikationsnummer gemäß § 27 a
|
||||
Umsatzsteuergesetz:
|
||||
<br />
|
||||
DE334815479
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<strong>Redaktionell verantwortlich:</strong>
|
||||
<br />
|
||||
Martin Porwoll
|
||||
<br />
|
||||
Hans-Böckler-Str. 19
|
||||
<br />
|
||||
46236 Bottrop
|
||||
</p>
|
||||
|
||||
<h2 className="text-xl font-bold text-navy mt-8 mb-4">
|
||||
EU-Streitschlichtung
|
||||
</h2>
|
||||
<p>
|
||||
Die Europäische Kommission stellt eine Plattform zur
|
||||
Online-Streitbeilegung (OS) bereit:{" "}
|
||||
<a
|
||||
href="https://ec.europa.eu/consumers/odr/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-primary hover:underline"
|
||||
>
|
||||
https://ec.europa.eu/consumers/odr/
|
||||
</a>
|
||||
.
|
||||
<br />
|
||||
Unsere E-Mail-Adresse finden Sie oben im Impressum.
|
||||
</p>
|
||||
|
||||
<h2 className="text-xl font-bold text-navy mt-8 mb-4">
|
||||
Verbraucherstreitbeilegung
|
||||
</h2>
|
||||
<p>
|
||||
Wir sind nicht bereit oder verpflichtet, an
|
||||
Streitbeilegungsverfahren vor einer
|
||||
Verbraucherschlichtungsstelle teilzunehmen.
|
||||
</p>
|
||||
</article>
|
||||
</Container>
|
||||
</section>
|
||||
)
|
||||
return <BlockRenderer blocks={(page.layout as any[]) || []} />
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,165 +1,16 @@
|
|||
import type { Metadata } from "next"
|
||||
import { Container } from "@/components/ui/Container"
|
||||
import { Heart, Shield, Eye, Users, Lightbulb, Scale } from "lucide-react"
|
||||
import { getPage } from "@/lib/api"
|
||||
import { BlockRenderer } from "@/components/blocks/BlockRenderer"
|
||||
import { notFound } from "next/navigation"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Motivation & Geschichte",
|
||||
description: "Erfahren Sie, warum zweitmeinu.ng gegründet wurde und welche Werte uns antreiben.",
|
||||
}
|
||||
|
||||
const values = [
|
||||
{ title: "Patientenwohl", description: "Der Mensch steht im Mittelpunkt jeder Entscheidung.", icon: Heart },
|
||||
{ title: "Unabhängigkeit", description: "Frei von wirtschaftlichen Interessen und institutionellen Bindungen.", icon: Shield },
|
||||
{ title: "Transparenz", description: "Offene Kommunikation und nachvollziehbare Prozesse.", icon: Eye },
|
||||
{ title: "Qualität", description: "Höchste Standards in medizinischer Expertise und Beratung.", icon: Lightbulb },
|
||||
{ title: "Empathie", description: "Verständnis und Mitgefühl in jeder Situation.", icon: Users },
|
||||
{ title: "Gerechtigkeit", description: "Gleicher Zugang zu medizinischer Expertise für alle.", icon: Scale },
|
||||
]
|
||||
export default async function MotivationPage() {
|
||||
const page = await getPage("motivation")
|
||||
if (!page) notFound()
|
||||
|
||||
export default function MotivationPage() {
|
||||
return (
|
||||
<>
|
||||
{/* Hero */}
|
||||
<section className="bg-gradient-to-br from-navy via-navy-dark to-[#001a2e] py-20">
|
||||
<Container size="md">
|
||||
<div className="text-center">
|
||||
<h1 className="text-4xl sm:text-5xl font-bold text-white mb-4">
|
||||
Patientenwohl im Mittelpunkt
|
||||
</h1>
|
||||
<p className="text-white/60 text-lg">
|
||||
Wir sind Streiter für das Patientenwohl
|
||||
</p>
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
|
||||
{/* Story sections */}
|
||||
<section className="py-20 bg-white">
|
||||
<Container size="md">
|
||||
<div className="space-y-16">
|
||||
{/* Section 1 */}
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold text-navy mb-4">Unser Fokus</h2>
|
||||
<p className="text-text-muted leading-relaxed mb-4">
|
||||
Seit der Gründung konzentrieren wir uns darauf,
|
||||
Versorgungsangebote zu optimieren und dabei die Bedürfnisse der
|
||||
Patienten in den Mittelpunkt zu stellen.
|
||||
</p>
|
||||
<ul className="space-y-2 text-text-muted">
|
||||
<li className="flex gap-2">
|
||||
<span className="text-primary font-bold">›</span>
|
||||
Wir verfolgen einen patientenzentrierten Ansatz.
|
||||
</li>
|
||||
<li className="flex gap-2">
|
||||
<span className="text-primary font-bold">›</span>
|
||||
Wir legen besonderen Wert auf Transparenz, Unabhängigkeit und Qualitätssicherung.
|
||||
</li>
|
||||
<li className="flex gap-2">
|
||||
<span className="text-primary font-bold">›</span>
|
||||
Mit unserem nationalen und internationalen Expertennetzwerk entwickeln wir innovative Lösungen.
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Section 2 */}
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold text-navy mb-4">
|
||||
Motivation und Geschichte
|
||||
</h2>
|
||||
<p className="text-text-muted leading-relaxed mb-6">
|
||||
Complex care solutions wurde von Martin Porwoll, dem
|
||||
Whistleblower des Bottroper Zytoskandals, gegründet.
|
||||
</p>
|
||||
<blockquote className="border-l-4 border-gold pl-6 py-2 bg-gold/5 rounded-r-lg">
|
||||
<p className="text-navy italic">
|
||||
“Aus seinen Erfahrungen und der Erkenntnis um die Bedeutung
|
||||
von Transparenz und Patientenwohl entstand die Idee, ein
|
||||
unabhängiges Unternehmen zu etablieren.”
|
||||
</p>
|
||||
</blockquote>
|
||||
</div>
|
||||
|
||||
{/* Section 3 */}
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold text-navy mb-4">
|
||||
Der Zytoskandal Bottrop und Martin Porwoll
|
||||
</h2>
|
||||
<p className="text-text-muted leading-relaxed mb-4">
|
||||
Im Jahr 2016 deckte Martin Porwoll als Whistleblower den
|
||||
sogenannten Zytoskandal in Bottrop auf, bei dem ein Apotheker
|
||||
über Jahre hinweg Krebsmedikamente für tausende Patienten
|
||||
gestreckt hatte.
|
||||
</p>
|
||||
<blockquote className="border-l-4 border-primary pl-6 py-2">
|
||||
<p className="text-navy italic">
|
||||
“Der Bottroper Zytoskandal, den ich im Jahr 2016 als
|
||||
Whistleblower aufgedeckt habe, hat mich zutiefst
|
||||
erschüttert.”
|
||||
</p>
|
||||
<cite className="text-sm text-text-muted mt-2 block not-italic">
|
||||
— Martin Porwoll, Gründer & Geschäftsführer
|
||||
</cite>
|
||||
</blockquote>
|
||||
</div>
|
||||
|
||||
{/* Section 4 */}
|
||||
<div>
|
||||
<p className="text-text-muted leading-relaxed">
|
||||
Die Erfahrungen mit dem Zytoskandal haben Martin Porwoll zu
|
||||
einem engagierten Kämpfer für das Patientenwohl und gegen
|
||||
Missstände im Gesundheitswesen gemacht. So gründete er Complex
|
||||
care solutions mit dem Ziel, Patienten in komplexen
|
||||
Versorgungssituationen bestmöglich zu unterstützen.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
|
||||
{/* Core Values */}
|
||||
<section className="py-20 bg-bg">
|
||||
<Container>
|
||||
<div className="text-center mb-12">
|
||||
<h2 className="text-2xl sm:text-3xl font-bold text-navy mb-3">
|
||||
Unsere Grundwerte
|
||||
</h2>
|
||||
<p className="text-text-muted">
|
||||
Diese Prinzipien leiten uns bei allem, was wir für eine bessere
|
||||
Patientenversorgung tun.
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{values.map((v) => (
|
||||
<div key={v.title} className="bg-white rounded-xl p-6 border border-border">
|
||||
<v.icon className="h-8 w-8 text-primary mb-4" />
|
||||
<h3 className="font-bold text-navy mb-2">{v.title}</h3>
|
||||
<p className="text-text-muted text-sm">{v.description}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
|
||||
{/* Mission Statement */}
|
||||
<section className="py-20 bg-navy">
|
||||
<Container size="sm">
|
||||
<div className="text-center">
|
||||
<Heart className="h-10 w-10 text-gold mx-auto mb-6" />
|
||||
<h2 className="text-2xl font-bold text-white mb-6">
|
||||
Unsere Mission
|
||||
</h2>
|
||||
<blockquote className="text-white/80 text-lg leading-relaxed italic">
|
||||
“Wir setzen uns dafür ein, dass jeder Patient die bestmögliche
|
||||
Versorgung erhält. Durch innovative Technologie, Transparenz und
|
||||
unabhängige Expertise schaffen wir Vertrauen und verbessern die
|
||||
Gesundheitsversorgung.”
|
||||
</blockquote>
|
||||
<cite className="text-white/50 text-sm mt-4 block not-italic">
|
||||
— Das Team von zweitmeinu.ng
|
||||
</cite>
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
</>
|
||||
)
|
||||
return <BlockRenderer blocks={(page.layout as any[]) || []} />
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,126 +1,16 @@
|
|||
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,
|
||||
BadgeCheck, Users, ArrowRight,
|
||||
} from "lucide-react"
|
||||
import { getPage } from "@/lib/api"
|
||||
import { BlockRenderer } from "@/components/blocks/BlockRenderer"
|
||||
import { notFound } from "next/navigation"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "So funktioniert's",
|
||||
title: "So funktioniert\u0027s",
|
||||
description: "Ihr Weg zur medizinischen Zweitmeinung in 6 einfachen Schritten. Transparent, sicher und patientenorientiert.",
|
||||
}
|
||||
|
||||
const steps = [
|
||||
{ num: "01", title: "Kontaktaufnahme", description: "Sie rufen uns an oder schreiben uns. Unser Team nimmt Ihre Anfrage auf und klärt erste Fragen.", icon: Phone },
|
||||
{ num: "02", title: "Unterlagen sammeln", description: "Unsere Case Manager:innen unterstützen Sie bei der Zusammenstellung aller relevanten medizinischen Unterlagen.", icon: FileText },
|
||||
{ num: "03", title: "Experten-Zuordnung", description: "Wir ordnen Ihren Fall einem passenden Facharzt bzw. einer Fachärztin aus unserem Netzwerk zu.", icon: UserCheck },
|
||||
{ num: "04", title: "Medizinische Prüfung", description: "Der/die Fachärzt:in prüft Ihre Unterlagen und erstellt eine unabhängige medizinische Einschätzung.", icon: ClipboardCheck },
|
||||
{ num: "05", title: "Schriftliches Gutachten", description: "Sie erhalten ein verständliches Zweitmeinungsgutachten mit klarer Empfehlung.", icon: MessageSquare },
|
||||
{ num: "06", title: "Nachbetreuung", description: "Bei Bedarf besprechen wir das Ergebnis persönlich und unterstützen bei der weiteren Umsetzung.", icon: HeartHandshake },
|
||||
]
|
||||
export default async function SoFunktioniertsPage() {
|
||||
const page = await getPage("so-funktionierts")
|
||||
if (!page) notFound()
|
||||
|
||||
const whyCards = [
|
||||
{ title: "Unabhängig", description: "Unsere Expert:innen haben keine wirtschaftlichen Eigeninteressen.", icon: Shield },
|
||||
{ title: "Schnell", description: "In der Regel erhalten Sie Ihr Gutachten innerhalb weniger Tage.", icon: Clock },
|
||||
{ title: "Qualifiziert", description: "Nur erfahrene Fachärzt:innen mit nachgewiesener Expertise.", icon: BadgeCheck },
|
||||
{ title: "Persönlich", description: "Case Management und persönliche Betreuung von Anfang an.", icon: Users },
|
||||
]
|
||||
|
||||
export default async function SoFunktionierts() {
|
||||
const settings = await getSiteSettings()
|
||||
const phone = settings?.contact?.phone || "0800 80 44 100"
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Hero */}
|
||||
<section className="bg-gradient-to-br from-navy via-navy-dark to-[#001a2e] py-20">
|
||||
<Container size="md">
|
||||
<div className="text-center">
|
||||
<h1 className="text-4xl sm:text-5xl font-bold text-white mb-4">
|
||||
So funktioniert's
|
||||
</h1>
|
||||
<p className="text-white/60 text-lg max-w-2xl mx-auto">
|
||||
Ihr Weg zur medizinischen Zweitmeinung in 6 einfachen Schritten.
|
||||
Transparent, sicher und patientenorientiert.
|
||||
</p>
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
|
||||
{/* Process Steps */}
|
||||
<section className="py-20 bg-bg">
|
||||
<Container size="md">
|
||||
<div className="space-y-6">
|
||||
{steps.map((step) => (
|
||||
<div key={step.num} className="flex gap-6 bg-white rounded-xl p-6 border border-border">
|
||||
<div className="shrink-0">
|
||||
<div className="w-14 h-14 rounded-2xl bg-primary/10 flex items-center justify-center">
|
||||
<step.icon className="h-6 w-6 text-primary" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex items-center gap-3 mb-1">
|
||||
<span className="text-xs font-bold text-primary bg-primary/10 px-2 py-0.5 rounded-full">
|
||||
Schritt {step.num}
|
||||
</span>
|
||||
</div>
|
||||
<h3 className="text-lg font-bold text-navy mb-1">{step.title}</h3>
|
||||
<p className="text-text-muted text-sm">{step.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
|
||||
{/* Why choose us cards */}
|
||||
<section className="py-20 bg-white">
|
||||
<Container>
|
||||
<h2 className="text-2xl sm:text-3xl font-bold text-center text-navy mb-12">
|
||||
Warum complex care solutions?
|
||||
</h2>
|
||||
<div className="grid sm:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
{whyCards.map((card) => (
|
||||
<div key={card.title} className="bg-bg rounded-xl p-6 text-center border border-border">
|
||||
<div className="inline-flex items-center justify-center w-12 h-12 rounded-xl bg-primary/10 text-primary mb-4">
|
||||
<card.icon className="h-6 w-6" />
|
||||
</div>
|
||||
<h3 className="font-bold text-navy mb-2">{card.title}</h3>
|
||||
<p className="text-text-muted text-sm">{card.description}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
|
||||
{/* CTA */}
|
||||
<section className="py-20 bg-bg">
|
||||
<Container size="sm">
|
||||
<div className="bg-white rounded-2xl p-8 sm:p-12 text-center border border-border">
|
||||
<h2 className="text-2xl sm:text-3xl font-bold text-navy mb-4">
|
||||
Bereit für Ihre Zweitmeinung?
|
||||
</h2>
|
||||
<p className="text-text-muted mb-8">
|
||||
Starten Sie jetzt und erhalten Sie in kürzester Zeit eine
|
||||
fundierte, unabhängige Einschätzung.
|
||||
</p>
|
||||
<div className="flex flex-wrap justify-center gap-4">
|
||||
<Button href={phoneToHref(phone)} variant="gold" size="lg">
|
||||
<Phone className="h-4 w-4" />
|
||||
{phone}
|
||||
</Button>
|
||||
<Button href="/kontakt" variant="secondary" size="lg">
|
||||
Kontaktformular
|
||||
<ArrowRight className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
</>
|
||||
)
|
||||
return <BlockRenderer blocks={(page.layout as any[]) || []} />
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,134 +1,16 @@
|
|||
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,
|
||||
} from "lucide-react"
|
||||
import { getPage } from "@/lib/api"
|
||||
import { BlockRenderer } from "@/components/blocks/BlockRenderer"
|
||||
import { notFound } from "next/navigation"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Über uns",
|
||||
description: "complex care solutions GmbH – Ihr Partner für unabhängige medizinische Zweitmeinungen.",
|
||||
}
|
||||
|
||||
const qualityCards = [
|
||||
{ title: "Unabhängigkeit", description: "Unsere Gutachter haben keine wirtschaftlichen Verbindungen zu behandelnden Einrichtungen.", icon: Shield },
|
||||
{ title: "Expertise", description: "Nur Fachärzt:innen mit nachgewiesener Expertise und langjähriger Erfahrung.", icon: Award },
|
||||
{ title: "Patientenorientierung", description: "Der Mensch steht im Mittelpunkt – nicht die Diagnose.", icon: Heart },
|
||||
{ title: "Netzwerk", description: "Über 1000 Expert:innen aus über 50 Fachbereichen deutschlandweit.", icon: Globe },
|
||||
]
|
||||
|
||||
export default async function UeberUnsPage() {
|
||||
const settings = await getSiteSettings()
|
||||
const phone = settings?.contact?.phone || "0800 80 44 100"
|
||||
const page = await getPage("ueber-uns")
|
||||
if (!page) notFound()
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Hero */}
|
||||
<section className="bg-gradient-to-br from-navy via-navy-dark to-[#001a2e] py-20">
|
||||
<Container size="md">
|
||||
<div className="text-center">
|
||||
<h1 className="text-4xl sm:text-5xl font-bold text-white mb-4">
|
||||
Über uns
|
||||
</h1>
|
||||
<p className="text-white/60 text-lg max-w-2xl mx-auto">
|
||||
complex care solutions GmbH – Ihr Partner für unabhängige
|
||||
medizinische Zweitmeinungen seit über 15 Jahren.
|
||||
</p>
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
|
||||
{/* Quality */}
|
||||
<section className="py-20 bg-white">
|
||||
<Container>
|
||||
<h2 className="text-2xl sm:text-3xl font-bold text-center text-navy mb-12">
|
||||
Was uns auszeichnet
|
||||
</h2>
|
||||
<div className="grid sm:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
{qualityCards.map((c) => (
|
||||
<div key={c.title} className="bg-bg rounded-xl p-6 text-center border border-border">
|
||||
<div className="inline-flex items-center justify-center w-12 h-12 rounded-xl bg-primary/10 text-primary mb-4">
|
||||
<c.icon className="h-6 w-6" />
|
||||
</div>
|
||||
<h3 className="font-bold text-navy mb-2">{c.title}</h3>
|
||||
<p className="text-text-muted text-sm">{c.description}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
|
||||
{/* Company info */}
|
||||
<section className="py-20 bg-bg">
|
||||
<Container size="md">
|
||||
<div className="bg-white rounded-2xl p-8 sm:p-12 border border-border">
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<Building className="h-6 w-6 text-primary" />
|
||||
<h2 className="text-2xl font-bold text-navy">
|
||||
complex care solutions GmbH
|
||||
</h2>
|
||||
</div>
|
||||
<div className="grid sm:grid-cols-2 gap-8">
|
||||
<div className="space-y-4 text-sm text-text-muted">
|
||||
<p>
|
||||
Die complex care solutions GmbH mit Sitz in Bottrop ist ein
|
||||
unabhängiges Gesundheitsunternehmen, das sich auf medizinische
|
||||
Zweitmeinungen spezialisiert hat.
|
||||
</p>
|
||||
<p>
|
||||
Gegründet von Martin Porwoll, dem Whistleblower des Bottroper
|
||||
Zytoskandals, verfolgen wir das Ziel, Patient:innen in
|
||||
komplexen medizinischen Situationen bestmöglich zu
|
||||
unterstützen.
|
||||
</p>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
<CheckCircle className="h-4 w-4 text-gold shrink-0" />
|
||||
<span className="text-text-muted">Über 15 Jahre Erfahrung im Gesundheitswesen</span>
|
||||
</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">Netzwerk aus über 1000 Fachärzt:innen</span>
|
||||
</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">DSGVO-konform und datenschutzgeprüft</span>
|
||||
</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">Sitz in Bottrop, Hans-Böckler-Str. 19</span>
|
||||
</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: {phone}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
|
||||
{/* CTA */}
|
||||
<section className="py-16 bg-white">
|
||||
<Container size="sm">
|
||||
<div className="text-center">
|
||||
<h2 className="text-2xl font-bold text-navy mb-4">
|
||||
Haben Sie Fragen?
|
||||
</h2>
|
||||
<p className="text-text-muted mb-8">
|
||||
Wir beraten Sie gerne unverbindlich und kostenfrei.
|
||||
</p>
|
||||
<Button href={phoneToHref(phone)} variant="gold" size="lg">
|
||||
<Phone className="h-4 w-4" />
|
||||
{phone} (kostenlos)
|
||||
</Button>
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
</>
|
||||
)
|
||||
return <BlockRenderer blocks={(page.layout as any[]) || []} />
|
||||
}
|
||||
|
|
|
|||
43
src/components/blocks/BlockRenderer.tsx
Normal file
43
src/components/blocks/BlockRenderer.tsx
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import { HeroBlock } from "./HeroBlock"
|
||||
import { TextBlock } from "./TextBlock"
|
||||
import { CardGridBlock } from "./CardGridBlock"
|
||||
import { CTABlock } from "./CTABlock"
|
||||
import { ProcessStepsBlock } from "./ProcessStepsBlock"
|
||||
import { QuoteBlock } from "./QuoteBlock"
|
||||
import { HtmlEmbedBlock } from "./HtmlEmbedBlock"
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
interface BlockRendererProps {
|
||||
blocks: any[]
|
||||
}
|
||||
|
||||
export function BlockRenderer({ blocks }: BlockRendererProps) {
|
||||
if (!blocks || blocks.length === 0) return null
|
||||
|
||||
return (
|
||||
<>
|
||||
{blocks.map((block: any, index: number) => {
|
||||
const key = block.id || `block-${index}`
|
||||
|
||||
switch (block.blockType) {
|
||||
case "hero-block":
|
||||
return <HeroBlock key={key} block={block} isFirst={index === 0} />
|
||||
case "text-block":
|
||||
return <TextBlock key={key} block={block} />
|
||||
case "card-grid-block":
|
||||
return <CardGridBlock key={key} block={block} />
|
||||
case "cta-block":
|
||||
return <CTABlock key={key} block={block} />
|
||||
case "process-steps-block":
|
||||
return <ProcessStepsBlock key={key} block={block} />
|
||||
case "quote-block":
|
||||
return <QuoteBlock key={key} block={block} />
|
||||
case "html-embed-block":
|
||||
return <HtmlEmbedBlock key={key} block={block} />
|
||||
default:
|
||||
return null
|
||||
}
|
||||
})}
|
||||
</>
|
||||
)
|
||||
}
|
||||
57
src/components/blocks/CTABlock.tsx
Normal file
57
src/components/blocks/CTABlock.tsx
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
import { Container } from "@/components/ui/Container"
|
||||
import { Button } from "@/components/ui/Button"
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
interface CTABlockProps {
|
||||
block: any
|
||||
}
|
||||
|
||||
const bgClasses: Record<string, string> = {
|
||||
dark: "bg-navy text-white",
|
||||
light: "bg-bg text-navy",
|
||||
accent: "bg-primary text-white",
|
||||
}
|
||||
|
||||
export function CTABlock({ block }: CTABlockProps) {
|
||||
const bg = block.backgroundColor || "dark"
|
||||
const buttons = block.buttons || []
|
||||
const isLight = bg === "light"
|
||||
|
||||
return (
|
||||
<section className={`py-16 ${bgClasses[bg] || bgClasses.dark}`}>
|
||||
<Container size="sm">
|
||||
<div className="text-center">
|
||||
<h2 className="text-2xl sm:text-3xl font-bold mb-4">
|
||||
{block.headline}
|
||||
</h2>
|
||||
{block.description && (
|
||||
<p className={`mb-8 ${isLight ? "text-text-muted" : "text-white/80"}`}>
|
||||
{block.description}
|
||||
</p>
|
||||
)}
|
||||
{buttons.length > 0 && (
|
||||
<div className="flex flex-wrap justify-center gap-4">
|
||||
{buttons.map((btn: any, i: number) => {
|
||||
const variant = btn.style === "outline"
|
||||
? "outline"
|
||||
: btn.style === "secondary"
|
||||
? "secondary"
|
||||
: isLight ? "primary" : "gold"
|
||||
return (
|
||||
<Button
|
||||
key={btn.id || i}
|
||||
href={btn.link}
|
||||
variant={variant}
|
||||
size="lg"
|
||||
>
|
||||
{btn.text}
|
||||
</Button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
51
src/components/blocks/CardGridBlock.tsx
Normal file
51
src/components/blocks/CardGridBlock.tsx
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
import { Container } from "@/components/ui/Container"
|
||||
import { getLucideIcon } from "@/lib/icon-map"
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
interface CardGridBlockProps {
|
||||
block: any
|
||||
}
|
||||
|
||||
const columnClasses: Record<string, string> = {
|
||||
"2": "grid-cols-1 sm:grid-cols-2",
|
||||
"3": "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3",
|
||||
"4": "grid-cols-1 sm:grid-cols-2 lg:grid-cols-4",
|
||||
}
|
||||
|
||||
export function CardGridBlock({ block }: CardGridBlockProps) {
|
||||
const cols = block.columns || "3"
|
||||
const cards = block.cards || []
|
||||
|
||||
return (
|
||||
<section className="py-16 bg-bg">
|
||||
<Container>
|
||||
{block.headline && (
|
||||
<h2 className="text-2xl sm:text-3xl font-bold text-center text-navy mb-12">
|
||||
{block.headline}
|
||||
</h2>
|
||||
)}
|
||||
<div className={`grid ${columnClasses[cols] || columnClasses["3"]} gap-6`}>
|
||||
{cards.map((card: any, i: number) => {
|
||||
const Icon = card.mediaType === "icon" && card.icon
|
||||
? getLucideIcon(card.icon)
|
||||
: null
|
||||
|
||||
return (
|
||||
<div key={card.id || i} className="bg-white rounded-xl p-6 text-center border border-border">
|
||||
{Icon && (
|
||||
<div className="inline-flex items-center justify-center w-12 h-12 rounded-xl bg-primary/10 text-primary mb-4">
|
||||
<Icon className="h-6 w-6" />
|
||||
</div>
|
||||
)}
|
||||
<h3 className="font-bold text-navy mb-2">{card.title}</h3>
|
||||
{card.description && (
|
||||
<p className="text-text-muted text-sm">{card.description}</p>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
42
src/components/blocks/HeroBlock.tsx
Normal file
42
src/components/blocks/HeroBlock.tsx
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import { Container } from "@/components/ui/Container"
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
interface HeroBlockProps {
|
||||
block: any
|
||||
isFirst?: boolean
|
||||
}
|
||||
|
||||
export function HeroBlock({ block, isFirst }: HeroBlockProps) {
|
||||
const alignment = block.alignment || "center"
|
||||
|
||||
const alignClasses: Record<string, string> = {
|
||||
left: "text-left",
|
||||
center: "text-center",
|
||||
right: "text-right",
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="bg-gradient-to-br from-navy via-navy-dark to-[#001a2e] py-20">
|
||||
<Container size={isFirst ? "md" : "md"}>
|
||||
<div className={alignClasses[alignment] || "text-center"}>
|
||||
<h1 className="text-4xl sm:text-5xl font-bold text-white mb-4">
|
||||
{block.headline}
|
||||
</h1>
|
||||
{block.subline && (
|
||||
<p className="text-white/60 text-lg max-w-2xl mx-auto">
|
||||
{block.subline}
|
||||
</p>
|
||||
)}
|
||||
{block.cta?.text && block.cta?.link && (
|
||||
<a
|
||||
href={block.cta.link}
|
||||
className="mt-6 inline-flex items-center gap-2 bg-primary hover:bg-primary-dark text-white px-8 py-3.5 rounded-lg font-semibold transition-all"
|
||||
>
|
||||
{block.cta.text}
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
28
src/components/blocks/HtmlEmbedBlock.tsx
Normal file
28
src/components/blocks/HtmlEmbedBlock.tsx
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import { Container } from "@/components/ui/Container"
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
interface HtmlEmbedBlockProps {
|
||||
block: any
|
||||
}
|
||||
|
||||
const widthClasses: Record<string, string> = {
|
||||
narrow: "max-w-[620px]",
|
||||
medium: "max-w-[900px]",
|
||||
full: "max-w-7xl",
|
||||
}
|
||||
|
||||
export function HtmlEmbedBlock({ block }: HtmlEmbedBlockProps) {
|
||||
if (!block.code) return null
|
||||
|
||||
const maxWidth = block.maxWidth || "full"
|
||||
|
||||
return (
|
||||
<section className="py-16 bg-white">
|
||||
<Container>
|
||||
<div className={`mx-auto ${widthClasses[maxWidth] || widthClasses.full}`}>
|
||||
<div dangerouslySetInnerHTML={{ __html: block.code }} />
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
74
src/components/blocks/ProcessStepsBlock.tsx
Normal file
74
src/components/blocks/ProcessStepsBlock.tsx
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
import { Container } from "@/components/ui/Container"
|
||||
import { Button } from "@/components/ui/Button"
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
interface ProcessStepsBlockProps {
|
||||
block: any
|
||||
}
|
||||
|
||||
const bgClasses: Record<string, string> = {
|
||||
white: "bg-white",
|
||||
light: "bg-bg",
|
||||
dark: "bg-navy text-white",
|
||||
}
|
||||
|
||||
export function ProcessStepsBlock({ block }: ProcessStepsBlockProps) {
|
||||
const steps = block.steps || []
|
||||
const bg = block.backgroundColor || "white"
|
||||
|
||||
return (
|
||||
<section className={`py-16 ${bgClasses[bg] || "bg-white"}`}>
|
||||
<Container size="md">
|
||||
{(block.title || block.subtitle) && (
|
||||
<div className="text-center mb-12">
|
||||
{block.title && (
|
||||
<h2 className="text-2xl sm:text-3xl font-bold text-navy mb-3">
|
||||
{block.title}
|
||||
</h2>
|
||||
)}
|
||||
{block.subtitle && (
|
||||
<p className="text-text-muted">{block.subtitle}</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="space-y-6">
|
||||
{steps.map((step: any, i: number) => (
|
||||
<div key={step.id || i} className="flex gap-6 bg-white rounded-xl p-6 border border-border">
|
||||
<div className="shrink-0">
|
||||
<div className="w-14 h-14 rounded-2xl bg-primary/10 flex items-center justify-center">
|
||||
{step.icon ? (
|
||||
<span className="text-xl">{step.icon}</span>
|
||||
) : block.showNumbers ? (
|
||||
<span className="text-sm font-bold text-primary">
|
||||
{String(i + 1).padStart(2, "0")}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
{block.showNumbers && (
|
||||
<span className="text-xs font-bold text-primary bg-primary/10 px-2 py-0.5 rounded-full">
|
||||
Schritt {String(i + 1).padStart(2, "0")}
|
||||
</span>
|
||||
)}
|
||||
<h3 className="text-lg font-bold text-navy mb-1 mt-1">{step.title}</h3>
|
||||
{step.description && (
|
||||
<p className="text-text-muted text-sm">{step.description}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{block.cta?.show && block.cta?.href && (
|
||||
<div className="text-center mt-10">
|
||||
<Button href={block.cta.href} variant="primary" size="lg">
|
||||
{block.cta.label || "Jetzt starten"}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</Container>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
46
src/components/blocks/QuoteBlock.tsx
Normal file
46
src/components/blocks/QuoteBlock.tsx
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import { Container } from "@/components/ui/Container"
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
interface QuoteBlockProps {
|
||||
block: any
|
||||
}
|
||||
|
||||
export function QuoteBlock({ block }: QuoteBlockProps) {
|
||||
const isHighlighted = block.style === "highlighted"
|
||||
|
||||
if (isHighlighted) {
|
||||
return (
|
||||
<section className="py-12 bg-navy">
|
||||
<Container size="sm">
|
||||
<div className="text-center">
|
||||
<blockquote className="text-white/80 text-lg leading-relaxed italic">
|
||||
“{block.quote}”
|
||||
</blockquote>
|
||||
{(block.author || block.role) && (
|
||||
<cite className="text-white/50 text-sm mt-4 block not-italic">
|
||||
{block.author && `— ${block.author}`}
|
||||
{block.role && `, ${block.role}`}
|
||||
</cite>
|
||||
)}
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="py-12 bg-white">
|
||||
<Container size="md">
|
||||
<blockquote className="border-l-4 border-gold pl-6 py-2 bg-gold/5 rounded-r-lg">
|
||||
<p className="text-navy italic">“{block.quote}”</p>
|
||||
{(block.author || block.role) && (
|
||||
<cite className="text-sm text-text-muted mt-2 block not-italic">
|
||||
{block.author && `— ${block.author}`}
|
||||
{block.role && `, ${block.role}`}
|
||||
</cite>
|
||||
)}
|
||||
</blockquote>
|
||||
</Container>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
25
src/components/blocks/TextBlock.tsx
Normal file
25
src/components/blocks/TextBlock.tsx
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import { Container } from "@/components/ui/Container"
|
||||
import { RichTextRenderer } from "@/components/ui/RichTextRenderer"
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
interface TextBlockProps {
|
||||
block: any
|
||||
}
|
||||
|
||||
const widthMap: Record<string, "sm" | "md" | "lg"> = {
|
||||
narrow: "sm",
|
||||
medium: "md",
|
||||
full: "lg",
|
||||
}
|
||||
|
||||
export function TextBlock({ block }: TextBlockProps) {
|
||||
const size = widthMap[block.width] || "md"
|
||||
|
||||
return (
|
||||
<section className="py-16 bg-white">
|
||||
<Container size={size}>
|
||||
<RichTextRenderer content={block.content} />
|
||||
</Container>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
8
src/components/blocks/index.ts
Normal file
8
src/components/blocks/index.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
export { BlockRenderer } from "./BlockRenderer"
|
||||
export { HeroBlock } from "./HeroBlock"
|
||||
export { TextBlock } from "./TextBlock"
|
||||
export { CardGridBlock } from "./CardGridBlock"
|
||||
export { CTABlock } from "./CTABlock"
|
||||
export { ProcessStepsBlock } from "./ProcessStepsBlock"
|
||||
export { QuoteBlock } from "./QuoteBlock"
|
||||
export { HtmlEmbedBlock } from "./HtmlEmbedBlock"
|
||||
Loading…
Reference in a new issue