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

+107
View File
@@ -0,0 +1,107 @@
import Link from "next/link";
import type { ComponentPropsWithoutRef, ReactNode } from "react";
export function Container({
id,
className = "",
children,
}: {
id?: string;
className?: string;
children: ReactNode;
}) {
return (
<div id={id} className={`mx-auto w-full max-w-6xl px-6 md:px-10 ${className}`}>
{children}
</div>
);
}
type ButtonVariant = "primary" | "secondary" | "ghost";
const buttonStyles: Record<ButtonVariant, string> = {
primary:
"bg-forest text-cream hover:bg-[#1a271f] border border-forest",
secondary:
"bg-transparent text-forest border border-forest hover:bg-forest hover:text-cream",
ghost: "bg-transparent text-ink border border-ink/20 hover:border-ink",
};
interface LinkButtonProps extends ComponentPropsWithoutRef<typeof Link> {
variant?: ButtonVariant;
}
export function LinkButton({
variant = "primary",
className = "",
children,
...props
}: LinkButtonProps) {
return (
<Link
className={`inline-flex items-center justify-center gap-2 px-7 py-3 text-sm tracking-wide-caps transition-[color,background-color,border-color,transform] duration-300 active:scale-[0.97] ${buttonStyles[variant]} ${className}`}
{...props}
>
{children}
</Link>
);
}
interface ButtonProps extends ComponentPropsWithoutRef<"button"> {
variant?: ButtonVariant;
}
export function Button({
variant = "primary",
className = "",
children,
...props
}: ButtonProps) {
return (
<button
className={`inline-flex items-center justify-center gap-2 px-7 py-3 text-sm tracking-wide-caps transition-[color,background-color,border-color,transform] duration-300 active:scale-[0.97] disabled:cursor-not-allowed disabled:opacity-50 disabled:active:scale-100 ${buttonStyles[variant]} ${className}`}
{...props}
>
{children}
</button>
);
}
export function SectionHeading({
eyebrow,
title,
subtitle,
align = "left",
className = "",
level = "h2",
tone = "default",
}: {
eyebrow?: string;
title: string;
subtitle?: string;
align?: "left" | "center";
className?: string;
/** "h1" fuer Seiten, auf denen diese Komponente die einzige/primaere Ueberschrift ist (SEO-Heading-Hierarchie). Default h2, da die meisten Verwendungen einzelne Abschnitte innerhalb einer Seite mit eigenem h1 sind. */
level?: "h1" | "h2";
/** "inverted" fuer Abschnitte auf dunklem Grund (z.B. bg-forest) — steuert die Eyebrow-Farbe fuer ausreichenden Kontrast. */
tone?: "default" | "inverted";
}) {
const Heading = level;
return (
<div
className={`${align === "center" ? "text-center mx-auto" : "text-left"} max-w-2xl ${className}`}
>
{eyebrow ? (
<p
className={`tracking-wide-caps text-xs mb-3 ${tone === "inverted" ? "text-honey-light" : "text-honey-dark"}`}
>
{eyebrow}
</p>
) : null}
<Heading className="text-3xl md:text-4xl text-forest">{title}</Heading>
{subtitle ? (
<p className="mt-4 text-ink/70 leading-relaxed">{subtitle}</p>
) : null}
</div>
);
}