feat: complete CMS v2 with Docker, Fischerei, Meetings, Verband modules + UX audit fixes
Some checks failed
Workflow / ʦ TypeScript (push) Failing after 6m26s
Workflow / ⚫️ Test (push) Has been skipped

Major changes:
- Docker Compose: full Supabase stack (11 services) equivalent to supabase CLI
- Fischerei module: 16 DB tables, waters/species/stocking/catch books/competitions
- Sitzungsprotokolle module: meeting protocols, agenda items, task tracking
- Verbandsverwaltung module: federation management, member clubs, contacts, fees
- Per-account module activation via Modules page toggle
- Site Builder: live CMS data in Puck blocks (courses, events, membership registration)
- Public registration APIs: course signup, event registration, membership application
- Document generation: PDF member cards, Excel reports, HTML labels
- Landing page: real Com.BISS content (no filler text)
- UX audit fixes: AccountNotFound component, shared status badges, confirm dialog,
  pagination, duplicate heading removal, emoji→badge replacement, a11y fixes
- QA: healthcheck fix, API auth fix, enum mismatch fix, password required attribute
This commit is contained in:
Zaid Marzguioui
2026-03-31 16:35:46 +02:00
parent 16648c92eb
commit ebd0fd4638
176 changed files with 17133 additions and 981 deletions

View File

