feat: Hero, About, Contact components accept CMS props

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
MBO-Tech-IT 2026-06-02 20:35:23 +02:00
parent b9aa6a0a54
commit 3eeea27b92
3 changed files with 149 additions and 65 deletions

View File

@ -19,7 +19,19 @@ const highlights = [
}, },
]; ];
export default function About() { interface AboutProps {
eyebrowText?: string;
absatz1?: string;
absatz2?: string;
stats?: { wert: string; label: string }[];
}
export default function About({
eyebrowText = "Über uns",
absatz1 = "MBO-Tech-IT steht für über 30 Jahre IT-Erfahrung — angefangen bei der einfachen Hardware-Wartung, über den Aufbau großer Client-Server-Netzwerke, bis hin zu mehr als 20 Jahren Spezialisierung in der IT-Security.",
absatz2 = "Heute liegt der Fokus auf modernen Container-Technologien, Cloud-nativer Infrastruktur und Virtualisierung. Dieses breite Fundament ermöglicht es, Lösungen zu entwickeln, die nicht nur funktionieren — sondern auch sicher und langfristig tragfähig sind.",
stats,
}: AboutProps = {}) {
return ( return (
<section id="about" className="py-24 px-4 sm:px-6 lg:px-8 relative"> <section id="about" className="py-24 px-4 sm:px-6 lg:px-8 relative">
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_bottom_right,rgba(37,99,235,0.06)_0%,transparent_60%)]" /> <div className="absolute inset-0 bg-[radial-gradient(ellipse_at_bottom_right,rgba(37,99,235,0.06)_0%,transparent_60%)]" />
@ -29,23 +41,17 @@ export default function About() {
{/* Left: Text */} {/* Left: Text */}
<div> <div>
<span className="text-orange-400 font-mono text-xs font-bold tracking-[0.25em] uppercase"> <span className="text-orange-400 font-mono text-xs font-bold tracking-[0.25em] uppercase">
Über uns {eyebrowText}
</span> </span>
<h2 className="text-4xl sm:text-5xl font-black text-slate-900 dark:text-white mt-3 mb-6 leading-tight"> <h2 className="text-4xl sm:text-5xl font-black text-slate-900 dark:text-white mt-3 mb-6 leading-tight">
IT-Expertise,{" "} IT-Expertise,{" "}
<span className="text-gradient">die Sie weiterbringt</span> <span className="text-gradient">die Sie weiterbringt</span>
</h2> </h2>
<p className="text-slate-600 dark:text-slate-400 text-lg leading-relaxed mb-6"> <p className="text-slate-600 dark:text-slate-400 text-lg leading-relaxed mb-6">
MBO-Tech-IT steht für über <strong className="text-slate-900 dark:text-white">30 Jahre IT-Erfahrung</strong> &mdash; {absatz1}
angefangen bei der einfachen Hardware-Wartung, über den Aufbau großer
Client-Server-Netzwerke, bis hin zu mehr als 20 Jahren Spezialisierung
in der <strong className="text-slate-900 dark:text-white">IT-Security</strong>.
</p> </p>
<p className="text-slate-600 dark:text-slate-400 text-lg leading-relaxed mb-8"> <p className="text-slate-600 dark:text-slate-400 text-lg leading-relaxed mb-8">
Heute liegt der Fokus auf modernen Container-Technologien, Cloud-nativer {absatz2}
Infrastruktur und Virtualisierung. Dieses breite Fundament ermöglicht es,
Lösungen zu entwickeln, die nicht nur funktionieren &mdash; sondern auch
sicher und langfristig tragfähig sind.
</p> </p>
{/* Tagline callout */} {/* Tagline callout */}
@ -76,6 +82,17 @@ export default function About() {
</div> </div>
))} ))}
</div> </div>
{stats && stats.length > 0 && (
<div className="flex gap-8 mt-4">
{stats.map((s) => (
<div key={s.label}>
<div className="text-3xl font-black text-orange-400">{s.wert}</div>
<div className="text-sm text-slate-500 dark:text-slate-400">{s.label}</div>
</div>
))}
</div>
)}
</div> </div>
{/* Right: Process steps */} {/* Right: Process steps */}

