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:
@@ -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"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user