@@ -15,7 +15,7 @@ import {
import { getSupabaseServerClient } from '@kit/supabase/server-client';
import { Badge } from '@kit/ui/badge';
import { Button } from '@kit/ui/button';
import {
Card,
CardContent,
@@ -32,7 +32,9 @@ import { createBookingManagementApi } from '@kit/booking-management/api';
import { createEventManagementApi } from '@kit/event-management/api';
import { CmsPageShell } from '~/components/cms-page-shell';
import { EmptyState } from '~/components/empty-state';
import { StatsCard } from '~/components/stats-card';
import { AccountNotFound } from '~/components/account-not-found';
interface TeamAccountHomePageProps {
params: Promise<{ account: string }>;
@@ -50,7 +52,7 @@ export default async function TeamAccountHomePage({
.eq('slug', account)
.single();
if (!acct) return <div>Konto nicht gefunden</div>;
if (!acct) return <AccountNotFound />;
// Load all stats in parallel with allSettled for resilience
const [
@@ -157,7 +159,15 @@ export default async function TeamAccountHomePage({
href={`/home/${account}/bookings/${String(booking.id)}`}
className="text-sm font-medium hover:underline"
>
Buchung #{String(booking.id).slice(0, 8)}
Buchung{' '}
{booking.check_in
? new Date(
String(booking.check_in),
).toLocaleDateString('de-DE', {
day: '2-digit',
month: 'short',
})
: '—'}
</Link>
<p className="text-xs text-muted-foreground">
{booking.check_in
@@ -213,12 +223,11 @@ export default async function TeamAccountHomePage({
))}
{bookings.data.length === 0 && events.data.length === 0 && (
<div className="flex flex-col items-center justify-center py-8 text-center">
<Activity className="h-8 w-8 text-muted-foreground/50" />
<p className="mt-2 text-sm text-muted-foreground">
Noch keine Aktivitäten vorhanden
</p>
</div>
<EmptyState
icon={<Activity className="h-8 w-8" />}
title="Noch keine Aktivitäten"
description="Aktuelle Buchungen und Veranstaltungen werden hier angezeigt."
/>
)}
</div>
</CardContent>
@@ -231,69 +240,59 @@ export default async function TeamAccountHomePage({
<CardDescription>Häufig verwendete Aktionen</CardDescription>
</CardHeader>
<CardContent className="flex flex-col gap-2">
<Link href={`/home/${account}/members-cms/new`}>
<Button
variant="outline"
className="w-full justify-between"
>
<span className="flex items-center gap-2">
<UserPlus className="h-4 w-4" />
Neues Mitglied
</span>
<ArrowRight className="h-4 w-4" />
</Button>
<Link
href={`/home/${account}/members-cms/new`}
className="inline-flex w-full items-center justify-between gap-2 rounded-lg border border-border bg-background px-4 py-2 text-sm font-medium hover:bg-muted hover:text-foreground transition-all"
>
<span className="flex items-center gap-2">
<UserPlus className="h-4 w-4" />
Neues Mitglied
</span>
<ArrowRight className="h-4 w-4" />
</Link>
<Link href={`/home/${account}/courses/new`}>
<Button
variant="outline"
className="w-full justify-between"
>
<span className="flex items-center gap-2">
<GraduationCap className="h-4 w-4" />
Neuer Kurs
</span>
<ArrowRight className="h-4 w-4" />
</Button>
<Link
href={`/home/${account}/courses/new`}
className="inline-flex w-full items-center justify-between gap-2 rounded-lg border border-border bg-background px-4 py-2 text-sm font-medium hover:bg-muted hover:text-foreground transition-all"
>
<span className="flex items-center gap-2">
<GraduationCap className="h-4 w-4" />
Neuer Kurs
</span>
<ArrowRight className="h-4 w-4" />
</Link>
<Link href={`/home/${account}/newsletter/new`}>
<Button
variant="outline"
className="w-full justify-between"
>
<span className="flex items-center gap-2">
<Mail className="h-4 w-4" />
Newsletter erstellen
</span>
<ArrowRight className="h-4 w-4" />
</Button>
<Link
href={`/home/${account}/newsletter/new`}
className="inline-flex w-full items-center justify-between gap-2 rounded-lg border border-border bg-background px-4 py-2 text-sm font-medium hover:bg-muted hover:text-foreground transition-all"
>
<span className="flex items-center gap-2">
<Mail className="h-4 w-4" />
Newsletter erstellen
</span>
<ArrowRight className="h-4 w-4" />
</Link>
<Link href={`/home/${account}/bookings/new`}>
<Button
variant="outline"
className="w-full justify-between"
>
<span className="flex items-center gap-2">
<BedDouble className="h-4 w-4" />
Neue Buchung
</span>
<ArrowRight className="h-4 w-4" />
</Button>
<Link
href={`/home/${account}/bookings/new`}
className="inline-flex w-full items-center justify-between gap-2 rounded-lg border border-border bg-background px-4 py-2 text-sm font-medium hover:bg-muted hover:text-foreground transition-all"
>
<span className="flex items-center gap-2">
<BedDouble className="h-4 w-4" />
Neue Buchung
</span>
<ArrowRight className="h-4 w-4" />
</Link>
<Link href={`/home/${account}/events/new`}>
<Button
variant="outline"
className="w-full justify-between"
>
<span className="flex items-center gap-2">
<Plus className="h-4 w-4" />
Neue Veranstaltung
</span>
<ArrowRight className="h-4 w-4" />
</Button>
<Link
href={`/home/${account}/events/new`}
className="inline-flex w-full items-center justify-between gap-2 rounded-lg border border-border bg-background px-4 py-2 text-sm font-medium hover:bg-muted hover:text-foreground transition-all"
>
<span className="flex items-center gap-2">
<Plus className="h-4 w-4" />
Neue Veranstaltung
</span>
<ArrowRight className="h-4 w-4" />
</Link>
</CardContent>
</Card>
@@ -317,10 +316,11 @@ export default async function TeamAccountHomePage({
aktiv
</p>
</div>
<Link href={`/home/${account}/bookings`}>
<Button variant="ghost" size="icon">
<ArrowRight className="h-4 w-4" />
</Button>
<Link
href={`/home/${account}/bookings`}
className="inline-flex h-9 w-9 items-center justify-center rounded-lg text-sm font-medium hover:bg-muted hover:text-foreground transition-all"
>
<ArrowRight className="h-4 w-4" />
</Link>
</div>
</CardContent>
@@ -343,10 +343,11 @@ export default async function TeamAccountHomePage({
aktiv
</p>
</div>
<Link href={`/home/${account}/events`}>
<Button variant="ghost" size="icon">
<ArrowRight className="h-4 w-4" />
</Button>
<Link
href={`/home/${account}/events`}
className="inline-flex h-9 w-9 items-center justify-center rounded-lg text-sm font-medium hover:bg-muted hover:text-foreground transition-all"
>
<ArrowRight className="h-4 w-4" />
</Link>
</div>
</CardContent>
@@ -366,10 +367,11 @@ export default async function TeamAccountHomePage({
von {courseStats.totalCourses} insgesamt
</p>
</div>
<Link href={`/home/${account}/courses`}>
<Button variant="ghost" size="icon">
<ArrowRight className="h-4 w-4" />
</Button>
<Link
href={`/home/${account}/courses`}
className="inline-flex h-9 w-9 items-center justify-center rounded-lg text-sm font-medium hover:bg-muted hover:text-foreground transition-all"
>
<ArrowRight className="h-4 w-4" />
</Link>
</div>
</CardContent>