View File

@ -2,45 +2,61 @@
import { useState } from "react"; import { useState } from "react";
const contactItems = [ interface OeffnungsZeit { tag: string; von: string; bis: string }
{
icon: ( interface ContactProps {
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> telefon?: string;
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z" /> email?: string;
</svg> adresseZeile1?: string;
), adresseZeile2?: string;
label: "Telefon", oeffnungszeiten?: OeffnungsZeit[];
value: "+49 171 9345193", }
href: "tel:+4917193451093",
},
{
icon: (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
</svg>
),
label: "E-Mail",
value: "kontakt@mbo-tech-it.de",
href: "mailto:kontakt@mbo-tech-it.de",
},
{
icon: (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
),
label: "Standort",
value: "Crailsheim, 74564",
href: "https://maps.google.com/?q=Mörikestr.+2,+74564+Crailsheim",
},
];
type Status = "idle" | "loading" | "success" | "error"; type Status = "idle" | "loading" | "success" | "error";
export default function Contact() { export default function Contact({
telefon = "+49 171 9345193",
email = "kontakt@mbo-tech-it.de",
adresseZeile1 = "Mörikestr. 2",
adresseZeile2 = "74564 Crailsheim",
oeffnungszeiten,
}: ContactProps = {}) {
const contactItems = [
{
icon: (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z" />
</svg>
),
label: "Telefon",
value: telefon,
href: `tel:${telefon.replace(/\s/g, "")}`,
},
{
icon: (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
</svg>
),
label: "E-Mail",
value: email,
href: `mailto:${email}`,
},
{
icon: (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
),
label: "Standort",
value: `${adresseZeile1}, ${adresseZeile2}`,
href: `https://maps.google.com/?q=${encodeURIComponent(adresseZeile1 + " " + adresseZeile2)}`,
},
];
const [name, setName] = useState(""); const [name, setName] = useState("");
const [email, setEmail] = useState(""); const [emailInput, setEmailInput] = useState("");
const [betreff, setBetreff] = useState(""); const [betreff, setBetreff] = useState("");
const [nachricht, setNachricht] = useState(""); const [nachricht, setNachricht] = useState("");
const [status, setStatus] = useState<Status>("idle"); const [status, setStatus] = useState<Status>("idle");
@ -55,7 +71,7 @@ export default function Contact() {
const res = await fetch("/api/contact", { const res = await fetch("/api/contact", {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name, email, betreff, nachricht }), body: JSON.stringify({ name, email: emailInput, betreff, nachricht }),
}); });
const data = await res.json(); const data = await res.json();
@ -108,7 +124,7 @@ export default function Contact() {
onClick={() => { onClick={() => {
setStatus("idle"); setStatus("idle");
setName(""); setName("");
setEmail(""); setEmailInput("");
setBetreff(""); setBetreff("");
setNachricht(""); setNachricht("");
}} }}
@ -136,8 +152,8 @@ export default function Contact() {
<input <input
type="email" type="email"
required required
value={email} value={emailInput}
onChange={(e) => setEmail(e.target.value)} onChange={(e) => setEmailInput(e.target.value)}
placeholder="max@beispiel.de" placeholder="max@beispiel.de"
className="w-full px-4 py-3 rounded-xl bg-slate-50 dark:bg-[#111925] border border-slate-300 dark:border-gray-700 text-slate-900 dark:text-white placeholder-slate-400 dark:placeholder-slate-600 focus:outline-none focus:border-orange-500/60 focus:ring-1 focus:ring-orange-500/20 transition-colors" className="w-full px-4 py-3 rounded-xl bg-slate-50 dark:bg-[#111925] border border-slate-300 dark:border-gray-700 text-slate-900 dark:text-white placeholder-slate-400 dark:placeholder-slate-600 focus:outline-none focus:border-orange-500/60 focus:ring-1 focus:ring-orange-500/20 transition-colors"
/> />
@ -239,6 +255,22 @@ export default function Contact() {
</a> </a>
))} ))}
</div> </div>
{oeffnungszeiten && oeffnungszeiten.length > 0 && (
<div className="mt-6 pt-6 border-t border-slate-200 dark:border-slate-700">
<h4 className="text-sm font-semibold text-slate-500 dark:text-slate-400 uppercase tracking-wider mb-3">
Öffnungszeiten
</h4>
<ul className="space-y-1">
{oeffnungszeiten.map((oz) => (
<li key={oz.tag} className="flex justify-between text-sm text-slate-600 dark:text-slate-400">
<span>{oz.tag}</span>
<span>{oz.von} {oz.bis}</span>
</li>
))}
</ul>
</div>
)}
</div> </div>
</div> </div>
</section> </section>

