Workstream 1: Projekt-Fundament & Design-System (Phase 0)
Greenfield-Next.js-15-App-Router-Gerüst (TS strict) mit: - Route-Groups (auth)/(app) inkl. loading/error/not-found je Group; Guard-Slot-Kommentar im (app)/layout.tsx (vom Auth-WS zu füllen). - "Amtlich"/Netzknoten-Designsystem: Tailwind-Tokens (Navy #1B3A5B, Signalrot #E2231A, Anthrazit, Nebelgrau, bereit/Wartung), tabular-nums, Serif-Display/Inter-Sans via CSS-Variablen, Inline-SVG-Logo. - Radix-Basiskomponenten (button/input/label/badge/tabs/dialog/select/ switch/slider); StatusBadge entspricht asset_status. - Kanonisches src/lib/env.ts (Zod, Fail-Fast) mit ALLEN DB-/Auth-/Geo-Slots inkl. AUTH_URL; isHttps-Ableitung. Zentrale i18n-Tabelle de.ts + t(). - Drizzle-Setup: client.ts (Pool-Singleton), leeres schema/index.ts-Barrel (KEIN Migrations-Eigentümer), drizzle.config.ts, .env.example. - next.config.ts: output:standalone, experimental.authInterrupts, Security-Header. Vitest + Fail-Fast-Env-Test (TDD, 5/5 grün). Bewusst KEINE Auth-Logik und KEINE fachlichen Tabellen. Verifikation: typecheck/lint/test grün; npm run build erzeugt .next/standalone/server.js; curl /anmelden -> lang="de" + FlorianNetz. next/font/google durch CSS-Variablen ersetzt (air-gapped-Build). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
15
src/app/(app)/error.tsx
Normal file
15
src/app/(app)/error.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
"use client";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { t } from "@/lib/i18n/de";
|
||||
|
||||
export default function AppError({ reset }: { error: Error; reset: () => void }) {
|
||||
return (
|
||||
<div className="mx-auto max-w-md py-16 text-center">
|
||||
<p className="text-anthrazit">{t("fehler.allgemein")}</p>
|
||||
<Button onClick={reset} className="mt-4">
|
||||
{t("aktion.erneutVersuchen")}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
21
src/app/(app)/layout.tsx
Normal file
21
src/app/(app)/layout.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import { AppShell } from "@/components/layout/app-shell";
|
||||
|
||||
// Gated App-Shell.
|
||||
//
|
||||
// GUARD-SLOT (Default-deny, dreifach — Querschnittsstandard 1):
|
||||
// Der Auth-Workstream (Workstream 3) fügt hier als ALLERERSTE Anweisung den
|
||||
// serverseitigen Session-Guard ein, z. B.:
|
||||
//
|
||||
// import { requireSession } from "@/lib/auth/guards";
|
||||
// ...
|
||||
// await requireSession(); // leitet anonyme Aufrufe auf /anmelden um
|
||||
//
|
||||
// Lese-Seiten dürfen sich NICHT allein auf die Middleware verlassen.
|
||||
export default async function AppLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
// await requireSession(); // <- vom Auth-Workstream zu aktivieren
|
||||
return <AppShell>{children}</AppShell>;
|
||||
}
|
||||
9
src/app/(app)/loading.tsx
Normal file
9
src/app/(app)/loading.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import { t } from "@/lib/i18n/de";
|
||||
|
||||
export default function AppLoading() {
|
||||
return (
|
||||
<div className="py-16 text-center text-sm text-anthrazit/70">
|
||||
{t("aktion.laden")}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
14
src/app/(app)/not-found.tsx
Normal file
14
src/app/(app)/not-found.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import Link from "next/link";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { t } from "@/lib/i18n/de";
|
||||
|
||||
export default function AppNotFound() {
|
||||
return (
|
||||
<div className="mx-auto max-w-md py-16 text-center">
|
||||
<p className="text-anthrazit">{t("fehler.nichtGefunden")}</p>
|
||||
<Button asChild className="mt-4">
|
||||
<Link href="/start">{t("aktion.zurueckZurStartseite")}</Link>
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
24
src/app/(app)/start/page.tsx
Normal file
24
src/app/(app)/start/page.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import { StatusBadge } from "@/components/ui/badge";
|
||||
import { t } from "@/lib/i18n/de";
|
||||
|
||||
export default function StartPage() {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<header>
|
||||
<h1 className="font-display text-2xl text-navy">{t("app.name")}</h1>
|
||||
<p className="mt-1 text-sm text-anthrazit/70">
|
||||
Vernetzte Übersicht der Fahrzeuge und Geräte der Feuerwehren.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<section className="rounded-md border border-rand bg-white p-6">
|
||||
<h2 className="font-display text-lg text-navy">Statusübersicht</h2>
|
||||
<div className="mt-4 flex flex-wrap items-center gap-3">
|
||||
<StatusBadge status="einsatzbereit" />
|
||||
<StatusBadge status="wartung" />
|
||||
<StatusBadge status="ausser_dienst" />
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user