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

+22
View File
@@ -0,0 +1,22 @@
"use server";
import { cookies } from "next/headers";
import { revalidatePath } from "next/cache";
import { isLocale, LOCALE_COOKIE } from "./config";
const ONE_YEAR_SECONDS = 60 * 60 * 24 * 365;
/** Setzt die Sprachpraeferenz per Cookie und macht den gesamten Seitenbaum ungueltig, damit alle Server Components sofort in der neuen Sprache neu rendern. */
export async function setLocaleAction(formData: FormData): Promise<void> {
const locale = formData.get("locale");
if (typeof locale !== "string" || !isLocale(locale)) return;
const cookieStore = await cookies();
cookieStore.set(LOCALE_COOKIE, locale, {
path: "/",
maxAge: ONE_YEAR_SECONDS,
sameSite: "lax",
});
revalidatePath("/", "layout");
}
+21
View File
@@ -0,0 +1,21 @@
// Zentrale Sprachkonfiguration: um eine weitere Sprache zu ergaenzen, genuegt
// es, sie hier zur locales-Liste hinzuzufuegen und eine passende Datei unter
// dictionaries/<code>.ts anzulegen (siehe dictionaries/en.ts als Vorlage) —
// get-dictionary.ts, der LanguageSwitcher und alle Verbraucher der
// Dictionary lesen die Liste dynamisch, kein Code an anderer Stelle muss
// angepasst werden.
export const locales = ["de", "en"] as const;
export type Locale = (typeof locales)[number];
export const defaultLocale: Locale = "de";
export const localeLabels: Record<Locale, string> = {
de: "Deutsch",
en: "English",
};
export const LOCALE_COOKIE = "locale";
export function isLocale(value: string | undefined | null): value is Locale {
return !!value && (locales as readonly string[]).includes(value);
}
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+13
View File
@@ -0,0 +1,13 @@
import type { Locale } from "./config";
import type { Dictionary } from "./dictionary-type";
import { de } from "./dictionaries/de";
import { en } from "./dictionaries/en";
// Neue Sprache ergaenzen: hier den Import + Eintrag hinzufuegen (und die
// Sprache in i18n/config.ts zur locales-Liste ergaenzen) — mehr Stellen
// muessen dafuer nicht angepasst werden.
const dictionaries: Record<Locale, Dictionary> = { de, en };
export function getDictionary(locale: Locale): Dictionary {
return dictionaries[locale];
}
+34
View File
@@ -0,0 +1,34 @@
"use client";
import { createContext, useContext, type ReactNode } from "react";
import type { Locale } from "./config";
import type { Dictionary } from "./dictionary-type";
import { getDictionary } from "./get-dictionary";
interface I18nContextValue {
locale: Locale;
t: Dictionary;
}
const I18nContext = createContext<I18nContextValue | null>(null);
/**
* Stellt Locale + Uebersetzungswoerterbuch fuer Client Components bereit.
* Nimmt bewusst nur die Locale (ein String) als Prop entgegen und loest das
* Dictionary selbst per getDictionary() auf, statt das fertige Objekt vom
* Server-Layout hereinzureichen — Dictionaries enthalten Funktionswerte
* (z.B. fuer Pluralformen), und Funktionen lassen sich nicht als Props von
* Server an Client Components uebergeben (RSC-Serialisierungsgrenze).
* Server Components rufen stattdessen direkt getDictionary(await
* getLocale()) auf, ohne diesen Provider zu benoetigen.
*/
export function I18nProvider({ locale, children }: { locale: Locale; children: ReactNode }) {
const dict = getDictionary(locale);
return <I18nContext.Provider value={{ locale, t: dict }}>{children}</I18nContext.Provider>;
}
export function useI18n(): I18nContextValue {
const ctx = useContext(I18nContext);
if (!ctx) throw new Error("useI18n muss innerhalb von <I18nProvider> verwendet werden.");
return ctx;
}
+9
View File
@@ -0,0 +1,9 @@
import { cookies } from "next/headers";
import { defaultLocale, isLocale, LOCALE_COOKIE, type Locale } from "./config";
/** Serverseitige Spracherkennung ueber das Locale-Cookie, mit Fallback auf Deutsch. */
export async function getLocale(): Promise<Locale> {
const cookieStore = await cookies();
const value = cookieStore.get(LOCALE_COOKIE)?.value;
return isLocale(value) ? value : defaultLocale;
}