feat: complete migration to static MDX, stabilize local infrastructure and fix i18n issues
Some checks failed
🚀 Build & Deploy / 🔍 Prepare (push) Successful in 4s
🚀 Build & Deploy / 🚀 Deploy (push) Has been cancelled
🚀 Build & Deploy / 🧪 Post-Deploy Verification (push) Has been cancelled
🚀 Build & Deploy / 🏗️ Build (push) Has been cancelled
🚀 Build & Deploy / 🔔 Notify (push) Has been cancelled
🚀 Build & Deploy / 🧪 QA (push) Has been cancelled

This commit is contained in:
2026-05-04 10:26:45 +02:00
parent 41ef9092d6
commit 5fb73d57dd
43 changed files with 2304 additions and 532 deletions

View File

@@ -1,112 +1,37 @@
import React from "react";
import { Download } from "lucide-react";
import fs from "fs";
import path from "path";
import { notFound } from "next/navigation";
import { LegalLayout } from "@/components/LegalLayout";
export default function AVB() {
const filePath = path.join(process.cwd(), "context/avbs.md");
const fileContent = fs.readFileSync(filePath, "utf8");
interface PageProps {
params: Promise<{ locale: string }>;
}
// Split by double newlines to get major blocks
const rawBlocks = fileContent
.split(/\n\s*\n/)
.map((b) => b.trim())
.filter((b) => b !== "");
export default async function Page({ params }: PageProps) {
const { locale } = await params;
// Extract title and stand more robustly
const title =
rawBlocks.find((b) => b.startsWith("# "))?.replace(/^#\s+/, "") ||
"Allgemeine Verkaufsbedingungen";
const stand =
rawBlocks.find((b) => b.toLowerCase().startsWith("stand:")) ||
"Stand: April 2026";
let Content: React.ComponentType;
try {
const mdx = await import(`@/content/${locale}/avb.mdx`);
Content = mdx.default;
} catch (e) {
notFound();
}
const sections: { title: string; content: string[] }[] = [];
let currentSection: { title: string; content: string[] } | null = null;
// Process sections, skipping title and stand blocks
rawBlocks.forEach((block) => {
if (block.startsWith("# ") || block.toLowerCase().startsWith("stand:"))
return;
if (block.startsWith("##")) {
// New section header: e.g. "## 1. Geltungsbereich"
if (currentSection) sections.push(currentSection);
currentSection = {
title: block.replace(/^##\s+/, "").trim(),
content: [],
};
} else if (currentSection) {
// Clean up bold markers for better display
const cleanedBlock = block.replace(/\*\*(.*?)\*\*/g, "$1");
currentSection.content.push(cleanedBlock);
}
});
if (currentSection) sections.push(currentSection);
// The very last block might be a footer/schlussbestimmung if it's not in a section
// In our MD, everything is in a section, so we just use the last block of the last section as potential footer if we want,
// but the current UI expects a separate 'footer' variable.
const footer = "MB Grid Solutions & Services";
const downloadButton = (
<a
href="/assets/avb-mb-grid-4-2026.pdf"
download
className="btn-primary !py-3 !px-6 flex items-center gap-2"
>
<Download size={18} />
Als PDF herunterladen
</a>
);
return (
<div className="bg-slate-50 min-h-screen pt-40 pb-20">
<div className="container-custom">
<div className="max-w-4xl mx-auto bg-white p-8 md:p-12 rounded-[2.5rem] shadow-sm border border-slate-100">
<div className="flex flex-col md:flex-row md:items-center justify-between gap-6 mb-8">
<div>
<h1 className="text-4xl font-extrabold text-primary mb-2">
{title}
</h1>
<p className="text-slate-500 font-medium">{stand}</p>
</div>
<a
href="/assets/avb-mb-grid-4-2026.pdf"
download
className="btn-primary !py-3 !px-6 flex items-center gap-2"
>
<Download size={18} />
Als PDF herunterladen
</a>
</div>
<div className="space-y-8 text-slate-600 leading-relaxed">
{sections.map((section, index) => (
<div key={index}>
<h2 className="text-2xl font-bold text-primary mb-4">
{section.title}
</h2>
<div className="space-y-4">
{section.content.map((paragraph, pIndex) => (
<p key={pIndex}>
{paragraph.split(/(\[.*?\]\(.*?\))/g).map((part, i) => {
const match = part.match(/\[(.*?)\]\((.*?)\)/);
if (match) {
return (
<a
key={i}
href={match[2]}
className="text-primary hover:underline font-medium"
download={match[2].endsWith(".pdf")}
>
{match[1]}
</a>
);
}
return part;
})}
</p>
))}
</div>
</div>
))}
<div className="pt-8 border-t border-slate-100 flex justify-between items-center text-slate-400 text-sm italic">
<p>{footer}</p>
<p>{stand}</p>
</div>
</div>
</div>
</div>
</div>
<LegalLayout title="AVB" heroAction={downloadButton}>
<Content />
</LegalLayout>
);
}

View File

@@ -1,66 +1,25 @@
export default function Privacy() {
import React from "react";
import { notFound } from "next/navigation";
import { LegalLayout } from "@/components/LegalLayout";
interface PageProps {
params: Promise<{ locale: string }>;
}
export default async function Page({ params }: PageProps) {
const { locale } = await params;
let Content: React.ComponentType;
try {
const mdx = await import(`@/content/${locale}/datenschutz.mdx`);
Content = mdx.default;
} catch (e) {
notFound();
}
return (
<div className="bg-slate-50 min-h-screen pt-40 pb-20">
<div className="container-custom">
<div className="max-w-4xl mx-auto bg-white p-8 md:p-12 rounded-[2.5rem] shadow-sm border border-slate-100">
<h1 className="text-4xl font-extrabold text-primary mb-8">
Datenschutzerklärung
</h1>
<div className="space-y-8 text-slate-600 leading-relaxed">
<div>
<h2 className="text-2xl font-bold text-primary mb-4">
1. Datenschutz auf einen Blick
</h2>
<p>
Wir nehmen den Schutz Ihrer persönlichen Daten sehr ernst. Wir
behandeln Ihre personenbezogenen Daten vertraulich und
entsprechend der gesetzlichen Datenschutzvorschriften sowie
dieser Datenschutzerklärung.
</p>
</div>
<div>
<h2 className="text-2xl font-bold text-primary mb-4">
2. Hosting
</h2>
<p>
Unsere Website wird bei Hetzner Online GmbH gehostet. Der
Serverstandort ist Deutschland. Wir haben einen Vertrag über
Auftragsverarbeitung (AVV) mit Hetzner geschlossen.
</p>
</div>
<div>
<h2 className="text-2xl font-bold text-primary mb-4">
3. Kontaktformular
</h2>
<p>
Wenn Sie uns per Kontaktformular Anfragen zukommen lassen,
werden Ihre Angaben aus dem Anfrageformular inklusive der von
Ihnen dort angegebenen Kontaktdaten zwecks Bearbeitung der
Anfrage und für den Fall von Anschlussfragen bei uns
gespeichert. Diese Daten geben wir nicht ohne Ihre Einwilligung
weiter.
</p>
</div>
<div>
<h2 className="text-2xl font-bold text-primary mb-4">
4. Server-Log-Dateien
</h2>
<p>
Der Provider der Seiten erhebt und speichert automatisch
Informationen in sogenannten Server-Log-Dateien, die Ihr Browser
automatisch an uns übermittelt. Dies sind: Browsertyp und
Browserversion, verwendetes Betriebssystem, Referrer URL,
Hostname des zugreifenden Rechners, Uhrzeit der Serveranfrage,
IP-Adresse.
</p>
</div>
</div>
</div>
</div>
</div>
<LegalLayout title="Datenschutz">
<Content />
</LegalLayout>
);
}

View File

@@ -1,101 +1,25 @@
"use client";
import React from "react";
import { notFound } from "next/navigation";
import { LegalLayout } from "@/components/LegalLayout";
import { motion } from "framer-motion";
import { TechBackground } from "@/components/TechBackground";
interface PageProps {
params: Promise<{ locale: string }>;
}
export default async function Page({ params }: PageProps) {
const { locale } = await params;
let Content: React.ComponentType;
try {
const mdx = await import(`@/content/${locale}/impressum.mdx`);
Content = mdx.default;
} catch (e) {
notFound();
}
export default function Legal() {
return (
<div className="bg-slate-50 min-h-screen pt-40 pb-20 relative overflow-hidden">
<TechBackground />
<div className="container-custom relative z-10">
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ type: "spring", stiffness: 50, damping: 20 }}
className="max-w-4xl mx-auto bg-white p-8 md:p-12 rounded-[2.5rem] shadow-sm border border-slate-100 relative overflow-hidden group"
>
<div className="tech-corner top-8 left-8 border-t-2 border-l-2 opacity-20" />
<div className="tech-corner bottom-8 right-8 border-b-2 border-r-2 opacity-20" />
<h1 className="text-4xl font-extrabold text-primary mb-8 relative z-10">
Impressum
</h1>
<div className="space-y-8 text-slate-600 leading-relaxed relative z-10">
<div>
<h2 className="text-xl font-bold text-primary mb-4">
Angaben gemäß § 5 TMG
</h2>
<p>
MB Grid Solutions & Services GmbH
<br />
Raiffeisenstraße 22
<br />
73630 Remshalden
</p>
</div>
<div>
<h2 className="text-xl font-bold text-primary mb-4">
Vertreten durch
</h2>
<p>
Michael Bodemer
<br />
Klaus Mintel
</p>
</div>
<div>
<h2 className="text-xl font-bold text-primary mb-4">Kontakt</h2>
<p>
E-Mail:{" "}
<a
href="mailto:info@mb-grid-solutions.com"
className="text-accent hover:underline"
>
info@mb-grid-solutions.com
</a>
<br />
Web:{" "}
<a
href="https://www.mb-grid-solutions.com"
className="text-accent hover:underline"
>
www.mb-grid-solutions.com
</a>
</p>
</div>
<div>
<h2 className="text-xl font-bold text-primary mb-4">
Registereintrag
</h2>
<p>
Eintragung im Handelsregister.
<br />
Registergericht: Amtsgericht Stuttgart
<br />
Registernummer: HRB 803379
</p>
</div>
<div>
<h2 className="text-xl font-bold text-primary mb-4">
Urheberrecht
</h2>
<p>
Alle auf der Website veröffentlichten Texte, Bilder und
sonstigen Informationen unterliegen sofern nicht anders
gekennzeichnet dem Urheberrecht. Jede Vervielfältigung,
Verbreitung, Speicherung, Übermittlung, Wiedergabe bzw.
Weitergabe der Inhalte ohne schriftliche Genehmigung ist
ausdrücklich untersagt.
</p>
</div>
</div>
</motion.div>
</div>
</div>
<LegalLayout title="Impressum">
<Content />
</LegalLayout>
);
}

View File

@@ -5,7 +5,6 @@ import "../globals.css";
import { NextIntlClientProvider } from "next-intl";
import { getMessages } from "next-intl/server";
import { notFound } from "next/navigation";
import { LazyMotion, domAnimation } from "framer-motion";
import AnalyticsProvider from "@/components/analytics/AnalyticsProvider";
import { config } from "@/lib/config";
@@ -143,9 +142,7 @@ export default async function RootLayout({
<body className="antialiased">
<NextIntlClientProvider messages={messages}>
<AnalyticsProvider websiteId={config.analytics.umami.websiteId} />
<LazyMotion features={domAnimation}>
<Layout>{children}</Layout>
</LazyMotion>
<Layout>{children}</Layout>
</NextIntlClientProvider>
</body>
</html>

View File

@@ -1,4 +1,6 @@
import React from "react";
import { Metadata } from "next";
import { notFound } from "next/navigation";
import HomeContent from "@/components/HomeContent";
export const metadata: Metadata = {
@@ -7,6 +9,24 @@ export const metadata: Metadata = {
"Ihr spezialisierter Partner für herstellerneutrale technische Beratung und Projektbegleitung bei Energiekabelprojekten bis 110 kV.",
};
export default function Page() {
return <HomeContent />;
interface PageProps {
params: Promise<{ locale: string }>;
}
export default async function Page({ params }: PageProps) {
const { locale } = await params;
// Dynamically import the MDX content based on locale
let Content: React.ComponentType;
try {
const mdx = await import(`@/content/${locale}/index.mdx`);
Content = mdx.default;
} catch (e) {
// Fallback if MDX is not yet created for this locale
return <HomeContent />;
}
// Render the MDX content.
// We can use mdx-components.tsx to map MDX tags to our high-fidelity sections.
return <Content />;
}

View File

@@ -1,5 +1,6 @@
import React from "react";
import { Metadata } from "next";
import AboutContent from "@/components/AboutContent";
import { notFound } from "next/navigation";
export const metadata: Metadata = {
title: "Über uns",
@@ -7,6 +8,21 @@ export const metadata: Metadata = {
"Erfahren Sie mehr über MB Grid Solutions, unsere Expertise und unser Manifest für technische Exzellenz.",
};
export default function Page() {
return <AboutContent />;
interface PageProps {
params: Promise<{ locale: string }>;
}
export default async function Page({ params }: PageProps) {
const { locale } = await params;
// Dynamically import the MDX content based on locale
let Content: React.ComponentType;
try {
const mdx = await import(`@/content/${locale}/about.mdx`);
Content = mdx.default;
} catch (e) {
notFound();
}
return <Content />;
}