Text Generation
Transformers
PyTorch
English
llama
finance
Eval Results (legacy)
text-generation-inference
Instructions to use Wanxai/mula with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use Wanxai/mula with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-generation", model="Wanxai/mula")# Load model directly from transformers import AutoTokenizer, AutoModelForCausalLM tokenizer = AutoTokenizer.from_pretrained("Wanxai/mula") model = AutoModelForCausalLM.from_pretrained("Wanxai/mula") - Notebooks
- Google Colab
- Kaggle
- Local Apps
- vLLM
How to use Wanxai/mula with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "Wanxai/mula" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "Wanxai/mula", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }'Use Docker
docker model run hf.co/Wanxai/mula
- SGLang
How to use Wanxai/mula with SGLang:
Install from pip and serve model
# Install SGLang from pip: pip install sglang # Start the SGLang server: python3 -m sglang.launch_server \ --model-path "Wanxai/mula" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "Wanxai/mula", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }'Use Docker images
docker run --gpus all \ --shm-size 32g \ -p 30000:30000 \ -v ~/.cache/huggingface:/root/.cache/huggingface \ --env "HF_TOKEN=<secret>" \ --ipc=host \ lmsysorg/sglang:latest \ python3 -m sglang.launch_server \ --model-path "Wanxai/mula" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "Wanxai/mula", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }' - Docker Model Runner
How to use Wanxai/mula with Docker Model Runner:
docker model run hf.co/Wanxai/mula
Upload DriveSmart_Dashboard_PRD.md
Browse files- DriveSmart_Dashboard_PRD.md +882 -0
DriveSmart_Dashboard_PRD.md
ADDED
|
@@ -0,0 +1,882 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# DriveSmart AI — Instructor Dashboard
|
| 2 |
+
## Product Requirements Document (PRD)
|
| 3 |
+
### For: lovable.dev / v0.dev Frontend Generation
|
| 4 |
+
### Version: 1.0 | Status: Ready for Development
|
| 5 |
+
|
| 6 |
+
---
|
| 7 |
+
|
| 8 |
+
## 1. Project Overview
|
| 9 |
+
|
| 10 |
+
**Product Name:** DriveSmart AI Instructor Dashboard
|
| 11 |
+
**Purpose:** A web-based dashboard for driving school instructors to upload and manage training materials, monitor learner engagement, and configure their AI assistant — which serves learners 24/7 via WhatsApp.
|
| 12 |
+
**Primary User:** Driving school instructors and administrators
|
| 13 |
+
**Secondary Users:** Driving school managers / executives (read-only analytics view)
|
| 14 |
+
|
| 15 |
+
---
|
| 16 |
+
|
| 17 |
+
## 2. Brand Identity & Design System
|
| 18 |
+
|
| 19 |
+
### 2.1 Brand Colours (AA Kenya — strictly follow these)
|
| 20 |
+
|
| 21 |
+
```css
|
| 22 |
+
:root {
|
| 23 |
+
/* Primary Palette */
|
| 24 |
+
--green-900: #0F3D1A; /* deepest green — hero backgrounds */
|
| 25 |
+
--green-700: #1A5C2A; /* primary brand green — nav, buttons, headers */
|
| 26 |
+
--green-500: #2E8B46; /* hover states, active indicators */
|
| 27 |
+
--green-100: #E8F5EC; /* light tinted backgrounds, cards */
|
| 28 |
+
|
| 29 |
+
/* Accent Palette */
|
| 30 |
+
--yellow-500: #F5C200; /* primary yellow — CTAs, highlights, badges */
|
| 31 |
+
--yellow-300: #FFD84D; /* hover state for yellow elements */
|
| 32 |
+
--yellow-100: #FFF8D6; /* light yellow tint for alerts, info banners */
|
| 33 |
+
|
| 34 |
+
/* Neutral Palette */
|
| 35 |
+
--white: #FFFFFF;
|
| 36 |
+
--gray-50: #F8F9FA;
|
| 37 |
+
--gray-100: #F1F3F5;
|
| 38 |
+
--gray-200: #E9ECEF;
|
| 39 |
+
--gray-400: #ADB5BD;
|
| 40 |
+
--gray-600: #6C757D;
|
| 41 |
+
--gray-800: #343A40;
|
| 42 |
+
--black: #0A0A0A;
|
| 43 |
+
|
| 44 |
+
/* Semantic */
|
| 45 |
+
--success: #2E8B46;
|
| 46 |
+
--warning: #F5C200;
|
| 47 |
+
--error: #D63031;
|
| 48 |
+
--info: #0984E3;
|
| 49 |
+
}
|
| 50 |
+
```
|
| 51 |
+
|
| 52 |
+
### 2.2 Typography
|
| 53 |
+
|
| 54 |
+
```css
|
| 55 |
+
/* Import in <head> */
|
| 56 |
+
@import url('https://fonts.googleapis.com/css2?family=Barlow+Condensed:wght@600;700;800&family=DM+Sans:wght@400;500;600&family=DM+Mono:wght@400;500&display=swap');
|
| 57 |
+
|
| 58 |
+
:root {
|
| 59 |
+
--font-display: 'Barlow Condensed', sans-serif; /* headings, hero text, nav */
|
| 60 |
+
--font-body: 'DM Sans', sans-serif; /* body, labels, UI text */
|
| 61 |
+
--font-mono: 'DM Mono', monospace; /* code blocks, IDs, stats */
|
| 62 |
+
}
|
| 63 |
+
```
|
| 64 |
+
|
| 65 |
+
**Type Scale:**
|
| 66 |
+
| Token | Size | Weight | Font | Usage |
|
| 67 |
+
|---|---|---|---|---|
|
| 68 |
+
| `--text-hero` | 3.5rem / 56px | 800 | Barlow Condensed | Page hero titles |
|
| 69 |
+
| `--text-h1` | 2.25rem / 36px | 700 | Barlow Condensed | Section headings |
|
| 70 |
+
| `--text-h2` | 1.5rem / 24px | 600 | Barlow Condensed | Card headings |
|
| 71 |
+
| `--text-h3` | 1.125rem / 18px | 600 | DM Sans | Subsection labels |
|
| 72 |
+
| `--text-body` | 0.9375rem / 15px | 400 | DM Sans | Body copy |
|
| 73 |
+
| `--text-small` | 0.8125rem / 13px | 400 | DM Sans | Captions, metadata |
|
| 74 |
+
| `--text-mono` | 0.875rem / 14px | 500 | DM Mono | Stats, codes, counts |
|
| 75 |
+
|
| 76 |
+
### 2.3 Checkered Brand Pattern
|
| 77 |
+
|
| 78 |
+
The AA Kenya diagonal checkered pattern (green + yellow squares) is a signature brand element. Implement it as a CSS background pattern used as decorative accents — NOT as full-page backgrounds.
|
| 79 |
+
|
| 80 |
+
```css
|
| 81 |
+
/* Checkered accent strip — used as section dividers, card top-borders */
|
| 82 |
+
.checkered-strip {
|
| 83 |
+
background-image:
|
| 84 |
+
repeating-conic-gradient(
|
| 85 |
+
var(--green-700) 0% 25%,
|
| 86 |
+
var(--yellow-500) 0% 50%
|
| 87 |
+
);
|
| 88 |
+
background-size: 16px 16px;
|
| 89 |
+
height: 8px;
|
| 90 |
+
width: 100%;
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
/* Thin checkered border top on highlighted cards */
|
| 94 |
+
.card--featured::before {
|
| 95 |
+
content: '';
|
| 96 |
+
display: block;
|
| 97 |
+
height: 5px;
|
| 98 |
+
background-image: repeating-conic-gradient(
|
| 99 |
+
var(--green-700) 0% 25%, var(--yellow-500) 0% 50%
|
| 100 |
+
);
|
| 101 |
+
background-size: 10px 10px;
|
| 102 |
+
}
|
| 103 |
+
```
|
| 104 |
+
|
| 105 |
+
### 2.4 Spacing & Radius
|
| 106 |
+
|
| 107 |
+
```css
|
| 108 |
+
:root {
|
| 109 |
+
--radius-sm: 4px;
|
| 110 |
+
--radius-md: 8px;
|
| 111 |
+
--radius-lg: 12px;
|
| 112 |
+
--radius-xl: 20px;
|
| 113 |
+
|
| 114 |
+
--space-1: 4px; --space-2: 8px; --space-3: 12px;
|
| 115 |
+
--space-4: 16px; --space-6: 24px; --space-8: 32px;
|
| 116 |
+
--space-10: 40px; --space-12: 48px; --space-16: 64px;
|
| 117 |
+
}
|
| 118 |
+
```
|
| 119 |
+
|
| 120 |
+
### 2.5 Shadows
|
| 121 |
+
|
| 122 |
+
```css
|
| 123 |
+
:root {
|
| 124 |
+
--shadow-sm: 0 1px 3px rgba(0,0,0,0.08), 0 1px 2px rgba(0,0,0,0.06);
|
| 125 |
+
--shadow-md: 0 4px 12px rgba(0,0,0,0.10), 0 2px 4px rgba(0,0,0,0.06);
|
| 126 |
+
--shadow-lg: 0 10px 30px rgba(0,0,0,0.12), 0 4px 8px rgba(0,0,0,0.08);
|
| 127 |
+
--shadow-green: 0 4px 20px rgba(26, 92, 42, 0.25);
|
| 128 |
+
--shadow-yellow: 0 4px 20px rgba(245, 194, 0, 0.30);
|
| 129 |
+
}
|
| 130 |
+
```
|
| 131 |
+
|
| 132 |
+
---
|
| 133 |
+
|
| 134 |
+
## 3. Animation & Motion System
|
| 135 |
+
|
| 136 |
+
### 3.1 Motion Philosophy
|
| 137 |
+
**"Purposeful momentum"** — Every animation communicates state, not just decoration. Fast in, slower out. Elements that enter from a direction leave in the same direction. No bouncing, no elastic — the design is authoritative and professional.
|
| 138 |
+
|
| 139 |
+
### 3.2 Core Transitions
|
| 140 |
+
|
| 141 |
+
```css
|
| 142 |
+
:root {
|
| 143 |
+
--ease-standard: cubic-bezier(0.4, 0, 0.2, 1); /* general transitions */
|
| 144 |
+
--ease-enter: cubic-bezier(0.0, 0, 0.2, 1); /* elements entering */
|
| 145 |
+
--ease-exit: cubic-bezier(0.4, 0, 1, 1); /* elements leaving */
|
| 146 |
+
--ease-sharp: cubic-bezier(0.4, 0, 0.6, 1); /* quick snappy moves */
|
| 147 |
+
|
| 148 |
+
--duration-fast: 120ms;
|
| 149 |
+
--duration-base: 220ms;
|
| 150 |
+
--duration-slow: 380ms;
|
| 151 |
+
--duration-reveal: 500ms;
|
| 152 |
+
}
|
| 153 |
+
```
|
| 154 |
+
|
| 155 |
+
### 3.3 Animation Catalogue
|
| 156 |
+
|
| 157 |
+
**Page Entry (staggered reveal):**
|
| 158 |
+
```css
|
| 159 |
+
/* Apply to dashboard cards, table rows on page load */
|
| 160 |
+
@keyframes fadeSlideUp {
|
| 161 |
+
from { opacity: 0; transform: translateY(18px); }
|
| 162 |
+
to { opacity: 1; transform: translateY(0); }
|
| 163 |
+
}
|
| 164 |
+
|
| 165 |
+
.card { animation: fadeSlideUp var(--duration-reveal) var(--ease-enter) both; }
|
| 166 |
+
.card:nth-child(1) { animation-delay: 0ms; }
|
| 167 |
+
.card:nth-child(2) { animation-delay: 60ms; }
|
| 168 |
+
.card:nth-child(3) { animation-delay: 120ms; }
|
| 169 |
+
.card:nth-child(4) { animation-delay: 180ms; }
|
| 170 |
+
```
|
| 171 |
+
|
| 172 |
+
**Stat Counter Animation:**
|
| 173 |
+
```javascript
|
| 174 |
+
// Animate numbers counting up on dashboard load
|
| 175 |
+
// e.g. 0 → 1,247 over 1000ms using requestAnimationFrame
|
| 176 |
+
// Use easeOutExpo easing for satisfying deceleration
|
| 177 |
+
function animateCount(el, from, to, duration) { ... }
|
| 178 |
+
```
|
| 179 |
+
|
| 180 |
+
**Sidebar Navigation Active Indicator:**
|
| 181 |
+
```css
|
| 182 |
+
/* Sliding green pill indicator for active nav item */
|
| 183 |
+
.nav-indicator {
|
| 184 |
+
position: absolute;
|
| 185 |
+
left: 0; top: 0;
|
| 186 |
+
width: 3px; height: 100%;
|
| 187 |
+
background: var(--yellow-500);
|
| 188 |
+
transition: transform var(--duration-base) var(--ease-standard);
|
| 189 |
+
}
|
| 190 |
+
```
|
| 191 |
+
|
| 192 |
+
**Upload Drop Zone:**
|
| 193 |
+
```css
|
| 194 |
+
@keyframes dashedBorderPulse {
|
| 195 |
+
0%, 100% { border-color: var(--gray-400); }
|
| 196 |
+
50% { border-color: var(--green-500); }
|
| 197 |
+
}
|
| 198 |
+
/* On drag-over: smooth scale up + green glow */
|
| 199 |
+
.dropzone--active {
|
| 200 |
+
transform: scale(1.02);
|
| 201 |
+
box-shadow: var(--shadow-green);
|
| 202 |
+
transition: all var(--duration-base) var(--ease-enter);
|
| 203 |
+
}
|
| 204 |
+
```
|
| 205 |
+
|
| 206 |
+
**Document Processing Progress Bar:**
|
| 207 |
+
```css
|
| 208 |
+
@keyframes progressShimmer {
|
| 209 |
+
from { background-position: -200% center; }
|
| 210 |
+
to { background-position: 200% center; }
|
| 211 |
+
}
|
| 212 |
+
.progress-bar--processing {
|
| 213 |
+
background: linear-gradient(
|
| 214 |
+
90deg,
|
| 215 |
+
var(--green-500) 0%,
|
| 216 |
+
var(--yellow-500) 50%,
|
| 217 |
+
var(--green-500) 100%
|
| 218 |
+
);
|
| 219 |
+
background-size: 200% auto;
|
| 220 |
+
animation: progressShimmer 1.8s linear infinite;
|
| 221 |
+
}
|
| 222 |
+
```
|
| 223 |
+
|
| 224 |
+
**Toast Notifications:**
|
| 225 |
+
- Enter: slide in from top-right, fade in — 220ms
|
| 226 |
+
- Exit: slide out to right, fade out — 180ms
|
| 227 |
+
- Auto-dismiss after 4s with shrinking progress bar at bottom
|
| 228 |
+
|
| 229 |
+
**Modal:**
|
| 230 |
+
- Backdrop: fade in 200ms
|
| 231 |
+
- Panel: scale from 0.95 → 1.0 + fade in, 250ms ease-enter
|
| 232 |
+
- Exit: reverse at 180ms
|
| 233 |
+
|
| 234 |
+
**Button States:**
|
| 235 |
+
```css
|
| 236 |
+
.btn-primary {
|
| 237 |
+
transition: transform var(--duration-fast), box-shadow var(--duration-fast);
|
| 238 |
+
}
|
| 239 |
+
.btn-primary:hover { transform: translateY(-1px); box-shadow: var(--shadow-green); }
|
| 240 |
+
.btn-primary:active { transform: translateY(0px); box-shadow: none; }
|
| 241 |
+
```
|
| 242 |
+
|
| 243 |
+
---
|
| 244 |
+
|
| 245 |
+
## 4. Application Architecture
|
| 246 |
+
|
| 247 |
+
### 4.1 Tech Stack (for lovable.dev / v0.dev)
|
| 248 |
+
- **Framework:** Next.js 14 (App Router)
|
| 249 |
+
- **Styling:** Tailwind CSS with custom config extending AA Kenya tokens
|
| 250 |
+
- **Auth:** `@supabase/ssr` + `@supabase/supabase-js`
|
| 251 |
+
- **File Upload:** `@supabase/storage-js` (direct-to-bucket)
|
| 252 |
+
- **State:** React Context + `useState`/`useReducer` (no Redux)
|
| 253 |
+
- **Icons:** `lucide-react`
|
| 254 |
+
- **Charts:** `recharts`
|
| 255 |
+
- **Notifications:** Custom toast system (no library)
|
| 256 |
+
- **Drag & Drop:** `react-dropzone`
|
| 257 |
+
|
| 258 |
+
### 4.2 Route Structure
|
| 259 |
+
|
| 260 |
+
```
|
| 261 |
+
/ → redirect to /auth/login
|
| 262 |
+
/auth/login → Login page (public)
|
| 263 |
+
/auth/forgot-password → Password reset (public)
|
| 264 |
+
/dashboard → Overview / home (protected)
|
| 265 |
+
/dashboard/documents → Document library (protected)
|
| 266 |
+
/dashboard/upload → Upload new document (protected)
|
| 267 |
+
/dashboard/analytics → Usage analytics (protected)
|
| 268 |
+
/dashboard/settings → Account & system settings (protected)
|
| 269 |
+
```
|
| 270 |
+
|
| 271 |
+
### 4.3 Environment Variables (Supabase + n8n)
|
| 272 |
+
|
| 273 |
+
```env
|
| 274 |
+
# .env.local — provide these to the generated frontend
|
| 275 |
+
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
|
| 276 |
+
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
|
| 277 |
+
NEXT_PUBLIC_N8N_WEBHOOK_BASE=https://your-n8n-domain.com/webhook
|
| 278 |
+
NEXT_PUBLIC_N8N_API_SECRET=your-shared-secret
|
| 279 |
+
```
|
| 280 |
+
|
| 281 |
+
---
|
| 282 |
+
|
| 283 |
+
## 5. Pages — Detailed Specifications
|
| 284 |
+
|
| 285 |
+
---
|
| 286 |
+
|
| 287 |
+
### 5.1 Authentication — Login Page (`/auth/login`)
|
| 288 |
+
|
| 289 |
+
**Layout:** Full-screen split. Left panel: brand/visual. Right panel: form.
|
| 290 |
+
|
| 291 |
+
**Left Panel (40% width on desktop, hidden on mobile):**
|
| 292 |
+
- Background: `var(--green-900)` — deep forest green
|
| 293 |
+
- AA Kenya logo centered (white version)
|
| 294 |
+
- Tagline: "Inspiring Mobility" in `--font-display`, `--yellow-500`
|
| 295 |
+
- Below tagline: checkered strip (horizontal, full width of panel)
|
| 296 |
+
- Subtle road texture overlay — dark semi-transparent diagonal lines at 15deg suggesting tarmac grain (`opacity: 0.07`)
|
| 297 |
+
- Quote at bottom: *"Equipping drivers with knowledge for safer roads"* — white, italic, small
|
| 298 |
+
|
| 299 |
+
**Right Panel (60% width on desktop, 100% on mobile):**
|
| 300 |
+
- Background: `--white`
|
| 301 |
+
- Top-right corner: small checkered square motif (decorative, 40×40px)
|
| 302 |
+
- Vertical center-aligned form
|
| 303 |
+
|
| 304 |
+
**Form Elements:**
|
| 305 |
+
```
|
| 306 |
+
[AA Logo — small, green version] ← shown only on mobile
|
| 307 |
+
[H1: "Welcome back"] ← Barlow Condensed 700, green-700
|
| 308 |
+
[Subtext: "Sign in to your instructor dashboard"]
|
| 309 |
+
|
| 310 |
+
[Label: "Email address"]
|
| 311 |
+
[Input: email — full width]
|
| 312 |
+
|
| 313 |
+
[Label: "Password"]
|
| 314 |
+
[Input: password �� with show/hide toggle]
|
| 315 |
+
|
| 316 |
+
[Row: spacer | "Forgot password?" link] ← right-aligned, yellow-500
|
| 317 |
+
|
| 318 |
+
[Button: "Sign In"] ← full width, green-700 bg, white text
|
| 319 |
+
|
| 320 |
+
[Divider line]
|
| 321 |
+
|
| 322 |
+
[Small text: "Need access? Contact your administrator."]
|
| 323 |
+
```
|
| 324 |
+
|
| 325 |
+
**Behaviour:**
|
| 326 |
+
- On submit: button shows loading spinner (replacing text), disabled
|
| 327 |
+
- On error: input borders turn `--error`, error message slides down below input (18px DM Sans, error red)
|
| 328 |
+
- On success: brief green checkmark flash, then router.push('/dashboard') with page transition
|
| 329 |
+
- Form validation: inline, on blur
|
| 330 |
+
|
| 331 |
+
**Animations:**
|
| 332 |
+
- Right panel form elements: staggered fadeSlideUp on mount (logo → title → inputs → button), 60ms delay each
|
| 333 |
+
- Left panel: subtle parallax on mouse move — logo shifts ±8px, tagline shifts ±4px
|
| 334 |
+
- Logo: on hover, slow rotation of the AA badge inner ring (360deg, 3s, infinite, ease-in-out)
|
| 335 |
+
|
| 336 |
+
---
|
| 337 |
+
|
| 338 |
+
### 5.2 Forgot Password (`/auth/forgot-password`)
|
| 339 |
+
|
| 340 |
+
Simple centered card, white background with green-900 header strip.
|
| 341 |
+
- Email input + "Send Reset Link" button
|
| 342 |
+
- Success state: green checkmark + "Check your email" message
|
| 343 |
+
- Back to login link
|
| 344 |
+
|
| 345 |
+
---
|
| 346 |
+
|
| 347 |
+
### 5.3 App Shell — Persistent Layout (all `/dashboard/*` routes)
|
| 348 |
+
|
| 349 |
+
**Structure:**
|
| 350 |
+
```
|
| 351 |
+
┌─────────────────────────────────────────────────────┐
|
| 352 |
+
│ TOPBAR (full width, 64px height) │
|
| 353 |
+
├──────────┬──────────────────────────────────────────┤
|
| 354 |
+
│ │ │
|
| 355 |
+
│ SIDEBAR │ MAIN CONTENT AREA │
|
| 356 |
+
│ (240px) │ (fluid, scrollable) │
|
| 357 |
+
│ │ │
|
| 358 |
+
└──────────┴──────────────────────────────────────────┘
|
| 359 |
+
```
|
| 360 |
+
|
| 361 |
+
**Topbar:**
|
| 362 |
+
- Background: `var(--green-700)`
|
| 363 |
+
- Left: hamburger menu icon (mobile) | "DriveSmart AI" wordmark in Barlow Condensed, white
|
| 364 |
+
- Right: notification bell (with badge count) | Avatar circle with initials | Dropdown (Profile, Settings, Sign Out)
|
| 365 |
+
- Bottom border: 4px checkered strip (green-700/yellow-500)
|
| 366 |
+
- On scroll: add `box-shadow: var(--shadow-md)` transition
|
| 367 |
+
|
| 368 |
+
**Sidebar:**
|
| 369 |
+
- Background: `var(--green-900)`
|
| 370 |
+
- Top: AA Kenya logo (small, white) with "Instructor Portal" label below in gray-400
|
| 371 |
+
- Navigation items:
|
| 372 |
+
```
|
| 373 |
+
[icon] Overview /dashboard
|
| 374 |
+
[icon] Documents /dashboard/documents
|
| 375 |
+
[icon] Upload /dashboard/upload
|
| 376 |
+
[icon] Analytics /dashboard/analytics
|
| 377 |
+
─────────────────
|
| 378 |
+
[icon] Settings /dashboard/settings
|
| 379 |
+
[icon] Sign Out
|
| 380 |
+
```
|
| 381 |
+
- Active item: white text + yellow-500 left border (3px) + green-500 background
|
| 382 |
+
- Hover item: green-700 background, 200ms transition
|
| 383 |
+
- Collapse button at bottom (mobile: slides out as drawer)
|
| 384 |
+
- Bottom of sidebar: version number + "Powered by Gemini AI" in small gray text
|
| 385 |
+
|
| 386 |
+
**Mobile:** Sidebar is a slide-in drawer from left, triggered by hamburger. Backdrop overlay fades in.
|
| 387 |
+
|
| 388 |
+
---
|
| 389 |
+
|
| 390 |
+
### 5.4 Dashboard Overview (`/dashboard`)
|
| 391 |
+
|
| 392 |
+
**Hero Stats Row — 4 cards:**
|
| 393 |
+
```
|
| 394 |
+
┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
| 395 |
+
│ 📄 Documents │ │ ❓ Queries │ │ 👥 Learners │ │ ✅ Resolved │
|
| 396 |
+
│ 12 │ │ 1,247 │ │ 89 │ │ 94.2% │
|
| 397 |
+
│ +2 this week│ │ +18 today │ │ this month │ │ resolution │
|
| 398 |
+
└──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘
|
| 399 |
+
```
|
| 400 |
+
- Card style: white bg, `--shadow-sm`, `--radius-lg`
|
| 401 |
+
- Featured card (Queries): `card--featured` with checkered top border
|
| 402 |
+
- Stats animate up from 0 on page load (easeOutExpo, 1000ms)
|
| 403 |
+
- Subtle hover: translateY(-2px) + shadow-md
|
| 404 |
+
|
| 405 |
+
**Quick Actions Row:**
|
| 406 |
+
```
|
| 407 |
+
[+ Upload Document] [View Analytics] [Open WhatsApp Preview]
|
| 408 |
+
```
|
| 409 |
+
- Primary: green-700 bg
|
| 410 |
+
- Secondary: outlined green-700 border
|
| 411 |
+
- Tertiary: yellow-500 bg, green-900 text
|
| 412 |
+
|
| 413 |
+
**Recent Documents Table:**
|
| 414 |
+
- Last 5 uploaded documents
|
| 415 |
+
- Columns: Name | Type | Status | Chunks | Uploaded | Actions
|
| 416 |
+
- Status badges: `pending` (gray), `processing` (yellow, animated shimmer), `ready` (green), `failed` (red)
|
| 417 |
+
- Actions: View, Delete
|
| 418 |
+
- "View all" link to `/dashboard/documents`
|
| 419 |
+
|
| 420 |
+
**Activity Feed (right column, 30% width):**
|
| 421 |
+
- Recent WhatsApp queries (last 10)
|
| 422 |
+
- Each entry: phone number (masked: +254 7XX XXX X89) | input type icon | query preview (truncated 50 chars) | time ago
|
| 423 |
+
- Input type icons: 💬 text | 🎙️ voice | 📷 image
|
| 424 |
+
- Scrollable with custom scrollbar styled in green
|
| 425 |
+
|
| 426 |
+
**Animations:**
|
| 427 |
+
- Stats cards: staggered entry (0ms, 80ms, 160ms, 240ms)
|
| 428 |
+
- Activity feed: items slide in from right, staggered 30ms each
|
| 429 |
+
- Processing status cards pulse with green glow
|
| 430 |
+
|
| 431 |
+
---
|
| 432 |
+
|
| 433 |
+
### 5.5 Document Library (`/dashboard/documents`)
|
| 434 |
+
|
| 435 |
+
**Header:**
|
| 436 |
+
```
|
| 437 |
+
[H1: "Document Library"]
|
| 438 |
+
[Subtext: "X documents · Y total chunks indexed"]
|
| 439 |
+
[Right: "+ Upload New" button]
|
| 440 |
+
```
|
| 441 |
+
|
| 442 |
+
**Filters & Search Bar:**
|
| 443 |
+
```
|
| 444 |
+
[🔍 Search documents...] [Type ▾] [Status ▾] [Date ▾] [Sort ▾]
|
| 445 |
+
```
|
| 446 |
+
|
| 447 |
+
**Document Grid (default view) | List toggle:**
|
| 448 |
+
|
| 449 |
+
**Card Mode (2–3 columns responsive grid):**
|
| 450 |
+
```
|
| 451 |
+
┌─────────────────────────────┐
|
| 452 |
+
│ [Checkered top border 5px] │
|
| 453 |
+
│ [🗎 icon — large, green] │
|
| 454 |
+
│ │
|
| 455 |
+
│ [Document name — truncated] │
|
| 456 |
+
│ [File type badge] │
|
| 457 |
+
│ │
|
| 458 |
+
│ [Chunks: 47] [Size: 2.3MB] │
|
| 459 |
+
│ [Uploaded: 2 days ago] │
|
| 460 |
+
│ [Status badge] │
|
| 461 |
+
│ │
|
| 462 |
+
│ [──────────────────────────] │
|
| 463 |
+
│ [Delete] [View →] │
|
| 464 |
+
└─────────────────────────────┘
|
| 465 |
+
```
|
| 466 |
+
|
| 467 |
+
**List Mode:**
|
| 468 |
+
- Full-width table rows with hover highlight (green-100)
|
| 469 |
+
- Sortable column headers with sort direction arrows
|
| 470 |
+
|
| 471 |
+
**Empty State:**
|
| 472 |
+
```
|
| 473 |
+
[Large dashed border box — center of page]
|
| 474 |
+
[Document illustration or road-sign icon in green-200]
|
| 475 |
+
[H2: "No documents yet"]
|
| 476 |
+
[Body: "Upload your first training material to get started."]
|
| 477 |
+
[Button: "+ Upload Document"]
|
| 478 |
+
```
|
| 479 |
+
|
| 480 |
+
**Delete Confirmation:**
|
| 481 |
+
- Modal with warning icon
|
| 482 |
+
- "This will remove [filename] and all [N] indexed chunks. Learners will no longer have access to this content."
|
| 483 |
+
- [Cancel] [Delete Document] — red destroy button
|
| 484 |
+
|
| 485 |
+
---
|
| 486 |
+
|
| 487 |
+
### 5.6 Document Upload (`/dashboard/upload`)
|
| 488 |
+
|
| 489 |
+
**Layout:** Centered, max-width 720px
|
| 490 |
+
|
| 491 |
+
**Step Indicator:**
|
| 492 |
+
```
|
| 493 |
+
[1 Select File] ──── [2 Processing] ──── [3 Complete]
|
| 494 |
+
```
|
| 495 |
+
Yellow active step, green completed steps, gray-400 upcoming.
|
| 496 |
+
|
| 497 |
+
**Step 1 — File Selection:**
|
| 498 |
+
|
| 499 |
+
Drop Zone:
|
| 500 |
+
```
|
| 501 |
+
┌─────────────────────────────────────────────────┐
|
| 502 |
+
│ │
|
| 503 |
+
│ [Cloud upload icon — 48px, green-700] │
|
| 504 |
+
│ │
|
| 505 |
+
│ Drop your file here, or click to browse │
|
| 506 |
+
│ Supports: PDF, DOCX, TXT · Max 50MB │
|
| 507 |
+
│ │
|
| 508 |
+
└─────────────────────────────────────────────────┘
|
| 509 |
+
```
|
| 510 |
+
- Border: 2px dashed gray-300, radius-xl
|
| 511 |
+
- Drag-over: scale(1.02), green border, green-100 bg, shadow-green
|
| 512 |
+
- File preview after selection: filename, size, type icon, remove button
|
| 513 |
+
|
| 514 |
+
Document Name Field:
|
| 515 |
+
- Auto-populated from filename (editable)
|
| 516 |
+
- Character count: 0/100
|
| 517 |
+
|
| 518 |
+
[Upload & Process] button — disabled until file selected, green-700
|
| 519 |
+
|
| 520 |
+
**Step 2 — Processing (auto-navigated after upload):**
|
| 521 |
+
```
|
| 522 |
+
[Animated document icon with pages flipping — CSS animation]
|
| 523 |
+
|
| 524 |
+
Processing "Traffic Rules Kenya 2024.pdf"
|
| 525 |
+
|
| 526 |
+
[Progress bar — green-500 shimmer animation]
|
| 527 |
+
|
| 528 |
+
Step 1 of 3: Uploading to storage... ✅
|
| 529 |
+
Step 2 of 3: Extracting text... ⏳ (spinning indicator)
|
| 530 |
+
Step 3 of 3: Generating embeddings... ○ (pending)
|
| 531 |
+
|
| 532 |
+
This may take 30–90 seconds depending on document size.
|
| 533 |
+
```
|
| 534 |
+
- Poll n8n webhook every 3 seconds for status update
|
| 535 |
+
- Polled via: `GET /webhook/document-status?id={id}` with JWT
|
| 536 |
+
|
| 537 |
+
**Step 3 — Complete:**
|
| 538 |
+
```
|
| 539 |
+
[Large green checkmark — animated draw-on SVG stroke]
|
| 540 |
+
|
| 541 |
+
"Training material ready!"
|
| 542 |
+
|
| 543 |
+
"Traffic Rules Kenya 2024.pdf"
|
| 544 |
+
47 knowledge chunks indexed and ready for learner queries.
|
| 545 |
+
|
| 546 |
+
[View in Library] [Upload Another]
|
| 547 |
+
```
|
| 548 |
+
|
| 549 |
+
---
|
| 550 |
+
|
| 551 |
+
### 5.7 Analytics (`/dashboard/analytics`)
|
| 552 |
+
|
| 553 |
+
**Date Range Selector:** Today | Last 7d | Last 30d | Custom
|
| 554 |
+
Sticky below topbar on scroll.
|
| 555 |
+
|
| 556 |
+
**Top Row — KPI Cards:**
|
| 557 |
+
```
|
| 558 |
+
Total Queries | Voice Queries | Image Queries | Avg Response Time
|
| 559 |
+
```
|
| 560 |
+
|
| 561 |
+
**Input Type Distribution — Donut Chart:**
|
| 562 |
+
- Colors: Text=green-700, Voice=yellow-500, Image=green-500
|
| 563 |
+
- Recharts `PieChart` + custom tooltip
|
| 564 |
+
|
| 565 |
+
**Queries Over Time — Line Chart:**
|
| 566 |
+
- X-axis: dates | Y-axis: query count
|
| 567 |
+
- Green line, yellow fill below line at 15% opacity
|
| 568 |
+
- Smooth curve (`type="monotone"`)
|
| 569 |
+
- Custom dot on hover: green circle + tooltip card
|
| 570 |
+
|
| 571 |
+
**Top Questions Table:**
|
| 572 |
+
```
|
| 573 |
+
# | Question (truncated) | Count | Input Type | Last Asked
|
| 574 |
+
─────────────────────────────────────────────────────────
|
| 575 |
+
1 | What does a yield sign mean? | 34 | 💬 | 2h ago
|
| 576 |
+
2 | Speed limit in school zones | 28 | 🎙️ | 5h ago
|
| 577 |
+
...
|
| 578 |
+
```
|
| 579 |
+
- Hover row: green-100 bg
|
| 580 |
+
|
| 581 |
+
**Document Usage Breakdown:**
|
| 582 |
+
- Horizontal bar chart showing which documents are cited most in responses
|
| 583 |
+
- Bars: green-700 fill, yellow-500 for top document
|
| 584 |
+
|
| 585 |
+
---
|
| 586 |
+
|
| 587 |
+
### 5.8 Settings (`/dashboard/settings`)
|
| 588 |
+
|
| 589 |
+
**Sections (tabbed or accordion):**
|
| 590 |
+
|
| 591 |
+
**Profile:**
|
| 592 |
+
- Name, Email (read-only — managed by Supabase)
|
| 593 |
+
- Change Password form
|
| 594 |
+
- Avatar upload (stored in Supabase Storage)
|
| 595 |
+
|
| 596 |
+
**WhatsApp Configuration:**
|
| 597 |
+
```
|
| 598 |
+
WhatsApp Phone Number ID: [__________________]
|
| 599 |
+
Meta Access Token: [__________________] [Show/Hide]
|
| 600 |
+
Webhook Verify Token: [__________________] [Copy]
|
| 601 |
+
n8n Webhook URL: [read-only display] [Copy]
|
| 602 |
+
```
|
| 603 |
+
Each field: save inline with green checkmark on success.
|
| 604 |
+
|
| 605 |
+
**AI Configuration:**
|
| 606 |
+
```
|
| 607 |
+
Response Language: [English ▾] [Swahili ▾] [Both]
|
| 608 |
+
Max Response Length: [Concise] [Standard ●] [Detailed]
|
| 609 |
+
Web Search: [Toggle — ON/OFF]
|
| 610 |
+
Voice Reply Language: [English ▾]
|
| 611 |
+
```
|
| 612 |
+
|
| 613 |
+
**Danger Zone:**
|
| 614 |
+
- "Delete all documents and embeddings" — outlined red button → confirmation modal
|
| 615 |
+
|
| 616 |
+
---
|
| 617 |
+
|
| 618 |
+
## 6. Component Library
|
| 619 |
+
|
| 620 |
+
### 6.1 Button Variants
|
| 621 |
+
|
| 622 |
+
```
|
| 623 |
+
[Primary] → bg: green-700 | text: white | hover: green-500
|
| 624 |
+
[Secondary] → bg: white | border: green-700 | text: green-700 | hover: green-100
|
| 625 |
+
[Accent] → bg: yellow-500 | text: green-900 | hover: yellow-300
|
| 626 |
+
[Danger] → bg: white | border: error | text: error | hover: error bg
|
| 627 |
+
[Ghost] → no bg/border | text: green-700 | hover: green-100 bg
|
| 628 |
+
[Loading] → spinner replaces text, disabled, reduced opacity
|
| 629 |
+
```
|
| 630 |
+
|
| 631 |
+
All buttons: `--radius-md`, `--duration-fast` transition, `translateY(-1px)` on hover.
|
| 632 |
+
|
| 633 |
+
### 6.2 Status Badges
|
| 634 |
+
|
| 635 |
+
```css
|
| 636 |
+
.badge--pending { bg: gray-200; color: gray-600; }
|
| 637 |
+
.badge--processing { bg: yellow-100; color: yellow-700; /* + shimmer */ }
|
| 638 |
+
.badge--ready { bg: green-100; color: green-700; }
|
| 639 |
+
.badge--failed { bg: error-light; color: error; }
|
| 640 |
+
```
|
| 641 |
+
|
| 642 |
+
### 6.3 Input Fields
|
| 643 |
+
|
| 644 |
+
```css
|
| 645 |
+
.input {
|
| 646 |
+
border: 1.5px solid var(--gray-200);
|
| 647 |
+
border-radius: var(--radius-md);
|
| 648 |
+
padding: 10px 14px;
|
| 649 |
+
font: var(--font-body);
|
| 650 |
+
transition: border-color var(--duration-fast), box-shadow var(--duration-fast);
|
| 651 |
+
}
|
| 652 |
+
.input:focus {
|
| 653 |
+
border-color: var(--green-700);
|
| 654 |
+
box-shadow: 0 0 0 3px rgba(26, 92, 42, 0.12);
|
| 655 |
+
outline: none;
|
| 656 |
+
}
|
| 657 |
+
.input--error {
|
| 658 |
+
border-color: var(--error);
|
| 659 |
+
box-shadow: 0 0 0 3px rgba(214, 48, 49, 0.10);
|
| 660 |
+
}
|
| 661 |
+
```
|
| 662 |
+
|
| 663 |
+
### 6.4 Toast Notification
|
| 664 |
+
|
| 665 |
+
Position: top-right, 16px from edges, z-index: 9999
|
| 666 |
+
```
|
| 667 |
+
┌──────────────────────────────┐
|
| 668 |
+
│ ✅ Document uploaded │
|
| 669 |
+
│ Processing has started │
|
| 670 |
+
│ ████████████░░░░░░░░░░░░░░ │ ← auto-dismiss progress bar
|
| 671 |
+
└──────────────────────────────┘
|
| 672 |
+
```
|
| 673 |
+
Types: success (green), warning (yellow), error (red), info (blue)
|
| 674 |
+
Animation: slide in from right + fade, auto-dismiss 4s
|
| 675 |
+
|
| 676 |
+
---
|
| 677 |
+
|
| 678 |
+
## 7. Supabase Integration
|
| 679 |
+
|
| 680 |
+
### 7.1 Auth Setup
|
| 681 |
+
|
| 682 |
+
```javascript
|
| 683 |
+
// lib/supabase.js
|
| 684 |
+
import { createClient } from '@supabase/supabase-js'
|
| 685 |
+
|
| 686 |
+
export const supabase = createClient(
|
| 687 |
+
process.env.NEXT_PUBLIC_SUPABASE_URL,
|
| 688 |
+
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY
|
| 689 |
+
)
|
| 690 |
+
```
|
| 691 |
+
|
| 692 |
+
```javascript
|
| 693 |
+
// middleware.js — protect /dashboard/* routes
|
| 694 |
+
import { createMiddlewareClient } from '@supabase/auth-helpers-nextjs'
|
| 695 |
+
|
| 696 |
+
export async function middleware(req) {
|
| 697 |
+
const res = NextResponse.next()
|
| 698 |
+
const supabase = createMiddlewareClient({ req, res })
|
| 699 |
+
const { data: { session } } = await supabase.auth.getSession()
|
| 700 |
+
if (!session && req.nextUrl.pathname.startsWith('/dashboard')) {
|
| 701 |
+
return NextResponse.redirect(new URL('/auth/login', req.url))
|
| 702 |
+
}
|
| 703 |
+
return res
|
| 704 |
+
}
|
| 705 |
+
|
| 706 |
+
export const config = { matcher: ['/dashboard/:path*'] }
|
| 707 |
+
```
|
| 708 |
+
|
| 709 |
+
### 7.2 File Upload to Supabase Storage
|
| 710 |
+
|
| 711 |
+
```javascript
|
| 712 |
+
async function uploadDocument(file, documentName) {
|
| 713 |
+
// 1. Insert metadata record first
|
| 714 |
+
const { data: doc, error: dbError } = await supabase
|
| 715 |
+
.from('documents')
|
| 716 |
+
.insert({ name: documentName, file_type: file.type, status: 'pending' })
|
| 717 |
+
.select()
|
| 718 |
+
.single()
|
| 719 |
+
|
| 720 |
+
if (dbError) throw dbError
|
| 721 |
+
|
| 722 |
+
// 2. Upload file to storage
|
| 723 |
+
const filePath = `${doc.id}/${file.name}`
|
| 724 |
+
const { error: storageError } = await supabase.storage
|
| 725 |
+
.from('training-documents')
|
| 726 |
+
.upload(filePath, file)
|
| 727 |
+
|
| 728 |
+
if (storageError) throw storageError
|
| 729 |
+
|
| 730 |
+
// 3. Get signed URL (1 hour expiry)
|
| 731 |
+
const { data: urlData } = await supabase.storage
|
| 732 |
+
.from('training-documents')
|
| 733 |
+
.createSignedUrl(filePath, 3600)
|
| 734 |
+
|
| 735 |
+
// 4. Trigger n8n ingestion workflow
|
| 736 |
+
const session = await supabase.auth.getSession()
|
| 737 |
+
await fetch(`${process.env.NEXT_PUBLIC_N8N_WEBHOOK_BASE}/ingest`, {
|
| 738 |
+
method: 'POST',
|
| 739 |
+
headers: {
|
| 740 |
+
'Content-Type': 'application/json',
|
| 741 |
+
'Authorization': `Bearer ${session.data.session.access_token}`,
|
| 742 |
+
'x-api-secret': process.env.NEXT_PUBLIC_N8N_API_SECRET
|
| 743 |
+
},
|
| 744 |
+
body: JSON.stringify({
|
| 745 |
+
document_id: doc.id,
|
| 746 |
+
signed_url: urlData.signedUrl,
|
| 747 |
+
file_name: file.name,
|
| 748 |
+
file_type: file.type
|
| 749 |
+
})
|
| 750 |
+
})
|
| 751 |
+
|
| 752 |
+
return doc.id
|
| 753 |
+
}
|
| 754 |
+
```
|
| 755 |
+
|
| 756 |
+
### 7.3 Polling Document Status
|
| 757 |
+
|
| 758 |
+
```javascript
|
| 759 |
+
// Poll every 3 seconds until status is 'ready' or 'failed'
|
| 760 |
+
async function pollDocumentStatus(documentId, onUpdate) {
|
| 761 |
+
const interval = setInterval(async () => {
|
| 762 |
+
const { data } = await supabase
|
| 763 |
+
.from('documents')
|
| 764 |
+
.select('status, chunk_count')
|
| 765 |
+
.eq('id', documentId)
|
| 766 |
+
.single()
|
| 767 |
+
|
| 768 |
+
onUpdate(data)
|
| 769 |
+
|
| 770 |
+
if (data.status === 'ready' || data.status === 'failed') {
|
| 771 |
+
clearInterval(interval)
|
| 772 |
+
}
|
| 773 |
+
}, 3000)
|
| 774 |
+
|
| 775 |
+
return () => clearInterval(interval) // cleanup
|
| 776 |
+
}
|
| 777 |
+
```
|
| 778 |
+
|
| 779 |
+
### 7.4 Fetching Analytics from n8n
|
| 780 |
+
|
| 781 |
+
```javascript
|
| 782 |
+
async function fetchAnalytics(range = '7d') {
|
| 783 |
+
const session = await supabase.auth.getSession()
|
| 784 |
+
const res = await fetch(
|
| 785 |
+
`${process.env.NEXT_PUBLIC_N8N_WEBHOOK_BASE}/stats?range=${range}`,
|
| 786 |
+
{
|
| 787 |
+
headers: {
|
| 788 |
+
'Authorization': `Bearer ${session.data.session.access_token}`,
|
| 789 |
+
'x-api-secret': process.env.NEXT_PUBLIC_N8N_API_SECRET
|
| 790 |
+
}
|
| 791 |
+
}
|
| 792 |
+
)
|
| 793 |
+
return res.json()
|
| 794 |
+
}
|
| 795 |
+
```
|
| 796 |
+
|
| 797 |
+
---
|
| 798 |
+
|
| 799 |
+
## 8. Responsive Breakpoints
|
| 800 |
+
|
| 801 |
+
```css
|
| 802 |
+
/* Mobile-first */
|
| 803 |
+
--bp-sm: 640px; /* large phone landscape */
|
| 804 |
+
--bp-md: 768px; /* tablet portrait */
|
| 805 |
+
--bp-lg: 1024px; /* tablet landscape / small laptop */
|
| 806 |
+
--bp-xl: 1280px; /* desktop */
|
| 807 |
+
--bp-2xl: 1536px; /* wide desktop */
|
| 808 |
+
```
|
| 809 |
+
|
| 810 |
+
| Element | Mobile (<768px) | Tablet (768–1024px) | Desktop (>1024px) |
|
| 811 |
+
|---|---|---|---|
|
| 812 |
+
| Sidebar | Hidden (drawer) | Collapsed (icons only) | Full (240px) |
|
| 813 |
+
| Stats cards | 2×2 grid | 4 in a row | 4 in a row |
|
| 814 |
+
| Document grid | 1 column | 2 columns | 3 columns |
|
| 815 |
+
| Login split | Single panel | Single panel | Split 40/60 |
|
| 816 |
+
| Analytics charts | Stacked | Side by side | Side by side |
|
| 817 |
+
|
| 818 |
+
---
|
| 819 |
+
|
| 820 |
+
## 9. Accessibility
|
| 821 |
+
|
| 822 |
+
- All interactive elements: `focus-visible` ring in `green-700` at 2px offset
|
| 823 |
+
- Color contrast: all text meets WCAG AA (4.5:1 minimum)
|
| 824 |
+
- Screen reader: `aria-label` on all icon-only buttons
|
| 825 |
+
- Form inputs: `id`/`htmlFor` linked labels
|
| 826 |
+
- Status badges: `role="status"` + `aria-live="polite"` for processing states
|
| 827 |
+
- Modal: focus trap, `aria-modal="true"`, `Escape` key closes
|
| 828 |
+
- Keyboard navigation: full tab order, sidebar navigable with arrow keys
|
| 829 |
+
|
| 830 |
+
---
|
| 831 |
+
|
| 832 |
+
## 10. Performance Requirements
|
| 833 |
+
|
| 834 |
+
- Lighthouse score target: 90+ (Performance, Accessibility, Best Practices)
|
| 835 |
+
- First Contentful Paint: < 1.5s
|
| 836 |
+
- Images: next/image with lazy loading
|
| 837 |
+
- Code splitting: automatic via Next.js App Router
|
| 838 |
+
- Supabase queries: select only needed columns (never `select *`)
|
| 839 |
+
- Analytics charts: lazy-loaded (only render when tab is visible)
|
| 840 |
+
|
| 841 |
+
---
|
| 842 |
+
|
| 843 |
+
## 11. Error States
|
| 844 |
+
|
| 845 |
+
Every async action must handle three states: loading, success, error.
|
| 846 |
+
|
| 847 |
+
| Scenario | UI Behaviour |
|
| 848 |
+
|---|---|
|
| 849 |
+
| Login fails — wrong password | Inline error below password field |
|
| 850 |
+
| Login fails — no network | Toast: "Connection error. Check your internet." |
|
| 851 |
+
| Upload — file too large | Inline error in drop zone before upload |
|
| 852 |
+
| Upload — server error | Toast error + revert to Step 1 with retry button |
|
| 853 |
+
| Document delete fails | Toast error, document remains in list |
|
| 854 |
+
| Analytics load fails | Skeleton loaders replaced with "Could not load data" + retry |
|
| 855 |
+
| n8n unreachable | Yellow warning banner at top: "AI backend is currently unavailable." |
|
| 856 |
+
|
| 857 |
+
---
|
| 858 |
+
|
| 859 |
+
## 12. Skeleton Loading States
|
| 860 |
+
|
| 861 |
+
Every data-loaded section must show a skeleton before data arrives:
|
| 862 |
+
- Stat cards: gray-200 rounded rectangles, `animation: pulse 1.5s ease-in-out infinite`
|
| 863 |
+
- Table rows: alternating gray-100/gray-50 bars
|
| 864 |
+
- Chart areas: gray-200 rounded rectangle same size as chart
|
| 865 |
+
- Document cards: gray placeholder blocks
|
| 866 |
+
|
| 867 |
+
```css
|
| 868 |
+
@keyframes skeleton-pulse {
|
| 869 |
+
0%, 100% { opacity: 1; }
|
| 870 |
+
50% { opacity: 0.5; }
|
| 871 |
+
}
|
| 872 |
+
.skeleton {
|
| 873 |
+
background: var(--gray-200);
|
| 874 |
+
border-radius: var(--radius-sm);
|
| 875 |
+
animation: skeleton-pulse 1.5s ease-in-out infinite;
|
| 876 |
+
}
|
| 877 |
+
```
|
| 878 |
+
|
| 879 |
+
---
|
| 880 |
+
|
| 881 |
+
*End of PRD — DriveSmart AI Instructor Dashboard v1.0*
|
| 882 |
+
*Built for AA Kenya driving school branding. Ready for lovable.dev / v0.dev generation.*
|