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
+296
View File
@@ -0,0 +1,296 @@
{
"common": {
"appName": "CannaManage",
"loading": "Loading...",
"save": "Save",
"cancel": "Cancel",
"delete": "Delete",
"edit": "Edit",
"create": "Create",
"search": "Search",
"filter": "Filter",
"export": "Export",
"back": "Back",
"next": "Next",
"confirm": "Confirm",
"yes": "Yes",
"no": "No",
"noData": "No data available"
},
"nav": {
"dashboard": "Dashboard",
"members": "Members",
"stock": "Stock",
"distributions": "Distributions",
"compliance": "Compliance",
"reports": "Reports",
"settings": "Settings",
"staff": "Staff",
"portal": "Member Portal"
},
"auth": {
"login": "Sign In",
"logout": "Sign Out",
"email": "Email address",
"password": "Password",
"forgotPassword": "Forgot password?",
"resetPassword": "Reset Password",
"loginButton": "Sign In",
"loggingIn": "Signing in...",
"loginSubtitle": "Sign in to your cannabis club",
"invalidCredentials": "Invalid email address or password.",
"networkError": "Connection error. Please try again.",
"sessionExpired": "Your session has expired. Please sign in again.",
"emailInvalid": "Please enter a valid email address.",
"passwordRequired": "Please enter your password.",
"passwordTooShort": "Password must be at least 8 characters.",
"footerText": "Secure management for your cannabis cultivation club"
},
"dashboard": {
"title": "Dashboard",
"activeMembers": "Active Members",
"distributionsToday": "Distributions Today",
"stockLevel": "Stock Level",
"monthlyQuota": "Monthly Quota",
"quickActions": "Quick Actions",
"newDistribution": "New Distribution",
"addMember": "Add Member",
"recentDistributions": "Recent Distributions",
"stockByStrain": "Stock by Strain",
"date": "Date",
"member": "Member",
"strain": "Strain",
"amount": "Amount (g)",
"staff": "Staff",
"grams": "g",
"today": "Today",
"trend": "+{value}% vs last month",
"quotaUsed": "{value}% used",
"distributionCount": "{count} distributions, {grams}g"
},
"members": {
"title": "Member Management",
"addMember": "Add Member",
"name": "Name",
"email": "Email",
"status": "Status",
"memberSince": "Member Since",
"quota": "Quota",
"actions": "Actions",
"edit": "Edit",
"active": "Active",
"suspended": "Suspended",
"expelled": "Expelled",
"back": "Back to List",
"save": "Save",
"create": "Create Member",
"firstName": "First Name",
"lastName": "Last Name",
"dateOfBirth": "Date of Birth",
"phone": "Phone",
"memberNumber": "Member Number",
"joinedAt": "Joined At",
"notes": "Notes",
"notesPlaceholder": "Optional notes about the member...",
"under21Warning": "Under 21 — reduced quota (30g/month)",
"ageError": "Members must be at least 18 years old.",
"saved": "Changes saved.",
"created": "Member created successfully.",
"search": "Search name or email...",
"perPage": "Per page",
"showing": "{from}{to} of {total}",
"previous": "Previous",
"next": "Next",
"noResults": "No members found.",
"notFound": "Member not found.",
"personalInfo": "Personal Information",
"membershipInfo": "Membership"
},
"stock": {
"title": "Stock & Batches",
"newBatch": "New Batch",
"stockOverview": "Stock Overview",
"batchId": "Batch ID",
"strain": "Strain",
"thc": "THC %",
"cbd": "CBD %",
"status": "Status",
"available": "Available",
"availableGrams": "Available (g)",
"receivedAt": "Received",
"actions": "Actions",
"statusAvailable": "Available",
"statusRecalled": "Recalled",
"statusDepleted": "Depleted",
"recall": "Recall",
"recallConfirm": "Really recall this batch? All open distributions with this batch will be blocked.",
"recallTitle": "Recall Batch",
"recallSuccess": "Batch recalled.",
"totalBatches": "Total Batches",
"availableStock": "Available Stock",
"recalledBatches": "Recalled Batches",
"strainCount": "Strains",
"filterAll": "All",
"filterAvailable": "Available only",
"filterRecalled": "Recalled only",
"addBatch": "Add Batch",
"strainName": "Strain Name",
"amount": "Amount (g)",
"supplier": "Supplier / Origin",
"harvestDate": "Harvest Date",
"notes": "Notes",
"notesPlaceholder": "Optional notes about the batch...",
"created": "Batch created successfully.",
"grams": "g",
"confirmRecall": "Confirm Recall",
"lowStock": "Low"
},
"distributions": {
"title": "Distributions",
"newDistribution": "New Distribution",
"todaySummary": "Today: {count} distributions, {grams}g distributed",
"dateTime": "Date/Time",
"member": "Member",
"strain": "Strain",
"amount": "Amount (g)",
"staff": "Staff",
"status": "Status",
"completed": "Completed",
"locked": "Locked (immutable)",
"filterToday": "Today",
"filterWeek": "This Week",
"filterMonth": "This Month",
"searchMember": "Search member...",
"step1": "Select Member",
"step2": "Check Quota",
"step3": "Strain & Amount",
"step4": "Confirmation",
"selectMember": "Search member (name or number)...",
"memberBlocked": "Member is blocked — distribution not possible.",
"under21Info": "Reduced quota: 30g/month (under 21)",
"dailyRemaining": "Daily remaining",
"monthlyRemaining": "Monthly remaining",
"selectBatch": "Select batch",
"available": "available",
"amountLabel": "Amount in grams",
"exceedsDaily": "Exceeds daily limit ({limit}g).",
"exceedsMonthly": "Exceeds monthly limit ({limit}g).",
"exceedsBatch": "Insufficient stock in this batch.",
"confirm": "Confirm Distribution",
"summary": "Summary",
"success": "Distribution recorded successfully.",
"grams": "g",
"date": "Date",
"monthlyQuota": "Monthly Quota",
"remaining": "Remaining"
},
"reports": {
"title": "Reports",
"monthly": "Monthly Report",
"monthlyDesc": "Overview of all distributions in the selected month, including member quotas and stock changes.",
"memberList": "Member List",
"memberListDesc": "Complete member list with status, quota utilization and contact details.",
"recall": "Recall Report",
"recallDesc": "All batches with recall status and affected distributions for regulatory reporting.",
"downloadPdf": "Download as PDF",
"downloadCsv": "Download as CSV",
"preview": "Show Preview",
"generating": "Generating report...",
"downloaded": "{name} downloaded.",
"selectMonth": "Select month",
"selectStatus": "Filter by status",
"allStatuses": "All",
"activeOnly": "Active",
"suspendedOnly": "Suspended",
"dateFrom": "From",
"dateTo": "To",
"previewTitle": "Report Preview",
"totalDistributions": "Total Distributions",
"totalGrams": "Total Grams",
"uniqueMembers": "Unique Members",
"averagePerMember": "Avg per Member",
"topStrains": "Top Strains",
"affectedDistributions": "Affected Distributions",
"affectedMembers": "Affected Members",
"recalledBatches": "Recalled Batches",
"close": "Close",
"complianceNote": "This report is suitable for submission to the responsible authority.",
"complianceBadge": "§19 KCanG compliant",
"auditTrail": "All reports are generated with timestamps. The underlying distribution data is immutable (audit trail).",
"memberNumber": "No.",
"name": "Name",
"status": "Status",
"joinedAt": "Joined",
"usage": "Usage",
"strain": "Strain",
"grams": "Grams",
"percent": "Share",
"batchId": "Batch ID",
"recalledAt": "Recalled on",
"reason": "Reason",
"distributed": "Distributed",
"original": "Original"
},
"portal": {
"title": "My Area",
"login": "Member Login",
"loginSubtitle": "Sign in to the member portal",
"email": "Email address",
"password": "Password",
"loginButton": "Sign In",
"loggingIn": "Signing in...",
"invalidCredentials": "Invalid email address or password.",
"networkError": "Connection error. Please try again.",
"welcome": "Welcome back, {name}!",
"dashboard": "Overview",
"quota": "My Quota",
"history": "Distribution History",
"profile": "Profile",
"settings": "Settings",
"logout": "Sign Out",
"dailyQuota": "Daily Quota",
"monthlyQuota": "Monthly Quota",
"remaining": "remaining",
"used": "used",
"of": "of",
"lastDistribution": "Last Distribution",
"noDistributions": "No distributions this month yet.",
"memberSince": "Member since",
"memberNumber": "Member number",
"nextAvailable": "Next available",
"nextAvailableTomorrow": "Tomorrow at 00:00",
"changePassword": "Change Password",
"currentPassword": "Current Password",
"newPassword": "New Password",
"confirmPassword": "Confirm Password",
"passwordChanged": "Password changed successfully.",
"passwordMismatch": "Passwords do not match.",
"club": "My Club",
"quotaWarning": "Warning: You have already used {percent}% of your monthly quota.",
"under21Notice": "For members under 21: Reduced quota of 30g/month (§19 Abs. 3 KCanG).",
"grams": "g",
"date": "Date",
"strain": "Strain",
"amount": "Amount",
"recordedBy": "Recorded by",
"noHistory": "No distributions recorded yet.",
"personalInfo": "Personal Information",
"language": "Language",
"theme": "Theme",
"themeLight": "Light",
"themeDark": "Dark",
"themeSystem": "System",
"german": "German",
"english": "English",
"quickInfo": "Quick Info",
"todayAvailable": "Available today",
"monthAvailable": "Available this month",
"limitReached": "Limit reached",
"pagination": "{from}{to} of {total}",
"previous": "Previous",
"next": "Next",
"allMonths": "All months",
"footerText": "Cannabis cultivation club — Secure member management",
"adminLogin": "Go to Admin Login"
}
}