feat: Sprint 4 complete — frontend MVP (admin dashboard + member portal)

Shadboard starter-kit (Next.js 15 + React 19 + shadcn/ui + Tailwind 4)

Sprint 4.a — Admin Dashboard:
- Auth: NextAuth.js v5, login page, middleware, token rotation
- Dashboard: KPI cards, Recharts stock chart, quick actions
- Members: TanStack Table (search/sort/paginate), add/edit forms
- Distributions: multi-step form, real-time quota check, history
- Stock: batch management, recall dialog, bar chart
- Reports: monthly/member-list/recall, PDF/CSV download, preview

Sprint 4.b — Member Portal:
- Separate route group with top-nav layout (mobile-first)
- Quota dashboard with radial SVG progress indicators
- Distribution history with month filter
- Profile/settings with password change

Cross-cutting:
- i18n: German (default) + English via next-intl
- Dark + light mode (next-themes, user-togglable)
- Playwright E2E tests (6/6 green)
- Docker multi-stage build (node:22-alpine)
- API proxy via Next.js rewrites

Tech: Next.js 15.2.8, React 19, Tailwind 4, NextAuth v5,
TanStack Table, Recharts, Zod, React Hook Form, Playwright
This commit is contained in:
Patrick Plate
2026-06-12 17:18:38 +02:00
parent a1d4ba44e3
commit fe6e96dd3f
143 changed files with 23568 additions and 0 deletions
+88
View File
@@ -0,0 +1,88 @@
export interface ClubStats {
totalMembers: number
activeMembers: number
distributionsToday: number
gramsDistributedToday: number
totalStockGrams: number
monthlyQuotaUsagePercent: number
}
export interface Distribution {
id: string
memberId: string
memberName: string
strainName: string
amountGrams: number
recordedBy: string
recordedAt: string // ISO 8601
}
export interface DistributionRecord {
id: string
memberId: string
memberName: string
batchId: string
strainName: string
amountGrams: number
recordedBy: string
recordedAt: string // ISO 8601
status: "COMPLETED" // immutable
}
export interface QuotaStatus {
dailyUsedGrams: number
dailyLimitGrams: number // always 25
monthlyUsedGrams: number
monthlyLimitGrams: number // 50 for ≥21, 30 for <21
isUnder21: boolean
}
export interface AvailableBatch {
id: string
strainName: string
availableGrams: number
thcPercent: number
status: "AVAILABLE"
}
export interface BatchSummary {
id: string
strainName: string
availableGrams: number
status: "AVAILABLE" | "RECALLED" | "DEPLETED"
}
export interface Batch {
id: string
strainName: string
thcPercent: number
cbdPercent: number
totalGrams: number
availableGrams: number
status: "AVAILABLE" | "RECALLED" | "DEPLETED"
supplier: string
harvestDate: string // ISO 8601
receivedAt: string // ISO 8601
notes?: string
}
export interface Strain {
id: string
name: string
defaultThcPercent: number
defaultCbdPercent: number
}
export interface Member {
id: string
firstName: string
lastName: string
email: string
dateOfBirth: string // ISO 8601
phone?: string
memberNumber: string
status: "ACTIVE" | "SUSPENDED" | "EXPELLED"
joinedAt: string // ISO 8601
monthlyQuotaUsedPercent: number
notes?: string
}
+30
View File
@@ -0,0 +1,30 @@
import type { DefaultSession } from "next-auth"
declare module "next-auth" {
interface Session {
user: {
role: string
clubId: string
} & DefaultSession["user"]
error?: string
}
interface User {
role: string
clubId: string
accessToken: string
refreshToken: string
expiresAt: number
}
}
declare module "next-auth/jwt" {
interface JWT {
role?: string
clubId?: string
accessToken?: string
refreshToken?: string
expiresAt?: number
error?: string
}
}