View File

@ -1,10 +1,33 @@
// components/Hero.tsx
"use client"; "use client";
import { useMemo } from "react"; import { useMemo } from "react";
import { useTypewriter } from "@/hooks/useTypewriter"; import { useTypewriter } from "@/hooks/useTypewriter";
export default function Hero() { interface HeroProps {
eyebrowText?: string;
headline1?: string;
subtext1?: string;
subtext2?: string;
cta1Text?: string;
cta1Href?: string;
cta2Text?: string;
cta2Href?: string;
bgImagePath?: string | null;
badges?: string[];
}
export default function Hero({
eyebrowText = "Digital Denken. Lokal Handeln.",
headline1 = "Ihre IT-Infrastruktur.",
subtext1 = "Ihr IT-Dienstleister in Crailsheim — egal ob zu Hause oder im Büro. Von Hard- & Software über Netzwerk & WLAN bis hin zu Webseiten & Webanwendungen: Wir lösen Ihre IT-Probleme persönlich und zum fairen Preis.",
subtext2 = "",
cta1Text = "Jetzt anfragen",
cta1Href = "#contact",
cta2Text = "Unsere Leistungen",
cta2Href = "#services",
bgImagePath = null,
badges = ["Docker", "Kubernetes", "Proxmox", "Hetzner Cloud", "Linux"],
}: HeroProps = {}) {
const words = useMemo(() => ["Professionell.", "DSGVO-konform.", "Zuverlässig.", "Skalierbar."], []); const words = useMemo(() => ["Professionell.", "DSGVO-konform.", "Zuverlässig.", "Skalierbar."], []);
const typed = useTypewriter(words); const typed = useTypewriter(words);
@ -20,48 +43,60 @@ export default function Hero() {
<div className="absolute top-1/4 left-1/4 w-72 h-72 bg-orange-500/5 rounded-full blur-3xl animate-pulse-slow" /> <div className="absolute top-1/4 left-1/4 w-72 h-72 bg-orange-500/5 rounded-full blur-3xl animate-pulse-slow" />
<div className="absolute bottom-1/4 right-1/4 w-80 h-80 bg-blue-600/5 rounded-full blur-3xl animate-pulse-slow" /> <div className="absolute bottom-1/4 right-1/4 w-80 h-80 bg-blue-600/5 rounded-full blur-3xl animate-pulse-slow" />
{bgImagePath && (
<div
className="absolute inset-0 bg-cover bg-center opacity-20"
style={{ backgroundImage: `url(${process.env.NEXT_PUBLIC_SUPABASE_URL}/storage/v1/object/public/hero-bilder/${bgImagePath})` }}
/>
)}
<div className="relative z-10 max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 text-center"> <div className="relative z-10 max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
{/* Tagline badge */} {/* Tagline badge */}
<div className="inline-flex items-center gap-2 px-5 py-2 rounded-full border border-orange-500/40 bg-orange-500/10 text-orange-400 text-xs font-black tracking-[0.3em] uppercase mb-8"> <div className="inline-flex items-center gap-2 px-5 py-2 rounded-full border border-orange-500/40 bg-orange-500/10 text-orange-400 text-xs font-black tracking-[0.3em] uppercase mb-8">
<span className="w-1.5 h-1.5 bg-orange-400 rounded-full animate-pulse" /> <span className="w-1.5 h-1.5 bg-orange-400 rounded-full animate-pulse" />
Digital Denken. Lokal Handeln. {eyebrowText}
</div> </div>
{/* Headline */} {/* Headline */}
<h1 className="text-5xl sm:text-6xl lg:text-7xl font-black text-slate-900 dark:text-white leading-tight mb-6 tracking-tight"> <h1 className="text-5xl sm:text-6xl lg:text-7xl font-black text-slate-900 dark:text-white leading-tight mb-6 tracking-tight">
Ihre IT-Infrastruktur.{" "} {headline1}{" "}
<span className="text-gradient"> <span className="text-gradient">
{typed} {typed}
<span className="animate-pulse">|</span> <span className="animate-pulse">|</span>
</span> </span>
<br />
Skalierbar. Zuverlässig.
</h1> </h1>
{/* Subheadline */} {/* Subheadline */}
<p className="text-xl sm:text-2xl text-slate-600 dark:text-slate-400 max-w-3xl mx-auto mb-4 leading-relaxed"> {subtext1 && (
Ihr IT-Dienstleister in <strong className="text-slate-700 dark:text-slate-300">Crailsheim</strong> &mdash; egal ob zu Hause oder im Büro. Von Hard- &amp; Software über Netzwerk &amp; WLAN bis hin zu Webseiten &amp; Webanwendungen: Wir lösen Ihre IT-Probleme persönlich und zum fairen Preis. <p className="text-xl sm:text-2xl text-slate-600 dark:text-slate-400 max-w-3xl mx-auto mb-4 leading-relaxed">
</p> {subtext1}
</p>
)}
{subtext2 && (
<p className="text-xl sm:text-2xl text-slate-600 dark:text-slate-400 max-w-3xl mx-auto mb-4 leading-relaxed">
{subtext2}
</p>
)}
{/* Tech pills */} {/* Tech pills */}
<div className="flex flex-wrap items-center justify-center gap-3 mb-10"> <div className="flex flex-wrap items-center justify-center gap-3 mb-10">
{["Docker", "Kubernetes", "Proxmox", "Hetzner Cloud", "Linux"].map((tech) => ( {badges.map((badge) => (
<span <span
key={tech} key={badge}
className="px-3 py-1 rounded-md bg-slate-100 dark:bg-gray-900 border border-slate-200 dark:border-gray-700 text-slate-700 dark:text-slate-300 text-sm font-mono" className="px-3 py-1 rounded-md bg-slate-100 dark:bg-gray-900 border border-slate-200 dark:border-gray-700 text-slate-700 dark:text-slate-300 text-sm font-mono"
> >
{tech} {badge}
</span> </span>
))} ))}
</div> </div>
{/* CTAs */} {/* CTAs */}
<div className="flex flex-col sm:flex-row items-center justify-center gap-4"> <div className="flex flex-col sm:flex-row items-center justify-center gap-4">
<a href="#contact" className="btn-primary w-full sm:w-auto px-8 py-4 text-lg"> <a href={cta1Href} className="btn-primary w-full sm:w-auto px-8 py-4 text-lg">
Projekt anfragen {cta1Text}
</a> </a>
<a href="#services" className="btn-secondary w-full sm:w-auto px-8 py-4 text-lg"> <a href={cta2Href} className="btn-secondary w-full sm:w-auto px-8 py-4 text-lg">
Services ansehen {cta2Text}
</a> </a>
</div> </div>