Frontend Modules¶
Tech Stack¶
- Framework: Flet (Python) - cross-platform (web, mobile, desktop)
- Communication: httpx for async API calls
- Images: SVG exclusively
- Typography: System (Inter on web)
- Architecture: Clean Architecture + Clean Code
Module Structure¶
modules/<module>/
├── domain/ # Entities, repositories (abstract), usecases
├── data/ # Datasources, repository implementations
└── presentation/ # Controllers, pages, components
Domain Layer¶
| Can Import | Cannot Import |
|---|---|
core.error |
flet, httpx, other modules |
Data Layer¶
| Can Import | Cannot Import |
|---|---|
domain, core.network, core.error |
flet, shared |
Presentation Layer¶
| Can Import | Cannot Import |
|---|---|
domain, core.theme, flet |
data, shared |
Core Layer¶
core/
├── config.py # Environment configuration
├── error/ # Result type (Success/Failure)
├── di/ # Service locator
├── network/ # ApiClient (httpx async)
├── router/ # AppRouter
├── security/ # Rate limiter, input validation, IP blocklist
├── storage/ # SharedPreferences, session storage
├── theme/ # Theme system (tokens + builder)
└── realtime/ # WebSocket client
Shared Layer¶
shared/
├── i18n/ # Translations service (t, t_text, t_drawer)
├── components/ # Shared components (app_bar, reactive_text_field)
├── pages/ # Shared pages (dashboard)
└── page_manager.py # Singleton for page state
Design System (Kinetic Courier)¶
Colors¶
| Token | Light Value | Usage |
|---|---|---|
primary |
#006e2d |
Primary actions, Spotify Green |
on-primary |
#ffffff |
Text on primary |
surface |
#f4fcef |
Background |
on-surface |
#161d16 |
Primary text |
Typography (Inter)¶
| Style | Size | Weight | Usage |
|---|---|---|---|
| Display | 48px | 700 | Hero sections |
| Title | 24px | 600 | Screen headers |
| Subtitle | 18px | 500 | Section headers |
| Body | 16px | 400 | General content |
| Caption | 12px | 500 | Metadata |
Spacing¶
- Base unit: 4px
- Touch target minimum: 44px
- Safe area bottom:
constant(safe-area-inset-bottom)
Shapes¶
- Cards: 16px radius
- Buttons: Pill-shaped (primary), 12px (secondary)
- Inputs: 8px radius
Key Pages¶
| Route | Page | Description |
|---|---|---|
/login |
LoginPage | Authentication |
/dashboard |
DashboardPage | Main dashboard with NavigationDrawer |
/forgot-password |
ForgotPasswordPage | Password reset request |
/reset-password |
ResetPasswordPage | New password entry |
/track |
TrackingPage | Public tracking (no auth) |
/shipments/board |
ShipmentsBoardPage | Kanban-style board |
/scan/result |
ScanResultPage | Scan result display |
/scan/package |
PackageDetailPage | Package details after scan |
/scan/bag |
BagDetailPage | Bag details after scan |
Scanner Module (modules/scanner/)¶
Core:
- scan_handler.py - ScanHandler singleton (pending scans, sync, process)
- deeplinks.py - parse_scan_url, build_scan_url (base64 encoded)
Presentation:
- scan_handler_page.py - Scan result display with validation icons
- package_detail_page.py - Package info after scanning QR
- bag_detail_page.py - Bag info after scanning QR
Permissions:
- permissions_service.py - Request camera, location, storage permissions
- location_service.py - Geolocator integration for GPS coordinates
- camera_service.py - OpenCV/pyzbar for QR scanning
QR Format:
{
"t": "pkg" | "bag", // entity type
"i": "uuid", // entity ID
"c": "hmac_sha256", // HMAC signature
"v": 1 // version
}
Deep Link Format: app://host/scan?data=<base64_json>
Pending Scans Flow:
1. User scans QR → stored in ScanHandler._pending_scan
2. If not authenticated → saved to SharedPreferences as pending
3. After login → SyncManager.sync_pending_scans() uploads all pending
4. Scan result displayed via scan_result_page
Sync Manager (shared/sync_manager.py)¶
Post-login sync for pending operations:
- sync_pending_scans() - Uploads all pending scans after login
- has_pending_scans() - Checks if there are pending scans
- get_pending_count() - Returns count of pending scans
Security Features¶
- Token encryption (Fernet/AES) in SharedPreferences
- HTTPS enforcement in production
- Rate limiting (30 req/min for tracking)
- IP blocklist after 50 failed attempts
- Input validation and sanitization
- Honeypot anti-bot field
i18n System¶
Translation files in assets/i18n/:
- en.json - English
- es.json - Spanish
| Function | Usage | Returns |
|---|---|---|
t("key") |
Static labels | str |
t_text("key", page, **kwargs) |
Reactive Text | ft.Text |
t_drawer(destination, "nav.home") |
NavigationDrawer labels | updates dest.label |
Change language with set_language(Lang("es")) + refresh_and_update(page).
Testing¶
Current coverage: 275 tests passing (2 skipped)
| Module | Tests |
|---|---|
| core/error | 3 |
| core/theme | 6 |
| auth/domain | 22 |
| core/storage | 8 |
| validators | 13 |
| translations | 20 |
| core/security | 37 |
| modules/tracking | 37 |
| modules/shipments | 18 |
| core/realtime | 12 |
| modules/scanner | 21 |
Multi-Tenant Architecture¶
From saas-fronted/SPEC.md §3:
- URL única por tenant:
https://{tenant-slug}.saas-courier.com - Personalización por tenant: colores (primarios, secundarios), logo, layouts específicos
- Componentes: comunes para todos los tenants
- Animaciones/tiempos: fijas, no configurables por tenant
Ver también: Design System · Components · Backend Modules