feat: Wabenhain Honig-Shop Anwendung

Vollstaendige Next.js/Prisma-E-Commerce-Anwendung fuer eine Imkerei:
Shop, Warenkorb, Stripe-Checkout, Kundenkonto mit 2FA, Admin-Bereich
(Produkte, Ernten, Chargen, Bestellungen, Gutscheine, admin-editierbare
Website-Inhalte), i18n (DE/EN), Rate-Limiting/CSP/Audit-Log-Haertung,
PostgreSQL- und MySQL-Unterstuetzung ueber austauschbare Prisma-
Treiber-Adapter, Unit-/Komponenten-/E2E-Tests.
This commit is contained in:
Nick Szaunig committed 2026-08-17 14:30:28 +02:00
1 parent 5c76fcc266
commit c7927f2883
240 files changed
+26096 -137

No files matched your search

+101 -15
View File
@@ -1,29 +1,115 @@
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import { Cormorant_Garamond, Inter } from "next/font/google";
import "./globals.css";
import { Header, Footer, CartDrawer } from "@/components/site-chrome";
import { CookieConsentBanner } from "@/components/cookie-consent";
import { siteConfig } from "@/lib/site-config";
import { getCompanyInfo } from "@/lib/company-info";
import { getLocale } from "@/i18n/locale";
import { getDictionary } from "@/i18n/get-dictionary";
import { I18nProvider } from "@/i18n/i18n-provider";
const geistSans = Geist({
variable: "--font-geist-sans",
const cormorant = Cormorant_Garamond({
variable: "--font-cormorant",
subsets: ["latin"],
weight: ["400", "500", "600"],
});
const inter = Inter({
variable: "--font-inter",
subsets: ["latin"],
});
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});
export async function generateMetadata(): Promise<Metadata> {
const company = await getCompanyInfo();
const title = `${company.name} | Regionaler Honig aus Altenmünster`;
return {
metadataBase: new URL(`https://${siteConfig.domain}`),
title: { default: title, template: `%s | ${company.name}` },
description: company.description,
openGraph: {
title,
description: company.description,
siteName: company.name,
locale: "de_DE",
type: "website",
},
twitter: { card: "summary_large_image", title, description: company.description },
};
}
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default async function RootLayout({ children }: LayoutProps<"/">) {
const locale = await getLocale();
const dict = getDictionary(locale);
const company = await getCompanyInfo();
// LocalBusiness-Schema fuer die gesamte Seite (einmal im Root-Layout statt
// auf jeder Seite einzeln) — hilft insbesondere bei lokaler Google-Suche
// ("Honig Altenmünster") und der Kartenanzeige. telephone ist hier bewusst
// NICHT enthalten: solange niemand im Admin-Bereich (Inhalte) eine echte
// Telefonnummer eingetragen hat, bliebe sonst der site-config-Platzhalter
// als strukturierte Daten faelschlich indexiert.
const localBusinessJsonLd = {
"@context": "https://schema.org",
"@type": "Store",
"@id": `https://${siteConfig.domain}/#business`,
name: company.name,
description: company.description,
url: `https://${siteConfig.domain}`,
address: {
"@type": "PostalAddress",
streetAddress: company.address.street,
postalCode: company.address.zip,
addressLocality: company.address.city,
addressCountry: "DE",
},
};
// Ermoeglicht Googles Sitelinks-Suchfeld direkt in den Suchergebnissen — die
// Ziel-URL entspricht der tatsaechlich vorhandenen Shop-Suche (?q=...).
const websiteJsonLd = {
"@context": "https://schema.org",
"@type": "WebSite",
name: company.name,
url: `https://${siteConfig.domain}`,
potentialAction: {
"@type": "SearchAction",
target: `https://${siteConfig.domain}/shop?q={search_term_string}`,
"query-input": "required name=search_term_string",
},
};
export default function RootLayout({ children }: LayoutProps<"/">) {
return (
<html
lang="en"
className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}
lang={locale}
data-scroll-behavior="smooth"
className={`${cormorant.variable} ${inter.variable} h-full antialiased`}
>
<body className="min-h-full flex flex-col">{children}</body>
<body className="min-h-full flex flex-col bg-background text-ink">
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(localBusinessJsonLd).replace(/</g, "\\u003c") }}
/>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(websiteJsonLd).replace(/</g, "\\u003c") }}
/>
<I18nProvider locale={locale}>
<a
href="#main-content"
className="sr-only focus:not-sr-only focus:fixed focus:top-4 focus:left-4 focus:z-50 focus:bg-forest focus:text-cream focus:px-4 focus:py-2 focus:text-sm"
>
{dict.nav.skipToContent}
</a>
<Header companyName={company.name} />
<main id="main-content" className="flex-1">
{children}
</main>
<Footer companyInfo={company} />
<CartDrawer />
<CookieConsentBanner />
</I18nProvider>
</body>
</html>
);
}