38 KiB
CannaManage — Wireframes & UI Mockups
Phase 4a | Document 6 of 7
Date: 2026-04-06
Stack: Spring Boot 3.x · React/Vite SPA · PostgreSQL
Table of Contents
- Design System Overview
- Admin Portal Screens
- Member Portal Screens
- Navigation & Information Architecture
- Responsive Design Notes
- Accessibility
1. Design System Overview
1.1 Color Palette
| Token | Hex | Usage |
|---|---|---|
--color-primary |
#2D5016 |
Sidebar background, primary buttons, active nav items |
--color-primary-medium |
#4A7C28 |
Hover states, section headers, badge outlines |
--color-accent |
#8BC34A |
Highlights, progress bars filled, success indicators |
--color-bg |
#F5F5F5 |
Page background, card backgrounds |
--color-text |
#1A1A1A |
Body text, table cell content |
--color-warning |
#FF6B35 |
Quota >80%, low stock, warnings |
--color-error |
#D32F2F |
Quota exceeded, recalled batches, destructive actions |
--color-white |
#FFFFFF |
Sidebar text, button labels on dark bg, card surfaces |
1.2 Typography
| Element | Font | Size | Weight |
|---|---|---|---|
| H1 — Page title | Inter | 24px | 600 |
| H2 — Section heading | Inter | 18px | 600 |
| H3 — Card title | Inter | 14px | 600 |
| Body / table rows | Inter | 14px | 400 |
| Caption / label | Inter | 12px | 400 |
| Mono (codes, IDs) | JetBrains Mono | 13px | 400 |
1.3 Component Library
The frontend is a React/Vite SPA with no PrimeFaces or JSF dependency. Component primitives come from shadcn/ui (Radix UI + Tailwind CSS). This gives full control over styling, accessibility, and mobile responsiveness without JSF's lifecycle overhead.
Why not PrimeFaces? JSF/PrimeFaces is a server-side component model ill-suited to the modern REST API backend we're building. It tightly couples UI lifecycle to the backend, makes mobile responsiveness painful, and creates a hiring bottleneck. React is the right tool here. PrimeFaces is a fine choice for internal enterprise apps — not for a commercial SaaS.
| Component | Library | Usage |
|---|---|---|
Card / Panel |
shadcn/ui | Section containers |
DataTable |
TanStack Table v8 | Distributions, members, batches — virtualized |
Pagination |
shadcn/ui Pagination | All tables |
Input |
shadcn/ui Input | Single-line text fields |
NumberInput |
react-number-format | Weight inputs (gram precision, min/max) |
Select |
shadcn/ui Select | Dropdown selects (member, strain, batch) |
DatePicker |
shadcn/ui Calendar | Date range pickers for reports |
Progress |
shadcn/ui Progress | Quota consumption bar |
Button |
shadcn/ui Button | Primary and secondary actions |
AlertDialog |
shadcn/ui AlertDialog | Dangerous actions (recall) |
Toast |
sonner | Success/error notifications |
Badge |
shadcn/ui Badge | Status indicators (AVAILABLE, LOW, RECALLED) |
Sheet |
shadcn/ui Sheet | Mobile nav drawer |
Dialog |
shadcn/ui Dialog | Modal overlays |
1.4 Layout Grid
┌────────────────────────────────────────────────────┐
│ TOP NAVBAR (56px) club name · avatar · logout │
├──────────────┬─────────────────────────────────────┤
│ │ │
│ SIDEBAR │ MAIN CONTENT │
│ (240px) │ (fluid, min 784px) │
│ fixed │ │
│ │ │
└──────────────┴─────────────────────────────────────┘
- Sidebar: fixed left,
#2D5016background, white nav labels with#8BC34Aicons - Top Navbar:
#FFFFFFwith bottom border#E0E0E0, breadcrumb left, user controls right - Main Content:
#F5F5F5background, 24px padding, max content width 1200px centered
2. Admin Portal Screens
Screen 1 — Admin Dashboard
ASCII Wireframe
┌─────────────────────────────────────────────────────────────────────┐
│ 🌿 CannaManage Grüne Oase Berlin e.V. 👤 Max M. [⏻] │
├────────────┬────────────────────────────────────────────────────────┤
│ │ Dashboard 🗓 April 2026 │
│ 📊 Dashboard◄│ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌───────────────┐ │
│ 👥 Members│ │ Total Members│ │ Distributions│ │ Stock Available│ │
│ │ │ │ │ This Month │ │ │ │
│ 📋 Distrib│ │ 142 │ │ 87 │ │ 3,240 g │ │
│ │ │ ▲ +3 MoM │ │ ▲ +12 MoM │ │ ▼ -800g MoM │ │
│ 📦 Stock │ └──────────────┘ └──────────────┘ └───────────────┘ │
│ │ │
│ 📄 Reports│ Recent Distributions [+ New Entry] │
│ │ ┌─────────────────────────────────────────────────┐ │
│ ✅ Complian│ │ Member │ Strain │ Qty │ Date │ ✓ │ │
│ │ ├─────────────┼─────────────┼───────┼───────┼────┤ │
│ ⚙ Settings│ │ Müller, A. │ OG Kush B12 │ 5.0g │ 06.04 │ ✓ │ │
│ │ │ Schmidt, K. │ Amnesia H09 │ 3.5g │ 06.04 │ ✓ │ │
│ │ │ Weber, T. │ OG Kush B12 │ 7.0g │ 05.04 │ ✓ │ │
│ │ │ … │ │ │ │ │ │
└────────────┴──┴─────────────────────────────────────────────────────┘
Components & Behavior
| Component | Library | Behavior |
|---|---|---|
| KPI Cards | shadcn/ui Card | Auto-refreshed via useQuery (react-query, 60s stale) |
| Recent Distributions table | TanStack Table (5 rows) | Row click → navigate to distribution detail |
| Member column link | React Router <Link> |
Navigate to /admin/members/{id} |
+ New Entry button |
shadcn/ui Button variant="default" | Navigate to /admin/distributions/new |
| Trend indicators | Tailwind text-green-600 / text-red-600 |
▲/▼ with delta value |
Screen 2 — Distribution Recording Form
ASCII Wireframe
┌─────────────────────────────────────────────────────────────────────┐
│ 🌿 CannaManage Grüne Oase Berlin e.V. 👤 Max M. [⏻] │
├────────────┬────────────────────────────────────────────────────────┤
│ │ Distributions › New Distribution │
│ 📊 Dashbrd│ │
│ │ ┌──────────────────────────────────────────────────┐ │
│ 👥 Members│ │ Member * │ │
│ │ │ ┌──────────────────────────────────────────┐ │ │
│ 📋 Distrib◄│ │ │ 🔍 Search by name or member no. │ │ │
│ │ │ └──────────────────────────────────────────┘ │ │
│ 📦 Stock │ │ │ │
│ │ │ Strain / Batch * │ │
│ 📄 Reports│ │ ┌──────────────────────────────────────────┐ │ │
│ │ │ │ Select available batch ▼ │ │ │
│ ✅ Complian│ │ └──────────────────────────────────────────┘ │ │
│ │ │ │ │
│ ⚙ Settings│ │ Weight (grams) * │ │
│ │ │ ┌──────────┐ │ │
│ │ │ │ 0.0 g │ ← p:inputNumber min=0.1 max=25 │ │
│ │ │ └──────────┘ │ │
│ │ │ │ │
│ │ │ Monthly Quota — Müller, Anna │ │
│ │ │ ████████████░░░░░░░░ 32.5g / 50g 65% │ │
│ │ │ [████████████████░░░] <- p:progressBar │ │
│ │ │ │ │
│ │ │ [ Record Distribution ] [Cancel] │ │
│ │ └──────────────────────────────────────────────────┘ │
└────────────┴────────────────────────────────────────────────────────┘
Compliance UX — Real-Time Quota Indicator
The quota progress bar updates live as the weight field changes (via f:ajax event="keyup"):
| Quota Used After Distribution | Bar Color | Submit Button | Message |
|---|---|---|---|
| 0–79% | #8BC34A (green) |
Enabled | — |
| 80–99% | #FF6B35 (orange) |
Enabled | "⚠ Approaching monthly limit" |
| 100% | #D32F2F (red) |
Disabled | "🚫 Monthly limit reached (50g)" |
| Over-21 member, >30g monthly | #D32F2F (red) |
Disabled | "🚫 Under-21 limit reached (30g)" |
Components & Behavior
| Component | Library | Behavior |
|---|---|---|
| Member search | shadcn/ui Combobox | useQuery debounced search; shows name + member no. |
| Strain/Batch dropdown | shadcn/ui Select | Populated after member selection; filters AVAILABLE batches |
| Weight input | react-number-format | min=0.1 max=25.0 step=0.1; triggers quota recalculation via onChange |
| Quota bar | shadcn/ui Progress | Color class via cn() utility computed in component state |
| Submit | shadcn/ui Button | disabled={quotaExceeded} from react state |
| Cancel | React Router <Link> |
Returns to distribution log without saving |
Screen 3 — Stock Management
ASCII Wireframe
┌─────────────────────────────────────────────────────────────────────┐
│ 🌿 CannaManage Grüne Oase Berlin e.V. 👤 Max M. [⏻] │
├────────────┬────────────────────────────────────────────────────────┤
│ │ Stock Management [+ Add Batch] │
│ 📊 Dashbrd│ │
│ │ ┌──────────────────────┐ ┌────────────────────┐ │
│ 👥 Members│ │ 🔍 Filter by strain │ │ Status: All ▼ │ │
│ │ └──────────────────────┘ └────────────────────┘ │
│ 📋 Distrib│ │
│ │ ┌───────────────────────────────────────────────────┐ │
│ 📦 Stock ◄│ │ Strain │Batch│THC% │CBD%│ Qty │Status│Act │ │
│ │ ├──────────────┼─────┼─────┼────┼───────┼──────┼────┤ │
│ 📄 Reports│ │ OG Kush │B-12 │ 19% │ 1% │ 850g │ ● │[R] │ │
│ │ │ Amnesia Haze │H-09 │ 22% │<1% │ 72g │ ⚠ │[R] │ │
│ ✅ Complian│ │ Blue Dream │D-05 │ 17% │ 2% │ 0g │ — │[R] │ │
│ │ │ Hindu Kush │K-21 │ 8% │15% │ 340g │ ✓ │[R] │ │
│ ⚙ Settings│ │ AK-47 #4 │A-03 │ 20% │ 1% │ RECALLED │ ⛔ │[R] │ │
│ │ └───────────────────────────────────────────────────┘ │
│ │ [◄ 1 2 3 … ►] Showing 1-10/42 │
└────────────┴────────────────────────────────────────────────────────┘
Status Badges
| Badge | Color | Icon | Condition |
|---|---|---|---|
AVAILABLE |
#4A7C28 bg |
✓ checkmark | qty > 100g and not recalled |
LOW |
#FF6B35 bg |
⚠ warning | 0 < qty ≤ 100g |
EXHAUSTED |
#9E9E9E bg |
— dash | qty = 0 |
RECALLED |
#D32F2F bg |
⛔ stop | recall_date IS NOT NULL |
Components & Behavior
| Component | Library | Behavior |
|---|---|---|
| Strain filter | shadcn/ui Input | Filters TanStack table client-side via columnFilters state |
| Status filter | shadcn/ui Select | Filters table rows by status value |
| Batch table | TanStack Table | Server-side pagination via manualPagination, 10 rows/page |
| Status badge | shadcn/ui Badge variant mapped | Icon + text label (not color alone) |
| Recall button | shadcn/ui Button variant="destructive" | Opens shadcn/ui AlertDialog before executing |
| Confirm dialog | shadcn/ui AlertDialog | "Recall batch B-12 (OG Kush, 850g)? This cannot be undone." |
| Add Batch | shadcn/ui Button | Opens shadcn/ui Dialog with batch entry form |
Screen 4 — Compliance Report Generation
ASCII Wireframe
┌─────────────────────────────────────────────────────────────────────┐
│ 🌿 CannaManage Grüne Oase Berlin e.V. 👤 Max M. [⏻] │
├────────────┬────────────────────────────────────────────────────────┤
│ │ Reports › Monthly Compliance Report │
│ 📊 Dashbrd│ │
│ │ ┌─────────────────────────────────────────────────┐ │
│ 👥 Members│ │ Reporting Period │ │
│ │ │ Month: [ March ▼ ] Year: [ 2026 ▼ ] │ │
│ 📋 Distrib│ │ [ Generate Report ] │ │
│ │ └─────────────────────────────────────────────────┘ │
│ 📦 Stock │ │
│ │ ┌─────────────────────────────────────────────────┐ │
│ 📄 Reports◄│ │ PDF PREVIEW │ │
│ │ │ ┌─────────────────────────────────────────┐ │ │
│ ✅ Complian│ │ │ 🌿 CannaManage — Monthly Report Mar 2026 │ │ │
│ │ │ │ Club: Grüne Oase Berlin e.V. │ │ │
│ ⚙ Settings│ │ │ ─────────────────────────────────────── │ │ │
│ │ │ │ Total Members: 142 │ │ │
│ │ │ │ Active Members (distributed): 87 │ │ │
│ │ │ │ Total Distributed: 435.5g │ │ │
│ │ │ └─────────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ │ [⬇ Download PDF] [⬇ Download CSV] │ │
│ │ └─────────────────────────────────────────────────┘ │
│ │ │
│ │ Summary Table │
│ │ ┌────────────────────────────────────────────────┐ │
│ │ │ Metric │ Value │ Limit │ │
│ │ ├──────────────────────┼───────────┼─────────────┤ │
│ │ │ Members >50g/month │ 0 │ Must be 0 │ │
│ │ │ Members >30g (U21) │ 0 │ Must be 0 │ │
│ │ │ Recalled Batches │ 1 │ — (info) │ │
│ │ │ Avg grams / member │ 5.0g │ — │ │
│ │ └────────────────────────────────────────────────┘ │
└────────────┴────────────────────────────────────────────────────────┘
Components & Behavior
| Component | Library | Behavior |
|---|---|---|
| Month selector | shadcn/ui Select | Months Jan–Dec |
| Year selector | shadcn/ui Select | Current year ± 2 |
| Generate button | shadcn/ui Button | Calls report API; shows loading spinner; renders PDF thumbnail |
| PDF preview | <iframe> embedding /api/v1/reports/preview?month=3&year=2026 |
Generated by iText 7 backend |
| Download PDF | shadcn/ui Button | window.open(reportUrl) — streams PDF from REST endpoint |
| Download CSV | shadcn/ui Button | window.open(csvUrl) — streams CSV from REST endpoint |
| Summary table | TanStack Table | Compliance metrics; zero violations row has text-green-600 |
3. Member Portal Screens
Screen 5 — Member Dashboard / Quota View
ASCII Wireframe
┌────────────────────────────────────────────────────┐
│ 🌿 CannaManage Anna Müller #M-0042 │
│ ────────────────────────────────────────────── │
│ │
│ Monthly Quota Remaining │
│ │
│ ╭───────────────╮ │
│ │ │ │
│ │ 17.5 g │ │
│ │ remaining │ │
│ │ │ │
│ │ of 50g/month │ │
│ ╰───────────────╯ │
│ ▓▓▓▓▓▓▓▓▓▓▓░░░░░░░ 65% used │
│ │
│ Distribution History │
│ ┌────────────────────────────────────────────┐ │
│ │ Date │ Strain │ Quantity │ │
│ ├────────────┼──────────────┼────────────────┤ │
│ │ 06.04.2026 │ OG Kush │ 5.0g │ │
│ │ 02.04.2026 │ Amnesia Haze │ 12.5g │ │
│ │ 28.03.2026 │ OG Kush │ 15.0g │ │
│ └────────────┴──────────────┴────────────────┘ │
│ │
│ Available Strains │
│ ┌────────────────────────────────────────────┐ │
│ │ Strain │ Availability │ │
│ ├──────────────┼─────────────────────────────┤ │
│ │ OG Kush │ ● Available │ │
│ │ Amnesia Haze │ ⚠ Limited │ │
│ │ Hindu Kush │ ● Available │ │
│ └────────────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────┘
Compliance Note — Available Strains Display
Per CanG §§6–7, members may NOT see specific batch quantities or total stock levels. The Available Strains table shows only:
- Strain name
- Availability status (Available / Limited / Unavailable)
Quantities, batch codes, and THC/CBD percentages are not exposed in the member portal.
Components & Behavior
| Component | Library | Behavior |
|---|---|---|
| Quota circle | Custom CSS radial progress (conic-gradient) |
Computed from monthly total; color matches threshold rules |
| Quota bar | shadcn/ui Progress | Same color logic as admin distribution form |
| History table | TanStack Table | Last 10 distributions; sorted newest first; no pagination in MVP |
| Strains table | TanStack Table | status column: text + icon only, no quantities |
Screen 6 — Member Login
No mockup image — ASCII wireframe only.
Screen 7 — Staff Management (Admin)
Core feature — not deferred to v2.
ASCII Wireframe
┌─────────────────────────────────────────────────────────────────────┐
│ 🌿 CannaManage Grüne Oase Berlin e.V. 👤 Max M. [⏻] │
├────────────┬────────────────────────────────────────────────────────┤
│ │ Settings › Staff Members [+ Add Staff] │
│ 📊 Dashbrd│ │
│ │ ┌──────────────────────────────────────────────────┐ │
│ 👥 Members│ │ Name │ Role Template │ Permissions │ Act│ │
│ │ ├─────────────────┼───────────────┼─────────────┼────┤ │
│ 📋 Distrib│ │ Lisa Schmidt │ Ausgabe │ 3 of 8 │[✎][⛔]│
│ │ │ Tom Weber │ Lager │ 4 of 8 │[✎][⛔]│
│ 📦 Stock │ │ Sandra Müller │ Vorstand │ 7 of 8 │[✎][⛔]│
│ │ └──────────────────────────────────────────────────┘ │
│ 📄 Reports│ │
│ │ ┌─── Add / Edit Staff ──────────────────────────────┐ │
│ ✅ Complian│ │ Name: _______________ Email: _______________ │ │
│ │ │ │ │
│ 👤 Staff │ │ Role Template: [ Ausgabe ▼ ] (pre-fills below) │ │
│ │ │ │ │
│ ⚙ Settings│ │ Permissions: │ │
│ │ │ ☑ Record Distribution ☑ View Member List │ │
│ │ │ ☑ View Member Quota ☐ Add Member │ │
│ │ │ ☐ View Stock ☐ Record Stock In │ │
│ │ │ ☐ View Compliance Report ☐ Manage Grow Calendar │ │
│ │ │ │ │
│ │ │ [ Save Staff Member ] [ Cancel ] │ │
│ │ └────────────────────────────────────────────────────┘ │
└────────────┴────────────────────────────────────────────────────────┘
Design Decisions
- Admin sees everything. The staff management screen is only accessible with
ROLE_CLUB_ADMIN. Staff accounts cannot modify their own permissions. - DSGVO principle of least privilege. Each staff member only sees the data their role requires. A distribution desk worker (
Ausgabe) does not see cultivation calendar or full stock levels — only what they need to hand out product. - Pre-created role templates reduce admin setup time. Templates are editable — they just pre-fill the permission checkboxes.
- Staff ≠ reduced admin. Staff accounts do not have access to billing, club settings, or staff management. Even a "Vorstand" staff member cannot create other staff accounts.
- Audit trail. All distributions recorded by staff include
recorded_by = staffUserIdso it's clear who did what.
Components & Behavior
| Component | Library | Behavior |
|---|---|---|
| Staff table | TanStack Table | Shows name, role template, permission count, actions |
| Role template dropdown | shadcn/ui Select | Pre-populates permission checkboxes on selection |
| Permission checkboxes | shadcn/ui Checkbox | Individual overrides after template selection |
| Save | shadcn/ui Button | POST/PUT /api/v1/staff with { permissions: [...] } |
| Deactivate | shadcn/ui Button variant="destructive" | Soft-deletes staff account; data retained for audit |
ASCII Wireframe
┌──────────────────────────────────────────┐
│ │
│ 🌿 CannaManage │
│ │
│ ┌──────────────────────────────────┐ │
│ │ E-Mail Address │ │
│ │ ┌────────────────────────────┐ │ │
│ │ │ you@example.com │ │ │
│ │ └────────────────────────────┘ │ │
│ │ │ │
│ │ Password │ │
│ │ ┌────────────────────────────┐ │ │
│ │ │ •••••••••••• │ │ │
│ │ └────────────────────────────┘ │ │
│ │ │ │
│ │ [ ████ Log In ████████████ ] │ │
│ └──────────────────────────────────┘ │
│ │
│ Problems logging in? │
│ Contact your club administrator. │
│ │
└──────────────────────────────────────────┘
Design Decisions
- No self-registration link — member accounts are created exclusively by admins via the admin portal. The login page has no "Create account" or "Sign up" flow.
- No forgot-password link — password resets are initiated by the club admin only. The login page directs users to contact their admin, avoiding email-based reset flows that would require verified email infrastructure in MVP.
- No social login — DSGVO compliance and club accountability require traceable credential management.
- Form submission: POST to
/login(Spring Security form login), redirect to/member/dashboardon success.
Components & Behavior
| Component | Library | Behavior |
|---|---|---|
| Email field | shadcn/ui Input type="email" | HTML5 validation + react-hook-form @Email |
| Password field | shadcn/ui Input type="password" | No strength meter on login |
| Login button | shadcn/ui Button | Submit via react-hook-form; shows error toast on failure |
| Error message | sonner toast | "Invalid email or password." (never specific about which field failed) |
4. Navigation & Information Architecture
graph TD
Root["CannaManage Root"]
Root --> AdminPortal["Admin Portal /admin/"]
Root --> MemberPortal["Member Portal /member/"]
Root --> StaffPortal["Staff Portal /staff/"]
AdminPortal --> AdminDash["Dashboard (default)"]
AdminPortal --> Members["Members"]
Members --> MemberList["Member List"]
Members --> MemberDetail["Member Detail"]
AdminPortal --> Distributions["Distributions"]
Distributions --> DistLog["Distribution Log"]
Distributions --> NewDist["New Distribution"]
AdminPortal --> Stock["Stock"]
Stock --> Strains["Strains"]
Stock --> Batches["Batches"]
AdminPortal --> Reports["Reports"]
Reports --> MonthlyReport["Monthly Compliance"]
Reports --> MemberExport["Member Export"]
Reports --> RecallReport["Batch Recall Report"]
AdminPortal --> Compliance["Compliance"]
Compliance --> PreventionOfficer["Prevention Officer Info"]
AdminPortal --> StaffMgmt["Staff Members"]
StaffMgmt --> StaffList["Staff List"]
StaffMgmt --> StaffNew["Add/Edit Staff"]
AdminPortal --> Settings["Settings"]
Settings --> ClubProfile["Club Profile"]
StaffPortal --> StaffDash["Staff Dashboard\n(permissions-filtered)"]
MemberPortal --> MemberDash["Dashboard / Quota"]
MemberPortal --> DistHistory["Distribution History"]
MemberPortal --> StockAvail["Stock Availability"]
URL Structure
| Path | Description | Role |
|---|---|---|
/login |
Login page | Public |
/admin/dashboard |
Admin home | ROLE_ADMIN |
/admin/members |
Member list | ROLE_ADMIN |
/admin/members/{id} |
Member detail | ROLE_ADMIN |
/admin/distributions |
Distribution log | ROLE_ADMIN |
/admin/distributions/new |
New distribution form | ROLE_ADMIN |
/admin/stock/strains |
Strain catalog | ROLE_ADMIN |
/admin/stock/batches |
Batch management | ROLE_ADMIN |
/admin/reports/monthly |
Compliance reports | ROLE_ADMIN |
/admin/reports/members |
Member data export | ROLE_ADMIN |
/admin/reports/recall |
Recall report | ROLE_ADMIN |
/admin/compliance |
Prevention officer | ROLE_ADMIN |
/admin/staff |
Staff list | ROLE_ADMIN |
/admin/staff/new |
Create staff account | ROLE_ADMIN |
/admin/staff/{id} |
Edit staff permissions | ROLE_ADMIN |
/admin/settings |
Club settings | ROLE_ADMIN |
/staff/dashboard |
Staff home (permissions-filtered) | ROLE_STAFF |
/member/dashboard |
Member quota view | ROLE_MEMBER |
/member/distributions |
Personal history | ROLE_MEMBER |
/member/stock |
Strain availability | ROLE_MEMBER |
5. Responsive Design Notes
MVP (v1) — Tailwind Breakpoints
The React/Vite SPA uses Tailwind CSS breakpoints throughout. The switch from PrimeFaces means we no longer depend on JSF's ui-g-* responsive grid — Tailwind's sm: / md: / lg: utilities apply cleanly to every component.
| Breakpoint | Tailwind prefix | Admin Portal | Member Portal |
|---|---|---|---|
≥ 1280px |
xl: |
Full layout — sidebar + content | Two-column: quota left, history right |
1024–1279px |
lg: |
Sidebar collapses to icons (60px) | Two-column (narrower) |
768–1023px |
md: |
Sidebar hidden; hamburger sheet | Single-column, full-width cards |
< 768px |
sm: / base |
Admin: horizontal table scroll | Member: compact quota ring, condensed table |
Member Portal — Mobile-First from Day One
Members will typically check quota status on their phone. The member portal uses flex-col mobile-first layout with md:flex-row for wider viewports — no breakpoint-specific class sprawl.
Responsive Conventions (React/Tailwind)
- No inline styles — use Tailwind utilities exclusively
cn()utility (clsx + tailwind-merge) for conditional class composition- Tables on mobile: horizontal scroll wrapper
overflow-x-autoon<div>wrapping<table> - All modals and sheets use
shadcn/ui Dialog/Sheet— these are already mobile-friendly (viewport-aware positioning) - Touch targets: all interactive elements
min-h-[44px]andmin-w-[44px]per WCAG 2.5.5
v2 Roadmap
- PWA manifest + service worker (offline quota display)
- Push notifications for low quota warnings
- Per-club subdomain routing (
clubname.cannamanage.de)
6. Accessibility
CannaManage targets WCAG 2.1 AA compliance across both portals.
Keyboard Navigation
| Element | Keyboard Behavior |
|---|---|
| Navigation sidebar | Tab navigates items; Enter activates |
| Data tables | Tab to table; arrow keys for row navigation |
| Dropdown menus | Enter/Space to open; arrow keys to navigate; Escape to close |
| Modal dialogs | Focus trapped inside; Escape to close; first focusable element receives focus on open |
| Confirmation dialogs | Tab between Confirm and Cancel; Enter on focused button |
Screen Reader Support
- All
Input/NumberInputfields have<label>withhtmlFor(React) — Radix UI enforces this automatically for shadcn/ui form fields aria-labelset on icon-only buttons (e.g., recall action column)aria-live="polite"region on quota bar — announces percentage changesaria-describedbylinks compliance warning messages to the weight input- TanStack Table exposes
role="grid"andaria-rowcountviagetTableProps()
Color Independence
Status badges must never rely on color alone:
| Status | Color | Icon | Text label |
|---|---|---|---|
| AVAILABLE | Green | ✓ | "Available" |
| LOW | Orange | ⚠ | "Low Stock" |
| RECALLED | Red | ⛔ | "Recalled" |
| EXHAUSTED | Gray | — | "Exhausted" |
Quota progress bar additionally shows numeric percentage text alongside color change.
Contrast Ratios
| Foreground | Background | Ratio | AA pass |
|---|---|---|---|
#FFFFFF |
#2D5016 |
9.1:1 | ✅ |
#1A1A1A |
#F5F5F5 |
16.0:1 | ✅ |
#1A1A1A |
#FFFFFF |
19.0:1 | ✅ |
#FFFFFF |
#D32F2F |
5.1:1 | ✅ |
#FFFFFF |
#FF6B35 |
3.1:1 | ⚠ verify at large text only |
Next: 07-CODING-STANDARDS.md




