mirror of
https://github.com/complexcaresolutions/frontend.zweitmeinu.ng.git
synced 2026-03-17 15:03:48 +00:00
feat: complete zweitmeinu.ng frontend implementation
Full medical second opinion website with: - 10 routes: home, fachbereiche (overview + 6 detail), faq, so-funktionierts, motivation, ueber-uns, kontakt, impressum, datenschutz - Premium medical design: navy/blue/gold color system, Roboto Condensed - Layout: TopBar, sticky Header with mega-menu, EmergencyBanner, Footer - Service detail pages with benefits, checklist, stats, CTA - FAQ page with search, category filter, accordion, Schema.org structured data - Contact form with validation and Payload CMS form submission - @c2s/payload-contracts integration for type-safe API access - Tailwind CSS v4 design system with custom animations - PM2 ecosystem config on port 3002 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
2d38b69d02
commit
69eb87edae
45 changed files with 7113 additions and 2 deletions
41
.gitignore
vendored
Normal file
41
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.*
|
||||||
|
.yarn/*
|
||||||
|
!.yarn/patches
|
||||||
|
!.yarn/plugins
|
||||||
|
!.yarn/releases
|
||||||
|
!.yarn/versions
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# next.js
|
||||||
|
/.next/
|
||||||
|
/out/
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
*.pem
|
||||||
|
|
||||||
|
# debug
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# env files (can opt-in for committing if needed)
|
||||||
|
.env*
|
||||||
|
|
||||||
|
# vercel
|
||||||
|
.vercel
|
||||||
|
|
||||||
|
# typescript
|
||||||
|
*.tsbuildinfo
|
||||||
|
next-env.d.ts
|
||||||
38
README.md
38
README.md
|
|
@ -1,2 +1,36 @@
|
||||||
# frontend.zweitmeinu.ng
|
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
|
||||||
Frontend for zweitmeinu.ng - Next.js with Payload CMS
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
First, run the development server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
# or
|
||||||
|
yarn dev
|
||||||
|
# or
|
||||||
|
pnpm dev
|
||||||
|
# or
|
||||||
|
bun dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||||
|
|
||||||
|
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
||||||
|
|
||||||
|
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
|
||||||
|
|
||||||
|
## Learn More
|
||||||
|
|
||||||
|
To learn more about Next.js, take a look at the following resources:
|
||||||
|
|
||||||
|
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||||
|
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||||
|
|
||||||
|
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
|
||||||
|
|
||||||
|
## Deploy on Vercel
|
||||||
|
|
||||||
|
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||||
|
|
||||||
|
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
|
||||||
|
|
|
||||||
12
ecosystem.config.js
Normal file
12
ecosystem.config.js
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
module.exports = {
|
||||||
|
apps: [{
|
||||||
|
name: 'zweitmeinu.ng',
|
||||||
|
script: 'node_modules/.bin/next',
|
||||||
|
args: 'start',
|
||||||
|
cwd: '/home/frontend/frontend.zweitmeinu.ng',
|
||||||
|
env: {
|
||||||
|
PORT: 3002,
|
||||||
|
NODE_ENV: 'production',
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
}
|
||||||
18
eslint.config.mjs
Normal file
18
eslint.config.mjs
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { defineConfig, globalIgnores } from "eslint/config";
|
||||||
|
import nextVitals from "eslint-config-next/core-web-vitals";
|
||||||
|
import nextTs from "eslint-config-next/typescript";
|
||||||
|
|
||||||
|
const eslintConfig = defineConfig([
|
||||||
|
...nextVitals,
|
||||||
|
...nextTs,
|
||||||
|
// Override default ignores of eslint-config-next.
|
||||||
|
globalIgnores([
|
||||||
|
// Default ignores of eslint-config-next:
|
||||||
|
".next/**",
|
||||||
|
"out/**",
|
||||||
|
"build/**",
|
||||||
|
"next-env.d.ts",
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
|
||||||
|
export default eslintConfig;
|
||||||
13
next.config.ts
Normal file
13
next.config.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
import type { NextConfig } from "next";
|
||||||
|
|
||||||
|
const nextConfig: NextConfig = {
|
||||||
|
transpilePackages: ["@c2s/payload-contracts"],
|
||||||
|
images: {
|
||||||
|
remotePatterns: [
|
||||||
|
{ hostname: "cms.c2sgmbh.de" },
|
||||||
|
{ hostname: "pl.porwoll.tech" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default nextConfig;
|
||||||
31
package.json
Normal file
31
package.json
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
{
|
||||||
|
"name": "frontend.zweitmeinu.ng",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "next dev",
|
||||||
|
"build": "next build",
|
||||||
|
"start": "next start",
|
||||||
|
"lint": "eslint"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@c2s/payload-contracts": "github:complexcaresolutions/payload-contracts",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
|
"lucide-react": "^0.474.0",
|
||||||
|
"next": "16.1.6",
|
||||||
|
"react": "19.2.1",
|
||||||
|
"react-dom": "19.2.1",
|
||||||
|
"tailwind-merge": "^3.4.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@tailwindcss/postcss": "^4",
|
||||||
|
"@types/node": "^22",
|
||||||
|
"@types/react": "^19",
|
||||||
|
"@types/react-dom": "^19",
|
||||||
|
"eslint": "^9",
|
||||||
|
"eslint-config-next": "16.1.6",
|
||||||
|
"tailwindcss": "^4",
|
||||||
|
"typescript": "^5"
|
||||||
|
},
|
||||||
|
"packageManager": "pnpm@10.6.2"
|
||||||
|
}
|
||||||
4053
pnpm-lock.yaml
Normal file
4053
pnpm-lock.yaml
Normal file
File diff suppressed because it is too large
Load diff
2
pnpm-workspace.yaml
Normal file
2
pnpm-workspace.yaml
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
onlyBuiltDependencies:
|
||||||
|
- "@c2s/payload-contracts"
|
||||||
7
postcss.config.mjs
Normal file
7
postcss.config.mjs
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
const config = {
|
||||||
|
plugins: {
|
||||||
|
"@tailwindcss/postcss": {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
1
public/file.svg
Normal file
1
public/file.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
|
||||||
|
After Width: | Height: | Size: 391 B |
1
public/globe.svg
Normal file
1
public/globe.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
|
||||||
|
After Width: | Height: | Size: 1 KiB |
1
public/next.svg
Normal file
1
public/next.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
1
public/vercel.svg
Normal file
1
public/vercel.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
|
||||||
|
After Width: | Height: | Size: 128 B |
1
public/window.svg
Normal file
1
public/window.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
|
||||||
|
After Width: | Height: | Size: 385 B |
27
src/app/datenschutz/page.tsx
Normal file
27
src/app/datenschutz/page.tsx
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
import type { Metadata } from "next"
|
||||||
|
import { Container } from "@/components/ui/Container"
|
||||||
|
|
||||||
|
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>
|
||||||
|
<div className="rounded-2xl overflow-hidden border border-border">
|
||||||
|
<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"
|
||||||
|
className="w-full min-h-[800px] border-0"
|
||||||
|
loading="lazy"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
357
src/app/fachbereiche/[slug]/page.tsx
Normal file
357
src/app/fachbereiche/[slug]/page.tsx
Normal file
|
|
@ -0,0 +1,357 @@
|
||||||
|
import type { Metadata } from "next"
|
||||||
|
import { notFound } from "next/navigation"
|
||||||
|
import { Container } from "@/components/ui/Container"
|
||||||
|
import { Button } from "@/components/ui/Button"
|
||||||
|
import {
|
||||||
|
Phone, Mail, ArrowRight, Check, Heart, Brain,
|
||||||
|
Stethoscope, Pill, FlaskConical, Activity, Shield,
|
||||||
|
FileCheck, Users, Clock, HeartHandshake,
|
||||||
|
} from "lucide-react"
|
||||||
|
|
||||||
|
// Static service data — will be enhanced with CMS data when populated
|
||||||
|
const servicesData: Record<string, {
|
||||||
|
title: string
|
||||||
|
fullTitle: string
|
||||||
|
category: string
|
||||||
|
icon: React.ElementType
|
||||||
|
shortDescription: string
|
||||||
|
description: string[]
|
||||||
|
benefits: Array<{ title: string; description: string; icon: React.ElementType }>
|
||||||
|
checklist: Array<{ title: string; description: string }>
|
||||||
|
seo: { title: string; description: string }
|
||||||
|
}> = {
|
||||||
|
"zweitmeinung-intensivmedizin": {
|
||||||
|
title: "Intensivmedizin",
|
||||||
|
fullTitle: "Zweitmeinung Intensivmedizin",
|
||||||
|
category: "Notfall",
|
||||||
|
icon: Activity,
|
||||||
|
shortDescription: "Unabhängige ärztliche Zweitmeinung bei laufender oder geplanter Intensivbehandlung. Wir prüfen medizinische Indikation, Patientenwille und Behandlungsalternativen.",
|
||||||
|
description: [
|
||||||
|
"Wenn intensivmedizinische Entscheidungen anstehen, brauchen Patient:innen und ihre Angehörigen mehr als nur medizinische Information – sie brauchen Orientierung, Sicherheit und eine unabhängige fachliche Einschätzung.",
|
||||||
|
"Unsere Dienstleistung richtet sich an Menschen in sehr schwerer gesundheitlicher Lage, etwa bei Langzeitbeatmung, Wachkoma oder im palliativen Kontext.",
|
||||||
|
],
|
||||||
|
benefits: [
|
||||||
|
{ title: "Unabhängige Beurteilung durch erfahrene Intensivmediziner", description: "Neutrale Einschätzung ohne wirtschaftliche Eigeninteressen – ausschließlich in Ihrem Interesse.", icon: Shield },
|
||||||
|
{ title: "Vermeidung unnötiger Eingriffe bei fehlender Indikation", description: "Schutz vor überflüssigen invasiven Behandlungen durch fundierte medizinische Bewertung.", icon: FileCheck },
|
||||||
|
{ title: "Besseres Verständnis der Risiken und Therapieziele", description: "Umfassende Aufklärung über alle Behandlungsoptionen und deren Auswirkungen.", icon: Users },
|
||||||
|
{ title: "Stärkung Ihrer Entscheidungssicherheit und Eigenverantwortung", description: "Fundierte Basis für selbstbestimmte Entscheidungen über Ihre Versorgung.", icon: HeartHandshake },
|
||||||
|
],
|
||||||
|
checklist: [
|
||||||
|
{ title: "Strukturierte Beratung durch erfahrene Case Manager:innen", description: "Individuelle Betreuung für Ihre weitere Behandlungsplanung." },
|
||||||
|
{ title: "Unabhängige ärztliche Zweitmeinung inkl. schriftlichem Gutachten", description: "Fundierte Einschätzung durch Fachärzt:innen für Intensivmedizin." },
|
||||||
|
{ title: "Bewertung von Therapiezielen, Indikationen und Prognose", description: "Umfassende Analyse aller relevanten medizinischen Aspekte." },
|
||||||
|
{ title: "Unterstützung bei palliativer Umsteuerung bis Pflegeüberleitung", description: "Begleitung bei der Umsetzung der empfohlenen Maßnahmen." },
|
||||||
|
],
|
||||||
|
seo: { title: "Zweitmeinung Intensivmedizin", description: "Unabhängige ärztliche Zweitmeinung bei intensivmedizinischer Behandlung. Jetzt fundierte Empfehlung einholen." },
|
||||||
|
},
|
||||||
|
"zweitmeinung-kardiologie": {
|
||||||
|
title: "Kardiologie",
|
||||||
|
fullTitle: "Zweitmeinung Kardiologie",
|
||||||
|
category: "Beratung",
|
||||||
|
icon: Heart,
|
||||||
|
shortDescription: "Unabhängige ärztliche Zweitmeinung vor Herzkatheter, Stent oder OP. Fundierte Empfehlung durch erfahrene Kardiolog:innen – verständlich, sicher, neutral.",
|
||||||
|
description: [
|
||||||
|
"Herzbeschwerden verunsichern – und geplante Eingriffe wie eine Stent-Implantation werfen viele Fragen auf.",
|
||||||
|
"Unsere kardiologische Zweitmeinung gibt Ihnen Klarheit und Sicherheit bei wichtigen Herzentscheidungen.",
|
||||||
|
],
|
||||||
|
benefits: [
|
||||||
|
{ title: "Unabhängige Beurteilung durch erfahrene Kardiolog:innen", description: "Neutrale Einschätzung Ihrer kardiologischen Befunde ohne wirtschaftliche Interessen.", icon: Shield },
|
||||||
|
{ title: "Vermeidung unnötiger Eingriffe bei fehlender Indikation", description: "Schutz vor überflüssigen invasiven Behandlungen durch fundierte Bewertung.", icon: FileCheck },
|
||||||
|
{ title: "Besseres Verständnis der Risiken und Therapieziele", description: "Umfassende Aufklärung über alle Behandlungsoptionen.", icon: Users },
|
||||||
|
{ title: "Stärkung Ihrer Entscheidungssicherheit", description: "Fundierte Basis für selbstbestimmte Entscheidungen über Ihre Herzgesundheit.", icon: HeartHandshake },
|
||||||
|
],
|
||||||
|
checklist: [
|
||||||
|
{ title: "Bewertung Ihrer Diagnosen und EKG-/Katheterbefunde", description: "Sach- und leitliniengerechte Beurteilung durch Fachärzt:innen." },
|
||||||
|
{ title: "Zweitmeinung bei geplanter PCI, Bypass-OP oder Umstellung", description: "Unabhängige Einschätzung aller kardiologischen Behandlungsoptionen." },
|
||||||
|
{ title: "Schriftliches ärztliches Gutachten mit klarer Empfehlung", description: "Nachvollziehbare, fundierte Empfehlung für Ihre Entscheidung." },
|
||||||
|
{ title: "Persönliche Erläuterung telefonisch oder per Video", description: "Direkter Austausch mit unseren Kardiologie-Expert:innen." },
|
||||||
|
],
|
||||||
|
seo: { title: "Zweitmeinung Kardiologie", description: "Unabhängige Zweitmeinung bei geplanter PCI oder Herzoperation." },
|
||||||
|
},
|
||||||
|
"zweitmeinung-onkologie": {
|
||||||
|
title: "Onkologie",
|
||||||
|
fullTitle: "Zweitmeinung Onkologie",
|
||||||
|
category: "Beratung",
|
||||||
|
icon: FlaskConical,
|
||||||
|
shortDescription: "Unabhängige ärztliche Zweitmeinung bei Krebs. Fundierte Einschätzung von Therapieoptionen durch erfahrene Onkolog:innen.",
|
||||||
|
description: [
|
||||||
|
"Eine Krebsdiagnose ist ein Einschnitt. Neben der seelischen Belastung stellt sich oft die Frage: Ist die empfohlene Behandlung wirklich die beste Wahl?",
|
||||||
|
"Unsere onkologische Zweitmeinung hilft Ihnen, die richtige Therapieentscheidung zu treffen.",
|
||||||
|
],
|
||||||
|
benefits: [
|
||||||
|
{ title: "Unabhängige Beurteilung durch erfahrene Onkolog:innen", description: "Neutrale Einschätzung Ihrer Krebsdiagnose und Therapieoptionen.", icon: Shield },
|
||||||
|
{ title: "Bewertung von Wirksamkeit und Nebenwirkungen", description: "Transparente Analyse aller Behandlungswege und deren Auswirkungen auf Ihre Lebensqualität.", icon: FileCheck },
|
||||||
|
{ title: "Einbindung von Case Management und Palliativberatung", description: "Ganzheitliche Betreuung über die reine Diagnose hinaus.", icon: Users },
|
||||||
|
{ title: "Stärkung Ihrer Entscheidungssicherheit", description: "Fundierte Grundlage für informierte Therapieentscheidungen.", icon: HeartHandshake },
|
||||||
|
],
|
||||||
|
checklist: [
|
||||||
|
{ title: "Auswertung Ihrer Diagnose und Befunde", description: "Umfassende Prüfung durch erfahrene Fachärzt:innen." },
|
||||||
|
{ title: "Bewertung der geplanten Therapie", description: "Analyse von Wirksamkeit, Nebenwirkungen und Lebensqualität." },
|
||||||
|
{ title: "Schriftliches Zweitmeinungsgutachten", description: "Nachvollziehbare, medizinisch fundierte Empfehlung." },
|
||||||
|
{ title: "Optionales Gespräch per Telefon oder Video", description: "Persönliche Erläuterung und Beantwortung Ihrer Fragen." },
|
||||||
|
],
|
||||||
|
seo: { title: "Zweitmeinung Onkologie", description: "Unabhängige ärztliche Zweitmeinung bei Krebs. Behandlungsalternativen prüfen." },
|
||||||
|
},
|
||||||
|
"zweitmeinung-nephrologie": {
|
||||||
|
title: "Nephrologie",
|
||||||
|
fullTitle: "Zweitmeinung Nephrologie",
|
||||||
|
category: "Beratung",
|
||||||
|
icon: Stethoscope,
|
||||||
|
shortDescription: "Unabhängige ärztliche Einschätzung bei Nierenerkrankungen, Dialyseempfehlung oder Transplantationsvorbereitung.",
|
||||||
|
description: [
|
||||||
|
"Die Diagnose einer chronischen Nierenerkrankung oder die Empfehlung zur Dialyse ist ein gravierender Einschnitt.",
|
||||||
|
"Unsere nephrologische Zweitmeinung gibt Ihnen Klarheit bei wichtigen Nierenentscheidungen.",
|
||||||
|
],
|
||||||
|
benefits: [
|
||||||
|
{ title: "Unabhängige Beurteilung durch erfahrene Nephrolog:innen", description: "Neutrale Einschätzung Ihrer nephrologischen Diagnostik.", icon: Shield },
|
||||||
|
{ title: "Prüfung der Dialyse-Notwendigkeit", description: "Bewertung ob und wann eine Dialyse tatsächlich erforderlich ist.", icon: FileCheck },
|
||||||
|
{ title: "Bewertung konservativer Behandlungsoptionen", description: "Prüfung alternativer Therapiewege vor invasiven Maßnahmen.", icon: Users },
|
||||||
|
{ title: "Stärkung Ihrer Entscheidungssicherheit", description: "Fundierte Basis für selbstbestimmte Entscheidungen.", icon: HeartHandshake },
|
||||||
|
],
|
||||||
|
checklist: [
|
||||||
|
{ title: "Prüfung der nephrologischen Diagnostik und Laborwerte", description: "Umfassende Analyse Ihrer Nierenfunktionswerte." },
|
||||||
|
{ title: "Zweitmeinung durch Fachärzt:innen für Nephrologie", description: "Unabhängige Einschätzung erfahrener Spezialist:innen." },
|
||||||
|
{ title: "Gutachten zur Notwendigkeit einer Dialyse", description: "Klare Empfehlung zum Zeitpunkt und zur Art der Behandlung." },
|
||||||
|
{ title: "Bewertung konservativer Behandlungsoptionen", description: "Prüfung aller verfügbaren Therapiealternativen." },
|
||||||
|
],
|
||||||
|
seo: { title: "Zweitmeinung Nephrologie", description: "Unabhängige ärztliche Zweitmeinung bei chronischer Niereninsuffizienz oder Dialyseempfehlung." },
|
||||||
|
},
|
||||||
|
"zweitmeinung-gallenblase": {
|
||||||
|
title: "Gallenblase",
|
||||||
|
fullTitle: "Zweitmeinung Gallenblase",
|
||||||
|
category: "Beratung",
|
||||||
|
icon: Pill,
|
||||||
|
shortDescription: "Unabhängige ärztliche Zweitmeinung vor einer geplanten Gallenblasen-OP. Wir prüfen, ob der Eingriff medizinisch notwendig ist.",
|
||||||
|
description: [
|
||||||
|
"Viele Menschen erhalten bei Gallensteinen die Empfehlung, die Gallenblase entfernen zu lassen. Doch nicht in jedem Fall ist eine Operation notwendig.",
|
||||||
|
"Unsere Zweitmeinung hilft Ihnen, unnötige Operationen zu vermeiden.",
|
||||||
|
],
|
||||||
|
benefits: [
|
||||||
|
{ title: "Unabhängige Beurteilung durch erfahrene Chirurg:innen", description: "Neutrale Einschätzung der OP-Indikation.", icon: Shield },
|
||||||
|
{ title: "Vermeidung unnötiger Operationen", description: "Schutz vor überflüssigen Eingriffen durch fundierte Bewertung.", icon: FileCheck },
|
||||||
|
{ title: "Aufklärung über konservative Alternativen", description: "Information über nicht-operative Behandlungsmöglichkeiten.", icon: Users },
|
||||||
|
{ title: "Verständliche Erklärung der Befunde", description: "Klare Darstellung von Risiken und Nutzen.", icon: HeartHandshake },
|
||||||
|
],
|
||||||
|
checklist: [
|
||||||
|
{ title: "Bewertung Ihrer Beschwerden und Untersuchungsergebnisse", description: "Sorgfältige Analyse aller vorliegenden Befunde." },
|
||||||
|
{ title: "Prüfung der OP-Indikation nach medizinischen Leitlinien", description: "Leitlinienbasierte Bewertung der Operationsnotwendigkeit." },
|
||||||
|
{ title: "Zweitmeinung durch Viszeralchirurg:innen oder Gastroenterolog:innen", description: "Unabhängige Einschätzung spezialisierter Fachärzt:innen." },
|
||||||
|
{ title: "Verständliches Gutachten mit klarer Empfehlung", description: "Nachvollziehbare Entscheidungsgrundlage." },
|
||||||
|
],
|
||||||
|
seo: { title: "Zweitmeinung Gallenblase", description: "Gallenblasen-OP empfohlen? Holen Sie sich eine unabhängige Zweitmeinung." },
|
||||||
|
},
|
||||||
|
"zweitmeinung-schilddruese": {
|
||||||
|
title: "Schilddrüse",
|
||||||
|
fullTitle: "Zweitmeinung Schilddrüse",
|
||||||
|
category: "Beratung",
|
||||||
|
icon: Brain,
|
||||||
|
shortDescription: "Unabhängige ärztliche Einschätzung vor einer geplanten Schilddrüsen-OP durch erfahrene Endokrinolog:innen.",
|
||||||
|
description: [
|
||||||
|
"Die Empfehlung zur Entfernung der Schilddrüse ist für viele Menschen mit Sorgen verbunden. Doch ist eine Operation wirklich notwendig?",
|
||||||
|
"Unsere Zweitmeinung gibt Ihnen Sicherheit bei der Entscheidung.",
|
||||||
|
],
|
||||||
|
benefits: [
|
||||||
|
{ title: "Unabhängige Beurteilung durch erfahrene Endokrinolog:innen", description: "Neutrale Einschätzung Ihrer Schilddrüsenbefunde.", icon: Shield },
|
||||||
|
{ title: "Prüfung der OP-Notwendigkeit", description: "Bewertung ob eine Operation tatsächlich indiziert ist.", icon: FileCheck },
|
||||||
|
{ title: "Bewertung konservativer Alternativen", description: "Prüfung medikamentöser oder abwartender Therapieoptionen.", icon: Users },
|
||||||
|
{ title: "Verständliche Erklärung der Befunde", description: "Nachvollziehbare Darstellung aller Optionen.", icon: HeartHandshake },
|
||||||
|
],
|
||||||
|
checklist: [
|
||||||
|
{ title: "Prüfung von Ultraschallbefunden, Szintigrammen, Laborwerten", description: "Umfassende Analyse aller diagnostischen Ergebnisse." },
|
||||||
|
{ title: "Zweitmeinung durch Endokrinolog:innen oder Schilddrüsenchirurg:innen", description: "Spezialisierte Fachärzt:innen bewerten Ihren Fall." },
|
||||||
|
{ title: "Schriftliches Gutachten mit nachvollziehbarer Empfehlung", description: "Fundierte Entscheidungsgrundlage für Sie und Ihre Ärzt:innen." },
|
||||||
|
{ title: "Bei Bedarf: telefonische Erläuterung", description: "Persönliches Gespräch zu allen offenen Fragen." },
|
||||||
|
],
|
||||||
|
seo: { title: "Zweitmeinung Schilddrüse", description: "Schilddrüsen-OP empfohlen? Lassen Sie die Notwendigkeit prüfen." },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const slugs = Object.keys(servicesData)
|
||||||
|
|
||||||
|
export async function generateStaticParams() {
|
||||||
|
return slugs.map((slug) => ({ slug }))
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function generateMetadata({
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
params: Promise<{ slug: string }>
|
||||||
|
}): Promise<Metadata> {
|
||||||
|
const { slug } = await params
|
||||||
|
const service = servicesData[slug]
|
||||||
|
if (!service) return {}
|
||||||
|
return { title: service.seo.title, description: service.seo.description }
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function ServiceDetailPage({
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
params: Promise<{ slug: string }>
|
||||||
|
}) {
|
||||||
|
const { slug } = await params
|
||||||
|
const service = servicesData[slug]
|
||||||
|
if (!service) notFound()
|
||||||
|
|
||||||
|
const Icon = service.icon
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* Hero */}
|
||||||
|
<section className="bg-gradient-to-br from-navy via-navy-dark to-[#001a2e] py-20 sm:py-28">
|
||||||
|
<Container size="md">
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="inline-flex items-center justify-center w-16 h-16 rounded-2xl bg-white/10 text-white mb-6">
|
||||||
|
<Icon className="h-8 w-8" />
|
||||||
|
</div>
|
||||||
|
<p className="text-primary-light text-sm font-semibold mb-2">
|
||||||
|
Fachbereiche / {service.title}
|
||||||
|
</p>
|
||||||
|
<h1 className="text-3xl sm:text-4xl lg:text-5xl font-bold text-white mb-6">
|
||||||
|
{service.fullTitle} – klare Empfehlungen bei{" "}
|
||||||
|
{service.title === "Intensivmedizin"
|
||||||
|
? "kritischen Entscheidungen"
|
||||||
|
: `${service.title}-Entscheidungen`}
|
||||||
|
</h1>
|
||||||
|
<p className="text-white/60 text-lg max-w-2xl mx-auto mb-10">
|
||||||
|
{service.shortDescription}
|
||||||
|
</p>
|
||||||
|
<div className="flex flex-wrap justify-center gap-4">
|
||||||
|
<Button href="tel:+4980080441000" variant="gold" size="lg">
|
||||||
|
<Phone className="h-4 w-4" />
|
||||||
|
Jetzt beraten lassen
|
||||||
|
</Button>
|
||||||
|
<Button href="#was-wir-tun" variant="outline" size="lg">
|
||||||
|
Mehr erfahren
|
||||||
|
<ArrowRight className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* When is a second opinion useful */}
|
||||||
|
<section className="py-20 bg-white">
|
||||||
|
<Container size="md">
|
||||||
|
<h2 className="text-2xl sm:text-3xl font-bold text-center text-navy mb-4">
|
||||||
|
Wann ist eine Zweitmeinung sinnvoll?
|
||||||
|
</h2>
|
||||||
|
<p className="text-text-muted text-center max-w-2xl mx-auto mb-12">
|
||||||
|
{service.description[0]}
|
||||||
|
</p>
|
||||||
|
<div className="grid sm:grid-cols-2 gap-6">
|
||||||
|
{service.benefits.map((b) => (
|
||||||
|
<div
|
||||||
|
key={b.title}
|
||||||
|
className="bg-bg rounded-xl p-6 border border-border"
|
||||||
|
>
|
||||||
|
<b.icon className="h-6 w-6 text-primary mb-3" />
|
||||||
|
<h3 className="font-bold text-navy mb-2">{b.title}</h3>
|
||||||
|
<p className="text-sm text-text-muted">{b.description}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* What we do for you */}
|
||||||
|
<section id="was-wir-tun" className="py-20 bg-bg">
|
||||||
|
<Container size="md">
|
||||||
|
<h2 className="text-2xl sm:text-3xl font-bold text-center text-navy mb-3">
|
||||||
|
Was wir für Sie tun
|
||||||
|
</h2>
|
||||||
|
<p className="text-text-muted text-center mb-12">
|
||||||
|
{service.description[1]}
|
||||||
|
</p>
|
||||||
|
<div className="space-y-4 max-w-2xl mx-auto">
|
||||||
|
{service.checklist.map((item) => (
|
||||||
|
<div
|
||||||
|
key={item.title}
|
||||||
|
className="flex gap-4 bg-white rounded-xl p-5 border border-border"
|
||||||
|
>
|
||||||
|
<div className="shrink-0 w-8 h-8 rounded-full bg-gold/20 flex items-center justify-center">
|
||||||
|
<Check className="h-4 w-4 text-gold-hover" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="font-bold text-navy">{item.title}</h3>
|
||||||
|
<p className="text-sm text-text-muted mt-1">
|
||||||
|
{item.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Stats */}
|
||||||
|
<section className="py-16 bg-white border-y border-border">
|
||||||
|
<Container size="md">
|
||||||
|
<h2 className="text-2xl font-bold text-center text-navy mb-3">
|
||||||
|
Warum complex care solutions?
|
||||||
|
</h2>
|
||||||
|
<p className="text-text-muted text-center max-w-2xl mx-auto mb-12">
|
||||||
|
Unsere Expert:innen arbeiten unabhängig und ausschließlich im
|
||||||
|
Interesse der Patient:innen.
|
||||||
|
</p>
|
||||||
|
<div className="grid grid-cols-3 gap-8 text-center">
|
||||||
|
<div>
|
||||||
|
<div className="text-4xl sm:text-5xl font-bold text-gold">
|
||||||
|
500+
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-text-muted mt-1">
|
||||||
|
{service.title === "Kardiologie"
|
||||||
|
? "Kardiologische Zweitmeinungen"
|
||||||
|
: "Medizinische Zweitmeinungen"}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="text-4xl sm:text-5xl font-bold text-gold">
|
||||||
|
15+
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-text-muted mt-1">Jahre Erfahrung</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="text-4xl sm:text-5xl font-bold text-gold">
|
||||||
|
95%
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-text-muted mt-1">
|
||||||
|
Patientenzufriedenheit
|
||||||
|
</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 shadow-sm">
|
||||||
|
<div className="inline-flex items-center justify-center w-14 h-14 rounded-2xl bg-primary/10 text-primary mb-6">
|
||||||
|
<Icon className="h-7 w-7" />
|
||||||
|
</div>
|
||||||
|
<h2 className="text-2xl sm:text-3xl font-bold text-navy mb-4">
|
||||||
|
Bereit für Ihre {service.title === "Intensivmedizin" ? "intensivmedizinische" : service.title.toLowerCase() === "gallenblase" ? "Gallenblasen-" : service.title.toLowerCase() === "schilddrüse" ? "Schilddrüsen-" : `${service.title.toLowerCase()}ische`} Zweitmeinung?
|
||||||
|
</h2>
|
||||||
|
<p className="text-text-muted mb-8">
|
||||||
|
Kontaktieren Sie uns für eine unabhängige, professionelle
|
||||||
|
Einschätzung.
|
||||||
|
</p>
|
||||||
|
<div className="flex flex-wrap justify-center gap-4">
|
||||||
|
<Button href="tel:+4980080441000" variant="gold" size="lg">
|
||||||
|
<Phone className="h-4 w-4" />
|
||||||
|
+49 800 80 44 100
|
||||||
|
</Button>
|
||||||
|
<Button href="/kontakt" variant="secondary" size="lg">
|
||||||
|
<Mail className="h-4 w-4" />
|
||||||
|
E-Mail schreiben
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-text-muted mt-4">
|
||||||
|
Kostenlos für gesetzlich und privat Versicherte
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</section>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
142
src/app/fachbereiche/page.tsx
Normal file
142
src/app/fachbereiche/page.tsx
Normal file
|
|
@ -0,0 +1,142 @@
|
||||||
|
import type { Metadata } from "next"
|
||||||
|
import Link from "next/link"
|
||||||
|
import { ArrowRight, Heart, Brain, Stethoscope, Pill, FlaskConical, Activity } from "lucide-react"
|
||||||
|
import { Container } from "@/components/ui/Container"
|
||||||
|
import { Button } from "@/components/ui/Button"
|
||||||
|
import { Phone } from "lucide-react"
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Fachbereiche",
|
||||||
|
description: "Medizinische Zweitmeinung in 6 Fachbereichen: Intensivmedizin, Kardiologie, Onkologie, Nephrologie, Gallenblase und Schilddrüse.",
|
||||||
|
}
|
||||||
|
|
||||||
|
const services = [
|
||||||
|
{
|
||||||
|
title: "Intensivmedizin",
|
||||||
|
slug: "zweitmeinung-intensivmedizin",
|
||||||
|
description: "Unabhängige ärztliche Zweitmeinung bei laufender oder geplanter Intensivbehandlung. Wir prüfen medizinische Indikation, Patientenwille und Behandlungsalternativen.",
|
||||||
|
icon: Activity,
|
||||||
|
category: "Notfall",
|
||||||
|
color: "bg-red-50 text-red-600",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Kardiologie",
|
||||||
|
slug: "zweitmeinung-kardiologie",
|
||||||
|
description: "Unabhängige ärztliche Zweitmeinung vor Herzkatheter, Stent oder OP. Fundierte Empfehlung durch erfahrene Kardiolog:innen.",
|
||||||
|
icon: Heart,
|
||||||
|
category: "Beratung",
|
||||||
|
color: "bg-primary/10 text-primary",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Onkologie",
|
||||||
|
slug: "zweitmeinung-onkologie",
|
||||||
|
description: "Unabhängige ärztliche Zweitmeinung bei Krebs. Fundierte Einschätzung von Therapieoptionen durch erfahrene Onkolog:innen.",
|
||||||
|
icon: FlaskConical,
|
||||||
|
category: "Beratung",
|
||||||
|
color: "bg-purple-50 text-purple-600",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Nephrologie",
|
||||||
|
slug: "zweitmeinung-nephrologie",
|
||||||
|
description: "Unabhängige ärztliche Einschätzung bei Nierenerkrankungen, Dialyseempfehlung oder Transplantationsvorbereitung.",
|
||||||
|
icon: Stethoscope,
|
||||||
|
category: "Beratung",
|
||||||
|
color: "bg-emerald-50 text-emerald-600",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Gallenblase",
|
||||||
|
slug: "zweitmeinung-gallenblase",
|
||||||
|
description: "Unabhängige ärztliche Zweitmeinung vor einer geplanten Gallenblasen-OP. Ist der Eingriff wirklich notwendig?",
|
||||||
|
icon: Pill,
|
||||||
|
category: "Beratung",
|
||||||
|
color: "bg-amber-50 text-amber-600",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Schilddrüse",
|
||||||
|
slug: "zweitmeinung-schilddruese",
|
||||||
|
description: "Unabhängige ärztliche Einschätzung vor einer geplanten Schilddrüsen-OP durch erfahrene Endokrinolog:innen.",
|
||||||
|
icon: Brain,
|
||||||
|
category: "Beratung",
|
||||||
|
color: "bg-cyan-50 text-cyan-600",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export default function FachbereichePage() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* Hero */}
|
||||||
|
<section className="bg-gradient-to-br from-navy via-navy-dark to-[#001a2e] py-20">
|
||||||
|
<Container size="md">
|
||||||
|
<div className="text-center">
|
||||||
|
<p className="text-primary-light font-semibold text-sm uppercase tracking-wider mb-3">
|
||||||
|
Unsere Fachbereiche
|
||||||
|
</p>
|
||||||
|
<h1 className="text-4xl sm:text-5xl font-bold text-white mb-4">
|
||||||
|
Medizinische Zweitmeinung
|
||||||
|
</h1>
|
||||||
|
<p className="text-white/60 text-lg max-w-2xl mx-auto">
|
||||||
|
Unabhängige, fundierte Zweitmeinungen in 6 medizinischen
|
||||||
|
Fachbereichen. Von erfahrenen Fachärzt:innen – verständlich,
|
||||||
|
neutral und sicher.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Service Grid */}
|
||||||
|
<section className="py-20 bg-bg">
|
||||||
|
<Container>
|
||||||
|
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||||
|
{services.map((s) => (
|
||||||
|
<Link
|
||||||
|
key={s.slug}
|
||||||
|
href={`/fachbereiche/${s.slug}`}
|
||||||
|
className="group bg-white rounded-2xl p-8 border border-border hover:border-primary/30 hover:shadow-xl transition-all"
|
||||||
|
>
|
||||||
|
<div className="flex items-start justify-between mb-5">
|
||||||
|
<div className={`w-14 h-14 rounded-2xl ${s.color} flex items-center justify-center`}>
|
||||||
|
<s.icon className="h-7 w-7" />
|
||||||
|
</div>
|
||||||
|
{s.category === "Notfall" && (
|
||||||
|
<span className="text-xs font-bold bg-red-100 text-red-700 px-3 py-1 rounded-full">
|
||||||
|
24/7 Notfall
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<h2 className="text-xl font-bold mb-3 text-navy group-hover:text-primary transition-colors">
|
||||||
|
Zweitmeinung {s.title}
|
||||||
|
</h2>
|
||||||
|
<p className="text-text-muted text-sm leading-relaxed mb-6">
|
||||||
|
{s.description}
|
||||||
|
</p>
|
||||||
|
<span className="inline-flex items-center gap-1 text-sm font-semibold text-primary group-hover:gap-2 transition-all">
|
||||||
|
Mehr erfahren
|
||||||
|
<ArrowRight className="h-4 w-4" />
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* CTA */}
|
||||||
|
<section className="py-16 bg-white">
|
||||||
|
<Container size="md">
|
||||||
|
<div className="text-center">
|
||||||
|
<h2 className="text-2xl sm:text-3xl font-bold text-navy mb-4">
|
||||||
|
Sie wissen nicht, welcher Fachbereich?
|
||||||
|
</h2>
|
||||||
|
<p className="text-text-muted mb-8 max-w-xl mx-auto">
|
||||||
|
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">
|
||||||
|
<Phone className="h-4 w-4" />
|
||||||
|
0800 80 44 100 (kostenlos)
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</section>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
119
src/app/faq/page.tsx
Normal file
119
src/app/faq/page.tsx
Normal file
|
|
@ -0,0 +1,119 @@
|
||||||
|
import type { Metadata } from "next"
|
||||||
|
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"
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "FAQ - Häufige Fragen",
|
||||||
|
description:
|
||||||
|
"Antworten auf die wichtigsten Fragen zur medizinischen Zweitmeinung. Von Onkologie über Kardiologie bis Intensivmedizin.",
|
||||||
|
}
|
||||||
|
|
||||||
|
const categories = [
|
||||||
|
{ name: "Allgemeine Fragen", slug: "allgemein" },
|
||||||
|
{ name: "Intensivmedizin", slug: "intensivmedizin" },
|
||||||
|
{ name: "Kardiologie", slug: "kardiologie" },
|
||||||
|
{ name: "Onkologie", slug: "onkologie" },
|
||||||
|
{ name: "Nephrologie", slug: "nephrologie" },
|
||||||
|
{ name: "Gallenblase", slug: "gallenblase" },
|
||||||
|
{ name: "Schilddrüse", slug: "schilddruese" },
|
||||||
|
]
|
||||||
|
|
||||||
|
const faqData = [
|
||||||
|
{ question: "Was bringt mir eine Zweitmeinung bei Krebs?", answer: "Eine Zweitmeinung kann Ihnen Sicherheit geben - vor allem bei schweren Diagnosen oder belastenden Therapien. Sie hilft, Behandlungsoptionen besser zu verstehen, Alternativen zu erkennen und eine informierte Entscheidung zu treffen.", category: "allgemein" },
|
||||||
|
{ question: "Wie läuft das Zweitmeinungsverfahren ab?", answer: "Nach einem telefonischen Vorgespräch prüfen unsere Fachärzt:innen Ihre Unterlagen. Anschließend erhalten Sie ein schriftliches Gutachten mit einer klaren, medizinisch fundierten Empfehlung. Wenn gewünscht, besprechen wir das Ergebnis zusätzlich persönlich mit Ihnen.", category: "allgemein" },
|
||||||
|
{ question: "Muss ich alle meine Unterlagen selbst zusammensuchen?", answer: "Nein. Unsere Case Manager:innen unterstützen Sie bei der Zusammenstellung aller relevanten medizinischen Unterlagen. Sie müssen lediglich eine Schweigepflichtentbindung unterschreiben.", category: "allgemein" },
|
||||||
|
{ question: "Kann ich die Zweitmeinung auch einholen, wenn die Therapie schon begonnen hat?", answer: "Ja, auch während einer laufenden Behandlung kann eine Zweitmeinung sinnvoll sein - z.B. um die Therapie zu überprüfen oder Alternativen zu bewerten.", category: "allgemein" },
|
||||||
|
{ question: "Beeinflusst die Zweitmeinung meine Behandlung?", answer: "Die Zweitmeinung ist eine unabhängige Empfehlung. Sie ersetzt nicht die Behandlung durch Ihre Ärzt:innen, sondern ergänzt sie. Die Entscheidung liegt immer bei Ihnen.", category: "allgemein" },
|
||||||
|
{ question: "Wann ist eine Zweitmeinung vor einem Herzkatheter sinnvoll?", answer: "Immer dann, wenn ein planbarer Eingriff wie eine PCI (Stent) oder eine OP empfohlen wurde. Auch bei Unsicherheit über Nutzen und Risiken ist eine Zweitmeinung sinnvoll.", category: "kardiologie" },
|
||||||
|
{ question: "Wer erstellt die kardiologische Zweitmeinung?", answer: "Ausschließlich erfahrene, unabhängige Fachärzt:innen für Kardiologie mit Leitlinienkompetenz und klinischer Erfahrung.", category: "kardiologie" },
|
||||||
|
{ question: "Welche Unterlagen brauche ich für die kardiologische Zweitmeinung?", answer: "Ideal sind aktuelle Befunde, EKGs, Herzkatheterberichte, Medikamente und ggf. OP-Empfehlungen. Wir helfen Ihnen gern bei der Beschaffung und Sichtung der Unterlagen.", category: "kardiologie" },
|
||||||
|
{ question: "Muss ich den Eingriff absagen, wenn ich eine Zweitmeinung einhole?", answer: "Nein. Die Zweitmeinung führt Ihren bestehenden Entscheidungsprozess nicht in Verzug. Sie entscheiden selbst, wie Sie mit dem Ergebnis umgehen.", category: "kardiologie" },
|
||||||
|
{ question: "Was passiert, wenn die Einschätzung abweicht?", answer: "Dann besprechen wir mit Ihnen nachvollziehbar, warum das so ist - z.B. weil die Leitlinien einen anderen Weg vorsehen oder weil Ihre individuelle Situation neu bewertet wurde.", category: "kardiologie" },
|
||||||
|
{ question: "Wann ist eine Zweitmeinung zur Schilddrüsen-OP sinnvoll?", answer: "Bei empfohlener OP wegen Knoten, Struma, Autonomie oder unklarer Laborwerte.", category: "schilddruese" },
|
||||||
|
{ question: "Welche Unterlagen werden für die Schilddrüsen-Zweitmeinung benötigt?", answer: "Ultraschallbefunde, Szintigramme, Laborwerte (TSH, fT3, fT4) und ggf. Feinnadelpunktionsergebnisse.", category: "schilddruese" },
|
||||||
|
{ question: "Wer erstellt die Schilddrüsen-Zweitmeinung?", answer: "Fachärzt:innen für Endokrinologie oder Schilddrüsenchirurgie mit langjähriger Erfahrung.", category: "schilddruese" },
|
||||||
|
{ question: "Ist die Zweitmeinung verbindlich?", answer: "Nein, die Zweitmeinung ist eine unabhängige Empfehlung. Sie sind nicht verpflichtet, ihr zu folgen.", category: "schilddruese" },
|
||||||
|
{ question: "Kostet die Schilddrüsen-Zweitmeinung etwas?", answer: "Für gesetzlich Versicherte ist die Zweitmeinung in vielen Fällen kostenfrei. Sprechen Sie uns an.", category: "schilddruese" },
|
||||||
|
{ question: "Wann ist eine Zweitmeinung zur Gallenblasenentfernung sinnvoll?", answer: "Wenn eine Cholezystektomie empfohlen wurde und Sie unsicher sind, ob die OP tatsächlich notwendig ist.", category: "gallenblase" },
|
||||||
|
{ question: "Wer erstellt die Gallenblase-Zweitmeinung?", answer: "Fachärzt:innen für Viszeralchirurgie oder Gastroenterologie.", category: "gallenblase" },
|
||||||
|
{ question: "Welche Unterlagen brauche ich für die Gallenblase-Zweitmeinung?", answer: "Ultraschallbefunde, Laborbefunde und die OP-Empfehlung Ihres behandelnden Arztes.", category: "gallenblase" },
|
||||||
|
{ question: "Gibt es Alternativen zur Gallenblasen-OP?", answer: "In manchen Fällen ja - etwa medikamentöse Therapie oder abwartendes Beobachten. Das hängt von Ihren individuellen Befunden ab.", category: "gallenblase" },
|
||||||
|
{ question: "Was kostet die Gallenblase-Zweitmeinung?", answer: "Sprechen Sie uns an - in vielen Fällen übernimmt Ihre Krankenkasse die Kosten.", category: "gallenblase" },
|
||||||
|
{ question: "Wann ist eine Zweitmeinung bei Nierenerkrankungen sinnvoll?", answer: "Bei Diagnose einer chronischen Niereninsuffizienz, Empfehlung zur Dialyse oder vor einer Transplantation.", category: "nephrologie" },
|
||||||
|
{ question: "Welche Unterlagen sind für die nephrologische Zweitmeinung wichtig?", answer: "Laborwerte (Kreatinin, GFR, Harnstoff), Urinbefunde, Ultraschall und bisherige Behandlungsdokumentation.", category: "nephrologie" },
|
||||||
|
{ question: "Kann ich die Zweitmeinung auch einholen, wenn die Dialyse bereits begonnen hat?", answer: "Ja, auch bei laufender Dialyse kann eine Zweitmeinung sinnvoll sein - etwa zur Prüfung von Therapiealternativen.", category: "nephrologie" },
|
||||||
|
{ question: "Kostet die nephrologische Zweitmeinung etwas?", answer: "In vielen Fällen wird die Zweitmeinung von Ihrer Krankenkasse übernommen. Wir beraten Sie gerne.", category: "nephrologie" },
|
||||||
|
]
|
||||||
|
|
||||||
|
export default function FAQPage() {
|
||||||
|
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">
|
||||||
|
FAQ - Häufige Fragen
|
||||||
|
</h1>
|
||||||
|
<p className="text-white/60 text-lg max-w-2xl mx-auto">
|
||||||
|
Antworten auf die wichtigsten Fragen zur medizinischen Zweitmeinung
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* FAQ Content (Client Component for search/filter) */}
|
||||||
|
<FAQClient categories={categories} faqs={faqData} />
|
||||||
|
|
||||||
|
{/* CTA */}
|
||||||
|
<section className="py-20 bg-navy">
|
||||||
|
<Container size="sm">
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="inline-flex items-center justify-center w-14 h-14 rounded-2xl bg-gold/20 text-gold mb-6">
|
||||||
|
<HelpCircle className="h-7 w-7" />
|
||||||
|
</div>
|
||||||
|
<h2 className="text-2xl sm:text-3xl font-bold text-white mb-4">
|
||||||
|
Ihre Frage nicht dabei?
|
||||||
|
</h2>
|
||||||
|
<p className="text-white/60 mb-8 max-w-xl mx-auto">
|
||||||
|
Unsere Experten beantworten gerne Ihre individuellen Fragen zur
|
||||||
|
medizinischen Zweitmeinung. Kontaktieren Sie uns für eine
|
||||||
|
kostenlose Erstberatung.
|
||||||
|
</p>
|
||||||
|
<div className="flex flex-wrap justify-center gap-4">
|
||||||
|
<Button href="tel:+4980080441000" variant="gold" size="lg">
|
||||||
|
<Phone className="h-4 w-4" />
|
||||||
|
Jetzt anrufen
|
||||||
|
</Button>
|
||||||
|
<Button href="/kontakt" variant="outline" size="lg">
|
||||||
|
<Mail className="h-4 w-4" />
|
||||||
|
E-Mail schreiben
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<p className="text-white/40 text-sm mt-6">
|
||||||
|
Kostenlose Hotline • Mo-Fr 9:00-16:00 Uhr • Notfall 24/7
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Schema.org FAQ structured data */}
|
||||||
|
<script
|
||||||
|
type="application/ld+json"
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: JSON.stringify({
|
||||||
|
"@context": "https://schema.org",
|
||||||
|
"@type": "FAQPage",
|
||||||
|
mainEntity: faqData.map((faq) => ({
|
||||||
|
"@type": "Question",
|
||||||
|
name: faq.question,
|
||||||
|
acceptedAnswer: { "@type": "Answer", text: faq.answer },
|
||||||
|
})),
|
||||||
|
}),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
BIN
src/app/favicon.ico
Normal file
BIN
src/app/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
91
src/app/globals.css
Normal file
91
src/app/globals.css
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
/* Google Font Import — must precede @import "tailwindcss" */
|
||||||
|
@import url('https://fonts.googleapis.com/css2?family=Roboto+Condensed:ital,wght@0,300;0,400;0,500;0,600;0,700;1,300;1,400&display=swap');
|
||||||
|
|
||||||
|
@import "tailwindcss";
|
||||||
|
|
||||||
|
@theme {
|
||||||
|
/* Brand Colors */
|
||||||
|
--color-navy: #004166;
|
||||||
|
--color-navy-light: #005580;
|
||||||
|
--color-navy-dark: #003050;
|
||||||
|
--color-primary: #1278B3;
|
||||||
|
--color-primary-light: #1590D6;
|
||||||
|
--color-primary-dark: #0E5F8F;
|
||||||
|
--color-gold: #B3AF09;
|
||||||
|
--color-gold-hover: #8F8C07;
|
||||||
|
--color-gold-light: #D4D10A;
|
||||||
|
--color-accent: #0091BD;
|
||||||
|
--color-bg: #f0f1f4;
|
||||||
|
--color-bg-white: #ffffff;
|
||||||
|
--color-text: #1a1a2e;
|
||||||
|
--color-text-muted: #64748b;
|
||||||
|
--color-text-light: #94a3b8;
|
||||||
|
--color-border: #e2e8f0;
|
||||||
|
--color-border-light: #f1f5f9;
|
||||||
|
|
||||||
|
/* Typography */
|
||||||
|
--font-family-heading: "Roboto Condensed", "Arial Narrow", Arial, sans-serif;
|
||||||
|
--font-family-body: "Roboto Condensed", "Arial Narrow", Arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Base styles */
|
||||||
|
html {
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: var(--font-family-body);
|
||||||
|
color: var(--color-text);
|
||||||
|
background-color: var(--color-bg);
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Headings */
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
font-family: var(--font-family-heading);
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Pulse animation for emergency CTA */
|
||||||
|
@keyframes pulse-gold {
|
||||||
|
0%, 100% {
|
||||||
|
box-shadow: 0 0 0 0 rgba(179, 175, 9, 0.4);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
box-shadow: 0 0 0 12px rgba(179, 175, 9, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-pulse-gold {
|
||||||
|
animation: pulse-gold 2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fade-in animation for scroll-triggered sections */
|
||||||
|
@keyframes fade-in-up {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(24px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-fade-in-up {
|
||||||
|
animation: fade-in-up 0.6s ease-out forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Focus styles for accessibility */
|
||||||
|
:focus-visible {
|
||||||
|
outline: 2px solid var(--color-primary);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Selection color */
|
||||||
|
::selection {
|
||||||
|
background-color: var(--color-primary);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
96
src/app/impressum/page.tsx
Normal file
96
src/app/impressum/page.tsx
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
import type { Metadata } from "next"
|
||||||
|
import { Container } from "@/components/ui/Container"
|
||||||
|
|
||||||
|
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>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
)
|
||||||
|
}
|
||||||
115
src/app/kontakt/page.tsx
Normal file
115
src/app/kontakt/page.tsx
Normal file
|
|
@ -0,0 +1,115 @@
|
||||||
|
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"
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Kontakt",
|
||||||
|
description: "Kontaktieren Sie uns für eine kostenlose Erstberatung. Telefon: 0800 80 44 100.",
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function KontaktPage() {
|
||||||
|
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">
|
||||||
|
Kontakt
|
||||||
|
</h1>
|
||||||
|
<p className="text-white/60 text-lg">
|
||||||
|
Wir sind für Sie da – persönlich, kompetent und vertraulich.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Contact Content */}
|
||||||
|
<section className="py-20 bg-bg">
|
||||||
|
<Container>
|
||||||
|
<div className="grid lg:grid-cols-5 gap-12">
|
||||||
|
{/* Left: Contact Info */}
|
||||||
|
<div className="lg:col-span-2 space-y-8">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-xl font-bold text-navy mb-6">
|
||||||
|
So erreichen Sie uns
|
||||||
|
</h2>
|
||||||
|
<div className="space-y-5">
|
||||||
|
<a
|
||||||
|
href="tel:+4980080441000"
|
||||||
|
className="flex items-start gap-4 group"
|
||||||
|
>
|
||||||
|
<div className="w-10 h-10 rounded-lg bg-primary/10 flex items-center justify-center shrink-0 group-hover:bg-primary group-hover:text-white transition-colors">
|
||||||
|
<Phone className="h-5 w-5 text-primary group-hover:text-white" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="font-semibold text-navy">Telefon (kostenlos)</div>
|
||||||
|
<div className="text-primary font-bold">0800 80 44 100</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<div className="flex items-start gap-4">
|
||||||
|
<div className="w-10 h-10 rounded-lg bg-primary/10 flex items-center justify-center shrink-0">
|
||||||
|
<Printer className="h-5 w-5 text-primary" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="font-semibold text-navy">Telefax</div>
|
||||||
|
<div className="text-text-muted">0800 80 44 190</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<a
|
||||||
|
href="mailto:kontakt@zweitmeinu.ng"
|
||||||
|
className="flex items-start gap-4 group"
|
||||||
|
>
|
||||||
|
<div className="w-10 h-10 rounded-lg bg-primary/10 flex items-center justify-center shrink-0 group-hover:bg-primary group-hover:text-white transition-colors">
|
||||||
|
<Mail className="h-5 w-5 text-primary group-hover:text-white" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="font-semibold text-navy">E-Mail</div>
|
||||||
|
<div className="text-primary">kontakt@zweitmeinu.ng</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<div className="flex items-start gap-4">
|
||||||
|
<div className="w-10 h-10 rounded-lg bg-primary/10 flex items-center justify-center shrink-0">
|
||||||
|
<MapPin className="h-5 w-5 text-primary" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="font-semibold text-navy">Adresse</div>
|
||||||
|
<div className="text-text-muted text-sm">
|
||||||
|
complex care solutions GmbH<br />
|
||||||
|
Hans-Böckler-Str. 19<br />
|
||||||
|
46236 Bottrop
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-start gap-4">
|
||||||
|
<div className="w-10 h-10 rounded-lg bg-primary/10 flex items-center justify-center shrink-0">
|
||||||
|
<Clock className="h-5 w-5 text-primary" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="font-semibold text-navy">Öffnungszeiten</div>
|
||||||
|
<div className="text-text-muted text-sm">
|
||||||
|
Mo – Fr: 09:00 – 16:00 Uhr<br />
|
||||||
|
<span className="text-gold font-semibold">Notfall-Hotline: 24/7</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Right: Contact Form */}
|
||||||
|
<div className="lg:col-span-3">
|
||||||
|
<div className="bg-white rounded-2xl p-6 sm:p-8 border border-border shadow-sm">
|
||||||
|
<h2 className="text-xl font-bold text-navy mb-6">
|
||||||
|
Nachricht senden
|
||||||
|
</h2>
|
||||||
|
<ContactForm />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</section>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
55
src/app/layout.tsx
Normal file
55
src/app/layout.tsx
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
import type { Metadata } from "next"
|
||||||
|
import { TopBar, Header, Footer, EmergencyBanner } from "@/components/layout"
|
||||||
|
import { getNavigation, getSiteSettings, getSocialLinks } from "@/lib/api"
|
||||||
|
import "./globals.css"
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: {
|
||||||
|
default: "zweitmeinu.ng – Medizinische Zweitmeinung",
|
||||||
|
template: "%s | zweitmeinu.ng",
|
||||||
|
},
|
||||||
|
description:
|
||||||
|
"Ihr zentrales Portal für qualifizierte medizinische Zweitmeinungen. Zugang zu erfahrenen Fachärzt:innen aus über 50 Fachbereichen.",
|
||||||
|
metadataBase: new URL(
|
||||||
|
process.env.NEXT_PUBLIC_SITE_URL || "https://zweitmeinu.ng",
|
||||||
|
),
|
||||||
|
openGraph: {
|
||||||
|
type: "website",
|
||||||
|
locale: "de_DE",
|
||||||
|
siteName: "zweitmeinu.ng",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function RootLayout({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode
|
||||||
|
}) {
|
||||||
|
const [navigation, settings, socialLinks] = await Promise.all([
|
||||||
|
getNavigation(),
|
||||||
|
getSiteSettings(),
|
||||||
|
getSocialLinks(),
|
||||||
|
])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<html lang="de">
|
||||||
|
<body className="font-body antialiased">
|
||||||
|
<a
|
||||||
|
href="#main-content"
|
||||||
|
className="sr-only focus:not-sr-only focus:fixed focus:top-4 focus:left-4 focus:z-[200] focus:bg-white focus:px-4 focus:py-2 focus:rounded-lg focus:shadow-lg"
|
||||||
|
>
|
||||||
|
Zum Hauptinhalt springen
|
||||||
|
</a>
|
||||||
|
<div className="flex min-h-screen flex-col">
|
||||||
|
<TopBar />
|
||||||
|
<Header mainMenu={navigation?.mainMenu ?? null} />
|
||||||
|
<main id="main-content" className="flex-1">
|
||||||
|
{children}
|
||||||
|
</main>
|
||||||
|
<EmergencyBanner />
|
||||||
|
<Footer socialLinks={socialLinks} />
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
)
|
||||||
|
}
|
||||||
165
src/app/motivation/page.tsx
Normal file
165
src/app/motivation/page.tsx
Normal file
|
|
@ -0,0 +1,165 @@
|
||||||
|
import type { Metadata } from "next"
|
||||||
|
import { Container } from "@/components/ui/Container"
|
||||||
|
import { Heart, Shield, Eye, Users, Lightbulb, Scale } from "lucide-react"
|
||||||
|
|
||||||
|
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 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>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
15
src/app/page.tsx
Normal file
15
src/app/page.tsx
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { HeroSection } from "@/components/home/HeroSection"
|
||||||
|
import { WhySection } from "@/components/home/WhySection"
|
||||||
|
import { ServiceOverview } from "@/components/home/ServiceOverview"
|
||||||
|
import { HomeCTA } from "@/components/home/HomeCTA"
|
||||||
|
|
||||||
|
export default function HomePage() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<HeroSection />
|
||||||
|
<WhySection />
|
||||||
|
<ServiceOverview />
|
||||||
|
<HomeCTA />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
121
src/app/so-funktionierts/page.tsx
Normal file
121
src/app/so-funktionierts/page.tsx
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
import type { Metadata } from "next"
|
||||||
|
import { Container } from "@/components/ui/Container"
|
||||||
|
import { Button } from "@/components/ui/Button"
|
||||||
|
import {
|
||||||
|
Phone, FileText, UserCheck, ClipboardCheck,
|
||||||
|
MessageSquare, HeartHandshake, Shield, Clock,
|
||||||
|
BadgeCheck, Users, ArrowRight,
|
||||||
|
} from "lucide-react"
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "So funktioniert's",
|
||||||
|
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 },
|
||||||
|
]
|
||||||
|
|
||||||
|
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 function SoFunktionierts() {
|
||||||
|
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="tel:+4980080441000" variant="gold" size="lg">
|
||||||
|
<Phone className="h-4 w-4" />
|
||||||
|
0800 80 44 100
|
||||||
|
</Button>
|
||||||
|
<Button href="/kontakt" variant="secondary" size="lg">
|
||||||
|
Kontaktformular
|
||||||
|
<ArrowRight className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</section>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
129
src/app/ueber-uns/page.tsx
Normal file
129
src/app/ueber-uns/page.tsx
Normal file
|
|
@ -0,0 +1,129 @@
|
||||||
|
import type { Metadata } from "next"
|
||||||
|
import { Container } from "@/components/ui/Container"
|
||||||
|
import { Button } from "@/components/ui/Button"
|
||||||
|
import {
|
||||||
|
Shield, Award, Users, Heart, Phone,
|
||||||
|
CheckCircle, Building, Globe,
|
||||||
|
} from "lucide-react"
|
||||||
|
|
||||||
|
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 function UeberUnsPage() {
|
||||||
|
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: 0800 80 44 100</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="tel:+4980080441000" variant="gold" size="lg">
|
||||||
|
<Phone className="h-4 w-4" />
|
||||||
|
0800 80 44 100 (kostenlos)
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</section>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
203
src/components/contact/ContactForm.tsx
Normal file
203
src/components/contact/ContactForm.tsx
Normal file
|
|
@ -0,0 +1,203 @@
|
||||||
|
"use client"
|
||||||
|
|
||||||
|
import { useState } from "react"
|
||||||
|
import { Send, Loader2, CheckCircle } from "lucide-react"
|
||||||
|
|
||||||
|
const PAYLOAD_URL = process.env.NEXT_PUBLIC_PAYLOAD_URL || "https://cms.c2sgmbh.de"
|
||||||
|
|
||||||
|
export function ContactForm() {
|
||||||
|
const [form, setForm] = useState({
|
||||||
|
name: "",
|
||||||
|
email: "",
|
||||||
|
phone: "",
|
||||||
|
subject: "",
|
||||||
|
urgency: "normal",
|
||||||
|
message: "",
|
||||||
|
})
|
||||||
|
const [status, setStatus] = useState<"idle" | "sending" | "success" | "error">("idle")
|
||||||
|
const [errorMsg, setErrorMsg] = useState("")
|
||||||
|
|
||||||
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
|
e.preventDefault()
|
||||||
|
setStatus("sending")
|
||||||
|
setErrorMsg("")
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${PAYLOAD_URL}/api/form-submissions`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({
|
||||||
|
form: 1,
|
||||||
|
submissionData: [
|
||||||
|
{ field: "name", value: form.name },
|
||||||
|
{ field: "email", value: form.email },
|
||||||
|
{ field: "phone", value: form.phone },
|
||||||
|
{ field: "subject", value: form.subject },
|
||||||
|
{ field: "urgency", value: form.urgency },
|
||||||
|
{ field: "message", value: form.message },
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
setStatus("success")
|
||||||
|
setForm({ name: "", email: "", phone: "", subject: "", urgency: "normal", message: "" })
|
||||||
|
} else {
|
||||||
|
setStatus("error")
|
||||||
|
setErrorMsg("Beim Senden ist ein Fehler aufgetreten. Bitte versuchen Sie es erneut.")
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
setStatus("error")
|
||||||
|
setErrorMsg("Verbindungsfehler. Bitte versuchen Sie es später erneut.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status === "success") {
|
||||||
|
return (
|
||||||
|
<div className="text-center py-12">
|
||||||
|
<CheckCircle className="h-16 w-16 text-green-500 mx-auto mb-4" />
|
||||||
|
<h3 className="text-xl font-bold text-navy mb-2">Nachricht gesendet</h3>
|
||||||
|
<p className="text-text-muted">
|
||||||
|
Vielen Dank für Ihre Nachricht. Wir melden uns schnellstmöglich bei Ihnen.
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
onClick={() => setStatus("idle")}
|
||||||
|
className="mt-6 text-sm text-primary hover:underline"
|
||||||
|
>
|
||||||
|
Weitere Nachricht senden
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={handleSubmit} className="space-y-5">
|
||||||
|
<div className="grid sm:grid-cols-2 gap-5">
|
||||||
|
<div>
|
||||||
|
<label htmlFor="name" className="block text-sm font-medium text-navy mb-1.5">
|
||||||
|
Name *
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="name"
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
value={form.name}
|
||||||
|
onChange={(e) => setForm({ ...form, name: e.target.value })}
|
||||||
|
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"
|
||||||
|
placeholder="Ihr vollständiger Name"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label htmlFor="email" className="block text-sm font-medium text-navy mb-1.5">
|
||||||
|
E-Mail *
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="email"
|
||||||
|
type="email"
|
||||||
|
required
|
||||||
|
value={form.email}
|
||||||
|
onChange={(e) => setForm({ ...form, email: e.target.value })}
|
||||||
|
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"
|
||||||
|
placeholder="ihre@email.de"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid sm:grid-cols-2 gap-5">
|
||||||
|
<div>
|
||||||
|
<label htmlFor="phone" className="block text-sm font-medium text-navy mb-1.5">
|
||||||
|
Telefon
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="phone"
|
||||||
|
type="tel"
|
||||||
|
value={form.phone}
|
||||||
|
onChange={(e) => setForm({ ...form, phone: e.target.value })}
|
||||||
|
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"
|
||||||
|
placeholder="+49 ..."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label htmlFor="subject" className="block text-sm font-medium text-navy mb-1.5">
|
||||||
|
Fachbereich *
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
id="subject"
|
||||||
|
required
|
||||||
|
value={form.subject}
|
||||||
|
onChange={(e) => setForm({ ...form, subject: e.target.value })}
|
||||||
|
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>
|
||||||
|
<option value="sonstiges">Sonstiges</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label htmlFor="urgency" className="block text-sm font-medium text-navy mb-1.5">
|
||||||
|
Dringlichkeit
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
id="urgency"
|
||||||
|
value={form.urgency}
|
||||||
|
onChange={(e) => setForm({ ...form, urgency: e.target.value })}
|
||||||
|
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="normal">Normal</option>
|
||||||
|
<option value="dringend">Dringend (innerhalb 48h)</option>
|
||||||
|
<option value="notfall">Notfall (sofort)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label htmlFor="message" className="block text-sm font-medium text-navy mb-1.5">
|
||||||
|
Nachricht *
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
id="message"
|
||||||
|
required
|
||||||
|
rows={5}
|
||||||
|
value={form.message}
|
||||||
|
onChange={(e) => setForm({ ...form, message: e.target.value })}
|
||||||
|
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 resize-y"
|
||||||
|
placeholder="Beschreiben Sie Ihr Anliegen..."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{status === "error" && (
|
||||||
|
<div className="text-sm text-red-600 bg-red-50 px-4 py-3 rounded-lg">
|
||||||
|
{errorMsg}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
disabled={status === "sending"}
|
||||||
|
className="w-full sm:w-auto inline-flex items-center justify-center gap-2 bg-primary hover:bg-primary-dark text-white font-semibold px-8 py-3 rounded-lg transition-colors disabled:opacity-50"
|
||||||
|
>
|
||||||
|
{status === "sending" ? (
|
||||||
|
<>
|
||||||
|
<Loader2 className="h-4 w-4 animate-spin" />
|
||||||
|
Wird gesendet...
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Send className="h-4 w-4" />
|
||||||
|
Nachricht senden
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<p className="text-xs text-text-light">
|
||||||
|
* Pflichtfelder. Ihre Daten werden vertraulich behandelt und nicht an Dritte weitergegeben.
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
)
|
||||||
|
}
|
||||||
161
src/components/faq/FAQClient.tsx
Normal file
161
src/components/faq/FAQClient.tsx
Normal file
|
|
@ -0,0 +1,161 @@
|
||||||
|
"use client"
|
||||||
|
|
||||||
|
import { useState, useMemo } from "react"
|
||||||
|
import { Search, ChevronDown } from "lucide-react"
|
||||||
|
import { Container } from "@/components/ui/Container"
|
||||||
|
|
||||||
|
interface FAQ {
|
||||||
|
question: string
|
||||||
|
answer: string
|
||||||
|
category: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Category {
|
||||||
|
name: string
|
||||||
|
slug: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FAQClientProps {
|
||||||
|
categories: Category[]
|
||||||
|
faqs: FAQ[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function FAQClient({ categories, faqs }: FAQClientProps) {
|
||||||
|
const [search, setSearch] = useState("")
|
||||||
|
const [activeCategory, setActiveCategory] = useState<string | null>(null)
|
||||||
|
const [openItems, setOpenItems] = useState<Set<number>>(new Set())
|
||||||
|
|
||||||
|
const filtered = useMemo(() => {
|
||||||
|
let items = faqs
|
||||||
|
if (activeCategory) {
|
||||||
|
items = items.filter((f) => f.category === activeCategory)
|
||||||
|
}
|
||||||
|
if (search.trim()) {
|
||||||
|
const q = search.toLowerCase()
|
||||||
|
items = items.filter(
|
||||||
|
(f) =>
|
||||||
|
f.question.toLowerCase().includes(q) ||
|
||||||
|
f.answer.toLowerCase().includes(q),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return items
|
||||||
|
}, [faqs, search, activeCategory])
|
||||||
|
|
||||||
|
const toggle = (i: number) => {
|
||||||
|
setOpenItems((prev) => {
|
||||||
|
const next = new Set(prev)
|
||||||
|
if (next.has(i)) next.delete(i)
|
||||||
|
else next.add(i)
|
||||||
|
return next
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const faqCountByCategory = useMemo(() => {
|
||||||
|
const map: Record<string, number> = {}
|
||||||
|
for (const f of faqs) {
|
||||||
|
map[f.category] = (map[f.category] || 0) + 1
|
||||||
|
}
|
||||||
|
return map
|
||||||
|
}, [faqs])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* Search */}
|
||||||
|
<section className="py-8 bg-white border-b border-border">
|
||||||
|
<Container size="md">
|
||||||
|
<div className="relative max-w-xl mx-auto">
|
||||||
|
<Search className="absolute left-4 top-1/2 -translate-y-1/2 h-5 w-5 text-text-muted" />
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={search}
|
||||||
|
onChange={(e) => setSearch(e.target.value)}
|
||||||
|
placeholder="Suchen Sie nach Stichworten oder Fragen..."
|
||||||
|
className="w-full pl-12 pr-4 py-3.5 rounded-xl border border-border bg-bg focus:outline-none focus:ring-2 focus:ring-primary/30 focus:border-primary text-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Categories */}
|
||||||
|
<section className="py-12 bg-bg">
|
||||||
|
<Container>
|
||||||
|
<p className="text-center text-text-muted text-sm mb-6">
|
||||||
|
Wählen Sie Ihren Fachbereich für spezifische Antworten
|
||||||
|
</p>
|
||||||
|
<div className="flex flex-wrap justify-center gap-3">
|
||||||
|
<button
|
||||||
|
onClick={() => setActiveCategory(null)}
|
||||||
|
className={`px-4 py-2 rounded-lg text-sm font-medium transition-colors ${
|
||||||
|
activeCategory === null
|
||||||
|
? "bg-primary text-white"
|
||||||
|
: "bg-white text-text-muted hover:bg-gray-50 border border-border"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
Alle ({faqs.length})
|
||||||
|
</button>
|
||||||
|
{categories.map((cat) => (
|
||||||
|
<button
|
||||||
|
key={cat.slug}
|
||||||
|
onClick={() =>
|
||||||
|
setActiveCategory(activeCategory === cat.slug ? null : cat.slug)
|
||||||
|
}
|
||||||
|
className={`px-4 py-2 rounded-lg text-sm font-medium transition-colors ${
|
||||||
|
activeCategory === cat.slug
|
||||||
|
? "bg-primary text-white"
|
||||||
|
: "bg-white text-text-muted hover:bg-gray-50 border border-border"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{cat.name} ({faqCountByCategory[cat.slug] || 0})
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* FAQ Accordion */}
|
||||||
|
<section className="py-12 bg-white">
|
||||||
|
<Container size="md">
|
||||||
|
{filtered.length === 0 ? (
|
||||||
|
<p className="text-center text-text-muted py-12">
|
||||||
|
Keine Ergebnisse gefunden. Versuchen Sie es mit anderen
|
||||||
|
Suchbegriffen oder kontaktieren Sie uns direkt.
|
||||||
|
</p>
|
||||||
|
) : (
|
||||||
|
<div className="space-y-3">
|
||||||
|
{filtered.map((faq, i) => (
|
||||||
|
<div
|
||||||
|
key={i}
|
||||||
|
className="border border-border rounded-xl overflow-hidden"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
onClick={() => toggle(i)}
|
||||||
|
className="w-full flex items-center justify-between px-6 py-4 text-left hover:bg-gray-50 transition-colors"
|
||||||
|
>
|
||||||
|
<span className="font-semibold text-navy pr-4">
|
||||||
|
{faq.question}
|
||||||
|
</span>
|
||||||
|
<ChevronDown
|
||||||
|
className={`h-5 w-5 text-text-muted shrink-0 transition-transform ${
|
||||||
|
openItems.has(i) ? "rotate-180" : ""
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
{openItems.has(i) && (
|
||||||
|
<div className="px-6 pb-5 text-text-muted text-sm leading-relaxed border-t border-border pt-4">
|
||||||
|
{faq.answer}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{filtered.length > 0 && (
|
||||||
|
<p className="text-center text-text-light text-sm mt-6">
|
||||||
|
{filtered.length} Frage{filtered.length !== 1 ? "n" : ""} angezeigt
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</Container>
|
||||||
|
</section>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
113
src/components/home/HeroSection.tsx
Normal file
113
src/components/home/HeroSection.tsx
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
"use client"
|
||||||
|
|
||||||
|
import { useState, useEffect, useCallback } from "react"
|
||||||
|
import { Phone, ArrowRight, ChevronLeft, ChevronRight } from "lucide-react"
|
||||||
|
import { Button } from "@/components/ui/Button"
|
||||||
|
|
||||||
|
const slides = [
|
||||||
|
{
|
||||||
|
badge: "Medizinische Zweitmeinung",
|
||||||
|
title: "Sicherheit durch\nunabhängige Expertise",
|
||||||
|
subtitle:
|
||||||
|
"Fundierte ärztliche Zweitmeinungen für die wichtigsten Entscheidungen Ihres Lebens.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
badge: "Über 50 Fachbereiche",
|
||||||
|
title: "Kompetenz in\nallen Disziplinen",
|
||||||
|
subtitle:
|
||||||
|
"Von Kardiologie bis Onkologie – erfahrene Fachärzt:innen bewerten Ihren Fall unabhängig.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
badge: "Schnell & Vertraulich",
|
||||||
|
title: "Ihre Gesundheit\nverdient Klarheit",
|
||||||
|
subtitle:
|
||||||
|
"Innerhalb weniger Tage eine medizinisch fundierte Einschätzung. DSGVO-konform und sicher.",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export function HeroSection() {
|
||||||
|
const [current, setCurrent] = useState(0)
|
||||||
|
|
||||||
|
const next = useCallback(() => setCurrent((c) => (c + 1) % slides.length), [])
|
||||||
|
const prev = useCallback(
|
||||||
|
() => setCurrent((c) => (c - 1 + slides.length) % slides.length),
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const timer = setInterval(next, 6000)
|
||||||
|
return () => clearInterval(timer)
|
||||||
|
}, [next])
|
||||||
|
|
||||||
|
const slide = slides[current]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="relative bg-gradient-to-br from-navy via-navy-dark to-[#001a2e] overflow-hidden">
|
||||||
|
{/* Decorative gradient orbs */}
|
||||||
|
<div className="absolute top-0 right-0 w-[600px] h-[600px] bg-primary/10 rounded-full blur-3xl -translate-y-1/2 translate-x-1/3" />
|
||||||
|
<div className="absolute bottom-0 left-0 w-[400px] h-[400px] bg-accent/10 rounded-full blur-3xl translate-y-1/2 -translate-x-1/3" />
|
||||||
|
|
||||||
|
<div className="relative mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 py-24 sm:py-32 lg:py-40">
|
||||||
|
<div className="max-w-2xl">
|
||||||
|
{/* Badge */}
|
||||||
|
<div className="inline-flex items-center gap-2 bg-white/10 backdrop-blur-sm border border-white/20 rounded-full px-4 py-1.5 mb-6">
|
||||||
|
<div className="w-2 h-2 rounded-full bg-gold animate-pulse" />
|
||||||
|
<span className="text-sm text-white/90">{slide.badge}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Title */}
|
||||||
|
<h1 className="text-4xl sm:text-5xl lg:text-6xl font-bold text-white leading-[1.1] mb-6 whitespace-pre-line">
|
||||||
|
{slide.title}
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
{/* Subtitle */}
|
||||||
|
<p className="text-lg sm:text-xl text-white/70 mb-10 max-w-lg">
|
||||||
|
{slide.subtitle}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{/* CTAs */}
|
||||||
|
<div className="flex flex-wrap gap-4">
|
||||||
|
<Button href="/kontakt" variant="primary" size="lg">
|
||||||
|
<Phone className="h-4 w-4" />
|
||||||
|
Jetzt beraten lassen
|
||||||
|
</Button>
|
||||||
|
<Button href="/fachbereiche" variant="outline" size="lg">
|
||||||
|
Fachbereiche entdecken
|
||||||
|
<ArrowRight className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Slide navigation */}
|
||||||
|
<div className="flex items-center gap-4 mt-16">
|
||||||
|
<button
|
||||||
|
onClick={prev}
|
||||||
|
className="p-2 rounded-full border border-white/20 text-white/60 hover:text-white hover:border-white/40 transition-colors"
|
||||||
|
aria-label="Previous slide"
|
||||||
|
>
|
||||||
|
<ChevronLeft className="h-5 w-5" />
|
||||||
|
</button>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
{slides.map((_, i) => (
|
||||||
|
<button
|
||||||
|
key={i}
|
||||||
|
onClick={() => setCurrent(i)}
|
||||||
|
className={`h-2 rounded-full transition-all duration-300 ${
|
||||||
|
i === current ? "w-8 bg-white" : "w-2 bg-white/30"
|
||||||
|
}`}
|
||||||
|
aria-label={`Go to slide ${i + 1}`}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={next}
|
||||||
|
className="p-2 rounded-full border border-white/20 text-white/60 hover:text-white hover:border-white/40 transition-colors"
|
||||||
|
aria-label="Next slide"
|
||||||
|
>
|
||||||
|
<ChevronRight className="h-5 w-5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
34
src/components/home/HomeCTA.tsx
Normal file
34
src/components/home/HomeCTA.tsx
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { Phone, Mail } from "lucide-react"
|
||||||
|
import { Container } from "@/components/ui/Container"
|
||||||
|
import { Button } from "@/components/ui/Button"
|
||||||
|
|
||||||
|
export function HomeCTA() {
|
||||||
|
return (
|
||||||
|
<section className="py-20 bg-white">
|
||||||
|
<Container size="md">
|
||||||
|
<div className="text-center">
|
||||||
|
<p className="text-primary font-semibold text-sm uppercase tracking-wider mb-2">
|
||||||
|
Bereit für Ihre zweite Meinung?
|
||||||
|
</p>
|
||||||
|
<h2 className="text-2xl sm:text-3xl font-bold text-navy mb-4">
|
||||||
|
Kontaktieren Sie uns noch heute und erhalten Sie die medizinische
|
||||||
|
Sicherheit, die Sie verdienen.
|
||||||
|
</h2>
|
||||||
|
<div className="flex flex-wrap justify-center gap-4 mt-8">
|
||||||
|
<Button href="tel:+4980080441000" variant="gold" size="lg">
|
||||||
|
<Phone className="h-4 w-4" />
|
||||||
|
Jetzt kostenlos beraten lassen
|
||||||
|
</Button>
|
||||||
|
<Button href="/kontakt" variant="secondary" size="lg">
|
||||||
|
<Mail className="h-4 w-4" />
|
||||||
|
E-Mail schreiben
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<p className="text-text-muted text-sm mt-6">
|
||||||
|
Kostenlose Hotline • Mo-Fr 9:00-16:00 Uhr • Notfall 24/7
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
95
src/components/home/ServiceOverview.tsx
Normal file
95
src/components/home/ServiceOverview.tsx
Normal file
|
|
@ -0,0 +1,95 @@
|
||||||
|
import Link from "next/link"
|
||||||
|
import { ArrowRight, Heart, Brain, Stethoscope, Pill, FlaskConical, Activity } from "lucide-react"
|
||||||
|
import { Container } from "@/components/ui/Container"
|
||||||
|
|
||||||
|
const services = [
|
||||||
|
{
|
||||||
|
title: "Intensivmedizin",
|
||||||
|
slug: "zweitmeinung-intensivmedizin",
|
||||||
|
description: "Unabhängige Zweitmeinung bei laufender oder geplanter Intensivbehandlung.",
|
||||||
|
icon: Activity,
|
||||||
|
category: "Notfall",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Kardiologie",
|
||||||
|
slug: "zweitmeinung-kardiologie",
|
||||||
|
description: "Fundierte Empfehlung vor Herzkatheter, Stent oder kardiologischer OP.",
|
||||||
|
icon: Heart,
|
||||||
|
category: "Beratung",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Onkologie",
|
||||||
|
slug: "zweitmeinung-onkologie",
|
||||||
|
description: "Unabhängige Einschätzung bei Krebsdiagnosen und Therapieoptionen.",
|
||||||
|
icon: FlaskConical,
|
||||||
|
category: "Beratung",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Nephrologie",
|
||||||
|
slug: "zweitmeinung-nephrologie",
|
||||||
|
description: "Ärztliche Einschätzung bei Nierenerkrankungen und Dialyseempfehlung.",
|
||||||
|
icon: Stethoscope,
|
||||||
|
category: "Beratung",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Gallenblase",
|
||||||
|
slug: "zweitmeinung-gallenblase",
|
||||||
|
description: "Zweitmeinung vor geplanter Gallenblasen-OP – ist sie wirklich nötig?",
|
||||||
|
icon: Pill,
|
||||||
|
category: "Beratung",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Schilddrüse",
|
||||||
|
slug: "zweitmeinung-schilddruese",
|
||||||
|
description: "Fundierte Einschätzung vor einer geplanten Schilddrüsen-Operation.",
|
||||||
|
icon: Brain,
|
||||||
|
category: "Beratung",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export function ServiceOverview() {
|
||||||
|
return (
|
||||||
|
<section className="py-20 bg-bg">
|
||||||
|
<Container>
|
||||||
|
<div className="text-center mb-14">
|
||||||
|
<p className="text-primary font-semibold text-sm uppercase tracking-wider mb-2">
|
||||||
|
Unsere Fachbereiche
|
||||||
|
</p>
|
||||||
|
<h2 className="text-3xl sm:text-4xl font-bold text-navy">
|
||||||
|
Zweitmeinung in 6 Fachbereichen
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
|
{services.map((s) => (
|
||||||
|
<Link
|
||||||
|
key={s.slug}
|
||||||
|
href={`/fachbereiche/${s.slug}`}
|
||||||
|
className="group bg-white rounded-xl p-6 border border-border hover:border-primary/30 hover:shadow-lg transition-all"
|
||||||
|
>
|
||||||
|
<div className="flex items-start justify-between mb-4">
|
||||||
|
<div className="w-12 h-12 rounded-xl bg-primary/10 text-primary flex items-center justify-center group-hover:bg-primary group-hover:text-white transition-colors">
|
||||||
|
<s.icon className="h-6 w-6" />
|
||||||
|
</div>
|
||||||
|
{s.category === "Notfall" && (
|
||||||
|
<span className="text-xs font-bold bg-gold/20 text-gold-hover px-2 py-1 rounded-full">
|
||||||
|
24/7
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<h3 className="font-bold text-lg mb-2 text-navy group-hover:text-primary transition-colors">
|
||||||
|
Zweitmeinung {s.title}
|
||||||
|
</h3>
|
||||||
|
<p className="text-text-muted text-sm leading-relaxed mb-4">
|
||||||
|
{s.description}
|
||||||
|
</p>
|
||||||
|
<span className="inline-flex items-center gap-1 text-sm font-semibold text-primary">
|
||||||
|
Mehr erfahren
|
||||||
|
<ArrowRight className="h-4 w-4 group-hover:translate-x-1 transition-transform" />
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
54
src/components/home/WhySection.tsx
Normal file
54
src/components/home/WhySection.tsx
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
import { Search, Clock, ShieldCheck } from "lucide-react"
|
||||||
|
import { Container } from "@/components/ui/Container"
|
||||||
|
|
||||||
|
const benefits = [
|
||||||
|
{
|
||||||
|
icon: Search,
|
||||||
|
title: "Präzise Diagnose",
|
||||||
|
description:
|
||||||
|
"Bestätigung oder Korrektur bestehender Diagnosen durch unabhängige Fachärzte.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: Clock,
|
||||||
|
title: "Schnelle Antwort",
|
||||||
|
description:
|
||||||
|
"Erhalten Sie innerhalb von 24-48 Stunden eine fundierte medizinische Einschätzung.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: ShieldCheck,
|
||||||
|
title: "Absolut vertraulich",
|
||||||
|
description:
|
||||||
|
"Ihre medizinischen Daten werden streng vertraulich und DSGVO-konform behandelt.",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export function WhySection() {
|
||||||
|
return (
|
||||||
|
<section className="py-20 bg-white">
|
||||||
|
<Container size="md">
|
||||||
|
<div className="text-center mb-14">
|
||||||
|
<p className="text-primary font-semibold text-sm uppercase tracking-wider mb-2">
|
||||||
|
Warum eine zweite Meinung?
|
||||||
|
</p>
|
||||||
|
<p className="text-text-muted max-w-2xl mx-auto">
|
||||||
|
Eine zweite medizinische Meinung kann Ihnen Sicherheit geben und
|
||||||
|
dabei helfen, die beste Behandlungsentscheidung zu treffen.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="grid sm:grid-cols-3 gap-8">
|
||||||
|
{benefits.map((b) => (
|
||||||
|
<div key={b.title} className="text-center group">
|
||||||
|
<div className="inline-flex items-center justify-center w-14 h-14 rounded-2xl bg-primary/10 text-primary mb-5 group-hover:bg-primary group-hover:text-white transition-colors">
|
||||||
|
<b.icon className="h-6 w-6" />
|
||||||
|
</div>
|
||||||
|
<h3 className="font-bold text-lg mb-2">{b.title}</h3>
|
||||||
|
<p className="text-text-muted text-sm leading-relaxed">
|
||||||
|
{b.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
22
src/components/layout/EmergencyBanner.tsx
Normal file
22
src/components/layout/EmergencyBanner.tsx
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { Phone } from "lucide-react"
|
||||||
|
|
||||||
|
export function EmergencyBanner() {
|
||||||
|
return (
|
||||||
|
<div className="bg-gold">
|
||||||
|
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 py-4">
|
||||||
|
<div className="flex flex-col sm:flex-row items-center justify-center gap-3 sm:gap-6">
|
||||||
|
<span className="font-bold text-navy-dark text-lg">
|
||||||
|
Medizinischer Notfall?
|
||||||
|
</span>
|
||||||
|
<a
|
||||||
|
href="tel:+4980080441000"
|
||||||
|
className="inline-flex items-center gap-2 bg-navy-dark hover:bg-navy text-white font-bold px-6 py-2.5 rounded-lg transition-colors"
|
||||||
|
>
|
||||||
|
<Phone className="h-4 w-4" />
|
||||||
|
Jetzt anrufen
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
153
src/components/layout/Footer.tsx
Normal file
153
src/components/layout/Footer.tsx
Normal file
|
|
@ -0,0 +1,153 @@
|
||||||
|
import Link from "next/link"
|
||||||
|
import { Phone, Mail, MapPin, Clock, Shield, Lock } from "lucide-react"
|
||||||
|
import type { SocialLink } from "@c2s/payload-contracts/types"
|
||||||
|
import { socialLinksToMap } from "@/lib/payload-helpers"
|
||||||
|
|
||||||
|
interface FooterProps {
|
||||||
|
socialLinks?: SocialLink[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const footerColumns = [
|
||||||
|
{
|
||||||
|
title: "Top Fachbereiche",
|
||||||
|
links: [
|
||||||
|
{ 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" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Services",
|
||||||
|
links: [
|
||||||
|
{ label: "So funktioniert's", href: "/so-funktionierts" },
|
||||||
|
{ label: "Häufige Fragen (FAQ)", href: "/faq" },
|
||||||
|
{ label: "Alle Fachbereiche", href: "/fachbereiche" },
|
||||||
|
{ label: "Kontakt", href: "/kontakt" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Unternehmen",
|
||||||
|
links: [
|
||||||
|
{ label: "Über uns", href: "/ueber-uns" },
|
||||||
|
{ label: "Motivation", href: "/motivation" },
|
||||||
|
{ label: "complex care solutions GmbH", href: "https://complexcaresolutions.de" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Rechtliches",
|
||||||
|
links: [
|
||||||
|
{ label: "Impressum", href: "/impressum" },
|
||||||
|
{ label: "Datenschutz", href: "/datenschutz" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export function Footer({ socialLinks }: FooterProps) {
|
||||||
|
const socialMap = socialLinksToMap(socialLinks)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<footer className="bg-navy text-white">
|
||||||
|
{/* Trust Section */}
|
||||||
|
<div className="border-b border-white/10">
|
||||||
|
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 py-8">
|
||||||
|
<div className="flex flex-col md:flex-row items-start md:items-center justify-between gap-6">
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center gap-2 mb-2">
|
||||||
|
<div className="w-8 h-8 rounded-full bg-primary flex items-center justify-center">
|
||||||
|
<span className="text-white font-bold text-sm">ZM</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="font-bold text-lg">zweitmeinu.ng</div>
|
||||||
|
<div className="text-white/50 text-xs">
|
||||||
|
Beratung wenn sie wirklich wichtig ist
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-white/60 max-w-md mt-3">
|
||||||
|
Ihr zentrales Portal für medizinische Zweitmeinungen. Eine
|
||||||
|
Initiative der complex care solutions GmbH.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-wrap gap-4 text-sm text-white/70">
|
||||||
|
<span className="flex items-center gap-1.5">
|
||||||
|
<Shield className="h-4 w-4 text-gold" />
|
||||||
|
Datenschutz geprüft
|
||||||
|
</span>
|
||||||
|
<span className="flex items-center gap-1.5">
|
||||||
|
<Lock className="h-4 w-4 text-gold" />
|
||||||
|
SSL Verschlüsselt
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* Trust badges */}
|
||||||
|
<div className="flex flex-wrap gap-x-8 gap-y-2 mt-6 text-sm text-white/50">
|
||||||
|
<span>✓ 100% unabhängige Experten</span>
|
||||||
|
<span>✓ Datenschutz garantiert</span>
|
||||||
|
<span>✓ Keine versteckten Kosten</span>
|
||||||
|
<span>✓ Full-Service</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Links Grid */}
|
||||||
|
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 py-12">
|
||||||
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-8">
|
||||||
|
{footerColumns.map((col) => (
|
||||||
|
<div key={col.title}>
|
||||||
|
<h3 className="font-semibold text-sm mb-4">{col.title}</h3>
|
||||||
|
<ul className="space-y-2.5">
|
||||||
|
{col.links.map((link) => (
|
||||||
|
<li key={link.href}>
|
||||||
|
<Link
|
||||||
|
href={link.href}
|
||||||
|
className="text-sm text-white/60 hover:text-white transition-colors"
|
||||||
|
>
|
||||||
|
{link.label}
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Bottom Bar */}
|
||||||
|
<div className="border-t border-white/10">
|
||||||
|
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 py-6">
|
||||||
|
<div className="flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<span className="text-sm text-white/40">Folgen Sie uns:</span>
|
||||||
|
{socialMap.linkedin && (
|
||||||
|
<a href={socialMap.linkedin} target="_blank" rel="noopener noreferrer" className="text-white/50 hover:text-white" aria-label="LinkedIn">
|
||||||
|
<svg className="h-5 w-5" fill="currentColor" viewBox="0 0 24 24"><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 01-2.063-2.065 2.064 2.064 0 112.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/></svg>
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
{socialMap.facebook && (
|
||||||
|
<a href={socialMap.facebook} target="_blank" rel="noopener noreferrer" className="text-white/50 hover:text-white" aria-label="Facebook">
|
||||||
|
<svg className="h-5 w-5" fill="currentColor" viewBox="0 0 24 24"><path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/></svg>
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
{socialMap.instagram && (
|
||||||
|
<a href={socialMap.instagram} target="_blank" rel="noopener noreferrer" className="text-white/50 hover:text-white" aria-label="Instagram">
|
||||||
|
<svg className="h-5 w-5" fill="currentColor" viewBox="0 0 24 24"><path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zM12 0C8.741 0 8.333.014 7.053.072 2.695.272.273 2.69.073 7.052.014 8.333 0 8.741 0 12c0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98C8.333 23.986 8.741 24 12 24c3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98C15.668.014 15.259 0 12 0zm0 5.838a6.162 6.162 0 100 12.324 6.162 6.162 0 000-12.324zM12 16a4 4 0 110-8 4 4 0 010 8zm6.406-11.845a1.44 1.44 0 100 2.881 1.44 1.44 0 000-2.881z"/></svg>
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
{socialMap.youtube && (
|
||||||
|
<a href={socialMap.youtube} target="_blank" rel="noopener noreferrer" className="text-white/50 hover:text-white" aria-label="YouTube">
|
||||||
|
<svg className="h-5 w-5" fill="currentColor" viewBox="0 0 24 24"><path d="M23.498 6.186a3.016 3.016 0 00-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 00.502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 002.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 002.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"/></svg>
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-white/40">
|
||||||
|
© {new Date().getFullYear()} complex care solutions GmbH. Alle Rechte vorbehalten.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
)
|
||||||
|
}
|
||||||
150
src/components/layout/Header.tsx
Normal file
150
src/components/layout/Header.tsx
Normal file
|
|
@ -0,0 +1,150 @@
|
||||||
|
"use client"
|
||||||
|
|
||||||
|
import { useState, useEffect } from "react"
|
||||||
|
import Link from "next/link"
|
||||||
|
import { Phone, ChevronDown, Menu } from "lucide-react"
|
||||||
|
import { MobileMenu } from "./MobileMenu"
|
||||||
|
import type { Navigation } from "@c2s/payload-contracts/types"
|
||||||
|
|
||||||
|
interface HeaderProps {
|
||||||
|
mainMenu: Navigation["mainMenu"] | null
|
||||||
|
}
|
||||||
|
|
||||||
|
const services = [
|
||||||
|
{ name: "Intensivmedizin", slug: "zweitmeinung-intensivmedizin", icon: "🏥" },
|
||||||
|
{ name: "Kardiologie", slug: "zweitmeinung-kardiologie", icon: "❤️" },
|
||||||
|
{ name: "Onkologie", slug: "zweitmeinung-onkologie", icon: "🔬" },
|
||||||
|
{ name: "Nephrologie", slug: "zweitmeinung-nephrologie", icon: "🫘" },
|
||||||
|
{ name: "Gallenblase", slug: "zweitmeinung-gallenblase", icon: "💊" },
|
||||||
|
{ name: "Schilddrüse", slug: "zweitmeinung-schilddruese", icon: "🦋" },
|
||||||
|
]
|
||||||
|
|
||||||
|
const navLinks = [
|
||||||
|
{ label: "So funktioniert's", href: "/so-funktionierts" },
|
||||||
|
{ label: "FAQ", href: "/faq" },
|
||||||
|
{ label: "Über uns", href: "/ueber-uns" },
|
||||||
|
{ label: "Motivation", href: "/motivation" },
|
||||||
|
{ label: "Kontakt", href: "/kontakt" },
|
||||||
|
]
|
||||||
|
|
||||||
|
export function Header({ mainMenu }: HeaderProps) {
|
||||||
|
const [mobileOpen, setMobileOpen] = useState(false)
|
||||||
|
const [megaOpen, setMegaOpen] = useState(false)
|
||||||
|
const [scrolled, setScrolled] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const onScroll = () => setScrolled(window.scrollY > 10)
|
||||||
|
window.addEventListener("scroll", onScroll, { passive: true })
|
||||||
|
return () => window.removeEventListener("scroll", onScroll)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<header
|
||||||
|
className={`sticky top-0 z-50 bg-navy-dark border-b border-white/10 transition-shadow ${
|
||||||
|
scrolled ? "shadow-lg" : ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="flex items-center justify-between h-16">
|
||||||
|
{/* Logo */}
|
||||||
|
<Link href="/" className="flex items-center gap-2 shrink-0">
|
||||||
|
<div className="w-8 h-8 rounded-full bg-primary flex items-center justify-center">
|
||||||
|
<span className="text-white font-bold text-sm">ZM</span>
|
||||||
|
</div>
|
||||||
|
<div className="hidden sm:block">
|
||||||
|
<div className="text-white font-bold text-lg leading-tight">
|
||||||
|
zweitmeinu.ng
|
||||||
|
</div>
|
||||||
|
<div className="text-white/50 text-[10px] leading-tight">
|
||||||
|
Beratung wenn sie wirklich wichtig ist
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
{/* Desktop Navigation */}
|
||||||
|
<nav className="hidden lg:flex items-center gap-1">
|
||||||
|
{/* Fachbereiche Dropdown */}
|
||||||
|
<div
|
||||||
|
className="relative"
|
||||||
|
onMouseEnter={() => setMegaOpen(true)}
|
||||||
|
onMouseLeave={() => setMegaOpen(false)}
|
||||||
|
>
|
||||||
|
<button className="flex items-center gap-1 px-3 py-2 text-sm text-white/90 hover:text-white transition-colors rounded-lg hover:bg-white/5">
|
||||||
|
Fachbereiche
|
||||||
|
<ChevronDown
|
||||||
|
className={`h-3.5 w-3.5 transition-transform ${megaOpen ? "rotate-180" : ""}`}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{megaOpen && (
|
||||||
|
<div className="absolute top-full left-0 pt-2 w-72">
|
||||||
|
<div className="bg-white rounded-xl shadow-2xl border border-gray-100 overflow-hidden">
|
||||||
|
<div className="p-2">
|
||||||
|
{services.map((s) => (
|
||||||
|
<Link
|
||||||
|
key={s.slug}
|
||||||
|
href={`/fachbereiche/${s.slug}`}
|
||||||
|
className="flex items-center gap-3 px-3 py-2.5 rounded-lg hover:bg-gray-50 transition-colors group"
|
||||||
|
>
|
||||||
|
<span className="text-lg">{s.icon}</span>
|
||||||
|
<span className="text-sm font-medium text-gray-700 group-hover:text-primary">
|
||||||
|
Zweitmeinung {s.name}
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="border-t bg-gray-50 px-4 py-3">
|
||||||
|
<Link
|
||||||
|
href="/fachbereiche"
|
||||||
|
className="text-sm font-medium text-primary hover:text-primary-dark"
|
||||||
|
>
|
||||||
|
Alle Fachbereiche ansehen →
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{navLinks.map((link) => (
|
||||||
|
<Link
|
||||||
|
key={link.href}
|
||||||
|
href={link.href}
|
||||||
|
className="px-3 py-2 text-sm text-white/90 hover:text-white transition-colors rounded-lg hover:bg-white/5"
|
||||||
|
>
|
||||||
|
{link.label}
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
{/* Emergency CTA + Mobile Toggle */}
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<a
|
||||||
|
href="tel:+4980080441000"
|
||||||
|
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" />
|
||||||
|
Notfall-Zweitmeinung
|
||||||
|
</a>
|
||||||
|
<button
|
||||||
|
onClick={() => setMobileOpen(true)}
|
||||||
|
className="lg:hidden p-2 text-white hover:bg-white/10 rounded-lg"
|
||||||
|
aria-label="Menu"
|
||||||
|
>
|
||||||
|
<Menu className="h-6 w-6" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<MobileMenu
|
||||||
|
open={mobileOpen}
|
||||||
|
onClose={() => setMobileOpen(false)}
|
||||||
|
services={services}
|
||||||
|
navLinks={navLinks}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
97
src/components/layout/MobileMenu.tsx
Normal file
97
src/components/layout/MobileMenu.tsx
Normal file
|
|
@ -0,0 +1,97 @@
|
||||||
|
"use client"
|
||||||
|
|
||||||
|
import { useEffect } from "react"
|
||||||
|
import Link from "next/link"
|
||||||
|
import { X, Phone, Mail, ChevronRight } from "lucide-react"
|
||||||
|
|
||||||
|
interface MobileMenuProps {
|
||||||
|
open: boolean
|
||||||
|
onClose: () => void
|
||||||
|
services: Array<{ name: string; slug: string; icon: string }>
|
||||||
|
navLinks: Array<{ label: string; href: string }>
|
||||||
|
}
|
||||||
|
|
||||||
|
export function MobileMenu({ open, onClose, services, navLinks }: MobileMenuProps) {
|
||||||
|
useEffect(() => {
|
||||||
|
if (open) document.body.style.overflow = "hidden"
|
||||||
|
else document.body.style.overflow = ""
|
||||||
|
return () => { document.body.style.overflow = "" }
|
||||||
|
}, [open])
|
||||||
|
|
||||||
|
if (!open) return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed inset-0 z-[100]">
|
||||||
|
{/* Backdrop */}
|
||||||
|
<div className="absolute inset-0 bg-black/60" onClick={onClose} />
|
||||||
|
|
||||||
|
{/* Drawer */}
|
||||||
|
<div className="absolute right-0 top-0 bottom-0 w-full max-w-sm bg-white shadow-2xl overflow-y-auto">
|
||||||
|
<div className="flex items-center justify-between p-4 border-b">
|
||||||
|
<div className="font-bold text-navy text-lg">zweitmeinu.ng</div>
|
||||||
|
<button
|
||||||
|
onClick={onClose}
|
||||||
|
className="p-2 hover:bg-gray-100 rounded-lg"
|
||||||
|
aria-label="Close menu"
|
||||||
|
>
|
||||||
|
<X className="h-5 w-5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<nav className="p-4 space-y-1">
|
||||||
|
{/* Fachbereiche */}
|
||||||
|
<div className="pb-2 mb-2 border-b">
|
||||||
|
<div className="px-3 py-2 text-xs font-semibold text-gray-400 uppercase tracking-wider">
|
||||||
|
Fachbereiche
|
||||||
|
</div>
|
||||||
|
{services.map((s) => (
|
||||||
|
<Link
|
||||||
|
key={s.slug}
|
||||||
|
href={`/fachbereiche/${s.slug}`}
|
||||||
|
onClick={onClose}
|
||||||
|
className="flex items-center justify-between px-3 py-2.5 rounded-lg hover:bg-gray-50 text-gray-700"
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<span>{s.icon}</span>
|
||||||
|
<span className="text-sm font-medium">{s.name}</span>
|
||||||
|
</div>
|
||||||
|
<ChevronRight className="h-4 w-4 text-gray-400" />
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Main nav links */}
|
||||||
|
{navLinks.map((link) => (
|
||||||
|
<Link
|
||||||
|
key={link.href}
|
||||||
|
href={link.href}
|
||||||
|
onClick={onClose}
|
||||||
|
className="flex items-center justify-between px-3 py-3 rounded-lg hover:bg-gray-50 text-gray-700 font-medium"
|
||||||
|
>
|
||||||
|
{link.label}
|
||||||
|
<ChevronRight className="h-4 w-4 text-gray-400" />
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
{/* Emergency CTA */}
|
||||||
|
<div className="p-4 border-t">
|
||||||
|
<a
|
||||||
|
href="tel:+4980080441000"
|
||||||
|
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"
|
||||||
|
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" />
|
||||||
|
E-Mail schreiben
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
40
src/components/layout/TopBar.tsx
Normal file
40
src/components/layout/TopBar.tsx
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
import { Phone, Mail, Clock, AlertTriangle } from "lucide-react"
|
||||||
|
|
||||||
|
export function TopBar() {
|
||||||
|
return (
|
||||||
|
<div className="bg-navy text-white text-sm">
|
||||||
|
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="flex items-center justify-between h-10">
|
||||||
|
<div className="flex items-center gap-4 sm:gap-6">
|
||||||
|
<a
|
||||||
|
href="tel:+4980080441000"
|
||||||
|
className="flex items-center gap-1.5 hover:text-gold transition-colors"
|
||||||
|
>
|
||||||
|
<Phone className="h-3.5 w-3.5" />
|
||||||
|
<span className="font-semibold">+49 800 80 44 100</span>
|
||||||
|
</a>
|
||||||
|
<span className="hidden sm:inline text-white/30">|</span>
|
||||||
|
<a
|
||||||
|
href="mailto:kontakt@zweitmeinu.ng"
|
||||||
|
className="hidden sm:flex items-center gap-1.5 hover:text-gold transition-colors"
|
||||||
|
>
|
||||||
|
<Mail className="h-3.5 w-3.5" />
|
||||||
|
<span>kontakt@zweitmeinu.ng</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-4 text-xs">
|
||||||
|
<span className="hidden md:flex items-center gap-1.5 text-white/70">
|
||||||
|
<Clock className="h-3 w-3" />
|
||||||
|
Mo-Fr 09:00-16:00
|
||||||
|
</span>
|
||||||
|
<span className="hidden md:inline text-white/30">|</span>
|
||||||
|
<span className="flex items-center gap-1.5 text-gold font-semibold">
|
||||||
|
<AlertTriangle className="h-3 w-3" />
|
||||||
|
24/7 Notfall
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
5
src/components/layout/index.ts
Normal file
5
src/components/layout/index.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
export { TopBar } from "./TopBar"
|
||||||
|
export { Header } from "./Header"
|
||||||
|
export { MobileMenu } from "./MobileMenu"
|
||||||
|
export { Footer } from "./Footer"
|
||||||
|
export { EmergencyBanner } from "./EmergencyBanner"
|
||||||
69
src/components/ui/Button.tsx
Normal file
69
src/components/ui/Button.tsx
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
import { clsx } from "clsx"
|
||||||
|
import Link from "next/link"
|
||||||
|
|
||||||
|
interface ButtonProps {
|
||||||
|
children: React.ReactNode
|
||||||
|
href?: string
|
||||||
|
variant?: "primary" | "secondary" | "outline" | "gold" | "ghost"
|
||||||
|
size?: "sm" | "md" | "lg"
|
||||||
|
className?: string
|
||||||
|
onClick?: () => void
|
||||||
|
type?: "button" | "submit"
|
||||||
|
disabled?: boolean
|
||||||
|
external?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const variants = {
|
||||||
|
primary: "bg-primary hover:bg-primary-dark text-white",
|
||||||
|
secondary: "bg-navy hover:bg-navy-light text-white",
|
||||||
|
outline: "border-2 border-white text-white hover:bg-white hover:text-navy",
|
||||||
|
gold: "bg-gold hover:bg-gold-hover text-navy-dark font-bold",
|
||||||
|
ghost: "text-primary hover:bg-primary/10",
|
||||||
|
}
|
||||||
|
|
||||||
|
const sizes = {
|
||||||
|
sm: "px-4 py-2 text-sm",
|
||||||
|
md: "px-6 py-2.5 text-sm",
|
||||||
|
lg: "px-8 py-3.5 text-base",
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Button({
|
||||||
|
children,
|
||||||
|
href,
|
||||||
|
variant = "primary",
|
||||||
|
size = "md",
|
||||||
|
className,
|
||||||
|
onClick,
|
||||||
|
type = "button",
|
||||||
|
disabled,
|
||||||
|
external,
|
||||||
|
}: ButtonProps) {
|
||||||
|
const classes = clsx(
|
||||||
|
"inline-flex items-center justify-center gap-2 rounded-lg font-semibold transition-all duration-150",
|
||||||
|
variants[variant],
|
||||||
|
sizes[size],
|
||||||
|
disabled && "opacity-50 cursor-not-allowed",
|
||||||
|
className,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (href) {
|
||||||
|
if (external) {
|
||||||
|
return (
|
||||||
|
<a href={href} target="_blank" rel="noopener noreferrer" className={classes}>
|
||||||
|
{children}
|
||||||
|
</a>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Link href={href} className={classes}>
|
||||||
|
{children}
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button type={type} onClick={onClick} disabled={disabled} className={classes}>
|
||||||
|
{children}
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
26
src/components/ui/Container.tsx
Normal file
26
src/components/ui/Container.tsx
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { clsx } from "clsx"
|
||||||
|
|
||||||
|
interface ContainerProps {
|
||||||
|
children: React.ReactNode
|
||||||
|
className?: string
|
||||||
|
size?: "sm" | "md" | "lg" | "xl"
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxWidths = {
|
||||||
|
sm: "max-w-3xl",
|
||||||
|
md: "max-w-5xl",
|
||||||
|
lg: "max-w-7xl",
|
||||||
|
xl: "max-w-screen-2xl",
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Container({
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
size = "lg",
|
||||||
|
}: ContainerProps) {
|
||||||
|
return (
|
||||||
|
<div className={clsx("mx-auto px-4 sm:px-6 lg:px-8", maxWidths[size], className)}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
142
src/lib/api.ts
Normal file
142
src/lib/api.ts
Normal file
|
|
@ -0,0 +1,142 @@
|
||||||
|
/**
|
||||||
|
* Typed API functions for zweitmeinu.ng
|
||||||
|
* Uses @c2s/payload-contracts client for all CMS queries.
|
||||||
|
*/
|
||||||
|
import { cms } from "./cms"
|
||||||
|
import type {
|
||||||
|
Navigation,
|
||||||
|
Page,
|
||||||
|
Service,
|
||||||
|
ServiceCategory,
|
||||||
|
Faq,
|
||||||
|
SiteSetting,
|
||||||
|
SeoSetting,
|
||||||
|
SocialLink,
|
||||||
|
PaginatedResponse,
|
||||||
|
} from "@c2s/payload-contracts/types"
|
||||||
|
|
||||||
|
export type { Navigation, Service, ServiceCategory, Faq, SiteSetting, SocialLink }
|
||||||
|
|
||||||
|
// ─── Navigation ────────────────────────────────────────
|
||||||
|
export async function getNavigation(): Promise<Navigation | null> {
|
||||||
|
try {
|
||||||
|
return await cms.navigation.getNavigation({ depth: 2 })
|
||||||
|
} catch {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Site Settings ─────────────────────────────────────
|
||||||
|
export async function getSiteSettings(): Promise<SiteSetting | null> {
|
||||||
|
try {
|
||||||
|
return await cms.settings.getSiteSettings({ depth: 2 })
|
||||||
|
} catch {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── SEO Settings ──────────────────────────────────────
|
||||||
|
export async function getSeoSettings(): Promise<SeoSetting | null> {
|
||||||
|
try {
|
||||||
|
return await cms.settings.getSeoSettings()
|
||||||
|
} catch {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Social Links ──────────────────────────────────────
|
||||||
|
export async function getSocialLinks(): Promise<SocialLink[]> {
|
||||||
|
try {
|
||||||
|
const result = await cms.client.getCollection("social-links", {
|
||||||
|
limit: 20,
|
||||||
|
depth: 0,
|
||||||
|
where: { "isActive][equals": "true" },
|
||||||
|
})
|
||||||
|
return result.docs as SocialLink[]
|
||||||
|
} catch {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Pages ─────────────────────────────────────────────
|
||||||
|
export async function getPage(slug: string, locale = "de"): Promise<Page | null> {
|
||||||
|
return cms.pages.getPage(slug, { locale: locale as "de" | "en", depth: 2 })
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getPages(options: { limit?: number; locale?: string } = {}): Promise<PaginatedResponse<Page>> {
|
||||||
|
return cms.pages.getPages({
|
||||||
|
limit: options.limit || 100,
|
||||||
|
locale: (options.locale || "de") as "de" | "en",
|
||||||
|
depth: 1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Services ──────────────────────────────────────────
|
||||||
|
export async function getServices(): Promise<Service[]> {
|
||||||
|
try {
|
||||||
|
const result = await cms.services.getServices({ limit: 20, depth: 2 })
|
||||||
|
return result.docs
|
||||||
|
} catch {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getServiceBySlug(slug: string): Promise<Service | null> {
|
||||||
|
try {
|
||||||
|
return await cms.services.getServiceBySlug(slug, { depth: 3 })
|
||||||
|
} catch {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getServiceCategories(): Promise<ServiceCategory[]> {
|
||||||
|
try {
|
||||||
|
const result = await cms.services.getServiceCategories({ depth: 1 })
|
||||||
|
return result.docs
|
||||||
|
} catch {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── FAQs ──────────────────────────────────────────────
|
||||||
|
export async function getFaqs(): Promise<Faq[]> {
|
||||||
|
try {
|
||||||
|
const result = await cms.faqs.getFaqs({ limit: 100, depth: 1 })
|
||||||
|
return result.docs
|
||||||
|
} catch {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getFaqsByCategory(category: string): Promise<Faq[]> {
|
||||||
|
try {
|
||||||
|
const result = await cms.faqs.getFaqsByCategory(category, { depth: 1 })
|
||||||
|
return result.docs
|
||||||
|
} catch {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Contact Form ──────────────────────────────────────
|
||||||
|
// Note: Form submissions use direct fetch() — contracts client doesn't work client-side
|
||||||
|
export async function submitContactForm(data: {
|
||||||
|
name: string
|
||||||
|
email: string
|
||||||
|
phone?: string
|
||||||
|
subject: string
|
||||||
|
urgency?: string
|
||||||
|
message: string
|
||||||
|
formId?: number
|
||||||
|
}): Promise<{ success: boolean; message?: string }> {
|
||||||
|
return cms.post("/api/form-submissions", {
|
||||||
|
form: data.formId || 1,
|
||||||
|
submissionData: [
|
||||||
|
{ field: "name", value: data.name },
|
||||||
|
{ field: "email", value: data.email },
|
||||||
|
{ field: "phone", value: data.phone || "" },
|
||||||
|
{ field: "subject", value: data.subject },
|
||||||
|
{ field: "urgency", value: data.urgency || "normal" },
|
||||||
|
{ field: "message", value: data.message },
|
||||||
|
],
|
||||||
|
})
|
||||||
|
}
|
||||||
15
src/lib/cms.ts
Normal file
15
src/lib/cms.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
/**
|
||||||
|
* Payload CMS Client — initialized from @c2s/payload-contracts
|
||||||
|
*
|
||||||
|
* Single shared client instance for all API calls.
|
||||||
|
* Tenant isolation is handled automatically by the contracts client.
|
||||||
|
*/
|
||||||
|
import { createPayloadClient } from "@c2s/payload-contracts/api-client"
|
||||||
|
|
||||||
|
export const cms = createPayloadClient({
|
||||||
|
baseUrl: process.env.NEXT_PUBLIC_PAYLOAD_URL || "https://cms.c2sgmbh.de",
|
||||||
|
tenantId: process.env.NEXT_PUBLIC_TENANT_ID || "12",
|
||||||
|
defaultLocale: "de",
|
||||||
|
defaultDepth: 2,
|
||||||
|
defaultRevalidate: 60,
|
||||||
|
})
|
||||||
50
src/lib/payload-helpers.ts
Normal file
50
src/lib/payload-helpers.ts
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
import type { Media, SocialLink } from "@c2s/payload-contracts/types"
|
||||||
|
|
||||||
|
export function resolveRelation<T>(
|
||||||
|
value: number | T | null | undefined,
|
||||||
|
): T | null {
|
||||||
|
if (value == null || typeof value === "number") return null
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveMedia(
|
||||||
|
media: number | Media | null | undefined,
|
||||||
|
): Media | null {
|
||||||
|
return resolveRelation<Media>(media)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getMediaUrl(
|
||||||
|
media: number | Media | null | undefined,
|
||||||
|
size?: keyof NonNullable<Media["sizes"]>,
|
||||||
|
): string | undefined {
|
||||||
|
const resolved = resolveMedia(media)
|
||||||
|
if (!resolved) return undefined
|
||||||
|
if (size && resolved.sizes?.[size]?.url) return resolved.sizes[size]!.url!
|
||||||
|
return resolved.url ?? undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getMediaAlt(
|
||||||
|
media: number | Media | null | undefined,
|
||||||
|
fallback = "",
|
||||||
|
): string {
|
||||||
|
const resolved = resolveMedia(media)
|
||||||
|
return resolved?.alt ?? fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveRelationArray<T>(
|
||||||
|
items: (number | T)[] | null | undefined,
|
||||||
|
): T[] {
|
||||||
|
if (!items) return []
|
||||||
|
return items.filter((item): item is T => typeof item !== "number")
|
||||||
|
}
|
||||||
|
|
||||||
|
export function socialLinksToMap(
|
||||||
|
links: SocialLink[] | null | undefined,
|
||||||
|
): Record<string, string> {
|
||||||
|
if (!links) return {}
|
||||||
|
const map: Record<string, string> = {}
|
||||||
|
for (const link of links) {
|
||||||
|
if (link.isActive !== false && link.url) map[link.platform] = link.url
|
||||||
|
}
|
||||||
|
return map
|
||||||
|
}
|
||||||
34
tsconfig.json
Normal file
34
tsconfig.json
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2017",
|
||||||
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
|
"allowJs": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"strict": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"incremental": true,
|
||||||
|
"plugins": [
|
||||||
|
{
|
||||||
|
"name": "next"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"next-env.d.ts",
|
||||||
|
"**/*.ts",
|
||||||
|
"**/*.tsx",
|
||||||
|
".next/types/**/*.ts",
|
||||||
|
".next/dev/types/**/*.ts",
|
||||||
|
"**/*.mts"
|
||||||
|
],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue