305 lines
14 KiB
Markdown
305 lines
14 KiB
Markdown
LunarFront Platform
|
|
|
|
Frontend Strategy — Multiple UI Surfaces
|
|
|
|
Version 1.0 | Draft
|
|
|
|
|
|
|
|
# 1. Overview
|
|
|
|
LunarFront serves different users in different contexts — a store owner at their desk, a cashier at the counter, a staff member receiving stock in the back room, and a salesperson at a trade show. One UI cannot serve all of these well.
|
|
|
|
The platform uses a single backend API with multiple purpose-built frontends. Each frontend is a separate package in the monorepo, shares the same `@lunarfront/shared` schemas and types, and calls the same REST API. The backend doesn't know or care which frontend is making the request.
|
|
|
|
|
|
|
|
# 2. Frontend Packages
|
|
|
|
## 2.1 Admin UI — `packages/admin`
|
|
|
|
**Status:** Active development (current)
|
|
|
|
| Attribute | Detail |
|
|
|-----------|--------|
|
|
| Form factor | Desktop / laptop, large tablet |
|
|
| Stack | React, Vite, TanStack Router, TanStack Query, shadcn/ui |
|
|
| Users | Store owner, manager, back-office staff |
|
|
| Access | Web browser |
|
|
|
|
**Purpose:** Full management interface. Everything that isn't a real-time transaction happens here — accounts, members, inventory configuration, RBAC, roles, reports, settings, lesson scheduling, repair management, vault.
|
|
|
|
**Design priorities:**
|
|
- Dense information display (data tables, detail pages)
|
|
- Keyboard-friendly (tab navigation, search)
|
|
- Multi-column layouts, sidebars, dialogs
|
|
- Not optimized for touch or small screens — that's intentional
|
|
|
|
## 2.2 POS UI — `packages/pos`
|
|
|
|
**Status:** Planned
|
|
|
|
| Attribute | Detail |
|
|
|-----------|--------|
|
|
| Form factor | Counter-mounted tablet (10"+) or touchscreen monitor |
|
|
| Stack | React, Vite (same toolchain as admin) |
|
|
| Users | Cashier, sales associate |
|
|
| Access | Web browser (full-screen / kiosk mode) |
|
|
|
|
**Purpose:** Point-of-sale for in-store transactions. Staff stares at this 8 hours a day. Every interaction must be fast and require minimal taps.
|
|
|
|
**Design priorities:**
|
|
- Always-on, single-screen (no page navigation)
|
|
- Large touch targets (44px+ minimum)
|
|
- Fast product search (keyboard wedge barcode scanner + text search)
|
|
- Cart management (add, remove, adjust quantity, apply discount)
|
|
- Payment flow (cash, card via terminal, split payment)
|
|
- Cash drawer integration
|
|
- Receipt printing (thermal printer via browser print or ESC/POS)
|
|
- Session management (clock in/out, cash drawer open/close, X/Z reports)
|
|
- Offline resilience — queue transactions if network drops, sync when back
|
|
|
|
**PIN unlock:**
|
|
|
|
The POS terminal stays authenticated but locks between use. Staff unlock with a 4-6 digit PIN instead of email/password. The PIN maps to their real user account — every action (sale, void, drawer open) is attributed to the person who unlocked.
|
|
|
|
- Each user sets a numeric PIN from their profile or admin assigns one
|
|
- PIN is stored as a bcrypt hash on the user record (separate from password)
|
|
- POS lock screen shows a numeric keypad — tap PIN, unlock, start selling
|
|
- Auto-locks after configurable idle timeout (default: 2 minutes)
|
|
- Manual lock button always visible
|
|
- Shift change: current user walks away, screen locks, next user enters their PIN
|
|
- Wrong PIN: brief delay, max 5 attempts then require full login
|
|
- PIN unlock issues a short-lived JWT (or extends the existing session) scoped to POS permissions
|
|
- Backend: `POST /v1/auth/pin-login` — accepts `{ pin }`, returns token with same user identity and permissions
|
|
- PIN is optional — users without a PIN set must use full email/password login on the POS
|
|
|
|
This gives full audit trail (who did what) without the friction of typing credentials hundreds of times a day.
|
|
|
|
**Discounts & price overrides:**
|
|
- Line item discount (percentage or flat dollar amount)
|
|
- Whole-cart discount (e.g. 10% off everything)
|
|
- Price override with reason code (item rings up wrong, manual correction)
|
|
- Manager override: cashier attempts a discount or void above their permission level, manager punches their PIN to approve without taking over the session. Logged as "approved by [manager] for [cashier]"
|
|
- Configurable discount limits per role (e.g. sales associate can give up to 10%, manager up to 25%, admin unlimited)
|
|
|
|
**Parked carts / layaway:**
|
|
- Park a cart — customer wants to keep shopping or come back later
|
|
- Multiple parked carts, each tagged with customer name or account
|
|
- Retrieve a parked cart to resume the sale
|
|
- Layaway: park with a deposit, schedule remaining payments
|
|
- Auto-expire parked carts after configurable period (default: 7 days)
|
|
|
|
**Gift cards & store credit:**
|
|
- Sell a gift card (physical or digital, generates a unique code)
|
|
- Redeem gift card as payment method (scan or type code)
|
|
- Check balance
|
|
- Issue store credit (returns, adjustments) — tied to an account
|
|
- Store credit appears as a payment option when account is linked to the sale
|
|
|
|
**Returns:**
|
|
- With receipt: lookup transaction by receipt number or barcode, select items, refund to original payment method
|
|
- Without receipt: store credit only, capped amount (configurable), requires manager override
|
|
- Exchange: return + new sale in one transaction
|
|
- Restocking fee support (percentage or flat, configurable per category)
|
|
- Returned items: prompt for condition (resellable, damaged, defective) — updates inventory accordingly
|
|
|
|
**End of day / cash management:**
|
|
- **Open session:** cashier counts starting cash, enters amount, system records
|
|
- **Cash drops:** mid-shift safe drops, logged with amount and time
|
|
- **Close session:** two modes:
|
|
- *Counted close:* cashier counts the drawer, enters totals by denomination, system calculates over/short
|
|
- *Blind close:* cashier counts and enters total without seeing expected amount — prevents fudging
|
|
- **Z report:** end-of-day summary — gross sales, net sales, tax collected, payment method breakdown (cash, card, gift card, store credit), refunds, discounts given, over/short
|
|
- **X report:** mid-day snapshot (same data as Z but doesn't close the session)
|
|
- All reports printable on receipt printer
|
|
|
|
**Training mode:**
|
|
- Toggle per POS session — transactions don't hit real inventory or accounting
|
|
- Visually distinct: large "TRAINING MODE" banner across the screen, different background color
|
|
- Manager enables/disables via their PIN
|
|
- Training transactions are logged separately for review but don't affect reports or stock
|
|
|
|
**Customer-facing display:**
|
|
- Second screen (or portion of a split screen) showing the customer what's being rung up
|
|
- Line items, quantities, prices, running total
|
|
- Shows payment status ("Insert card", "Approved", "Change due: $4.25")
|
|
- Some jurisdictions legally require itemized display during transaction
|
|
- Configurable: on/off, which screen, store branding/logo
|
|
|
|
**Quick keys / favorites:**
|
|
- Configurable grid of frequently sold items (strings, picks, reeds, cables, lesson payments)
|
|
- Admin configures the grid layout (drag and drop in admin UI)
|
|
- Supports categories/tabs (e.g. "Accessories", "Lessons", "Repairs")
|
|
- Faster than searching — one tap to add to cart
|
|
- Can also map to common actions (open drawer, apply house discount, gift card sale)
|
|
|
|
**Receipts:**
|
|
- Thermal printer via ESC/POS (USB or network)
|
|
- Browser print fallback with receipt-formatted CSS
|
|
- Email receipt option (if customer has account with email)
|
|
- Text/SMS receipt (future, via email system)
|
|
- Configurable header/footer (store name, address, return policy, social media)
|
|
- Barcode/QR on receipt for easy return lookup
|
|
|
|
**Key screens:**
|
|
1. **Lock screen** — numeric keypad, current time, store name
|
|
2. **Sale screen** — product search (left), quick keys (top), cart (right), totals + pay button (bottom)
|
|
3. **Payment screen** — amount due, payment method selection, card terminal status, change calculation, tip (optional)
|
|
4. **Return/exchange screen** — lookup original transaction, select items, refund method, condition prompt
|
|
5. **Parked carts** — list of held transactions, tap to resume
|
|
6. **Drawer management** — open session, cash drops, close session with denomination counting
|
|
7. **Reports** — X report, Z report, transaction history for current session
|
|
|
|
**Hardware integration:**
|
|
- Barcode scanner: keyboard wedge (types into focused input, no special API)
|
|
- Card reader: local network or USB terminal via Stripe Terminal SDK or Global Payments SDK
|
|
- Cash drawer: triggered via receipt printer (kick pulse) or USB relay
|
|
- Receipt printer: ESC/POS over USB or network, or browser `window.print()` with receipt-formatted CSS
|
|
- Customer display: secondary screen via `window.open()` or dedicated display API
|
|
- Scale (future): for items sold by weight
|
|
|
|
## 2.3 Floor App — `packages/floor`
|
|
|
|
**Status:** Planned
|
|
|
|
| Attribute | Detail |
|
|
|-----------|--------|
|
|
| Form factor | Phone or small tablet |
|
|
| Stack | React Native (Expo) or PWA |
|
|
| Users | Sales staff on floor, inventory staff, trade show sales |
|
|
| Access | Native app (iOS/Android) or PWA in Safari/Chrome |
|
|
|
|
**Purpose:** Two modes in one mobile app — inventory management and mobile sales. Both are walk-around-the-store (or walk-around-a-trade-show) workflows with scanning, big inputs, and minimal typing.
|
|
|
|
**Design priorities:**
|
|
- Phone-first layout (single column, stacked)
|
|
- Camera barcode scanning (no external scanner needed)
|
|
- Large buttons, minimal text input
|
|
- Works on cellular (trade shows) and store WiFi
|
|
- Offline support for inventory counts (sync when connected)
|
|
|
|
### Inventory Mode
|
|
|
|
**Purpose:** Receiving, cycle counts, transfers, shelf checks.
|
|
|
|
**Key screens:**
|
|
1. **Receive stock** — scan items, enter quantities, confirm receipt against purchase order
|
|
2. **Cycle count** — scan location/shelf, scan items, enter counts, submit discrepancies
|
|
3. **Quick lookup** — scan barcode, see product details, stock levels across locations, price history
|
|
4. **Transfer** — scan items to move between locations
|
|
5. **Condition check** — scan item, update condition (new, used, damaged), add notes/photo
|
|
|
|
### Mobile POS Mode
|
|
|
|
**Purpose:** Sales at trade shows, on the shop floor, off-site events.
|
|
|
|
**Key screens:**
|
|
1. **Quick sale** — search/scan products, add to cart, take payment
|
|
2. **Payment** — Bluetooth card reader integration, or manual card entry
|
|
3. **Customer lookup** — search by name/phone/account number for account-linked sales
|
|
4. **Receipt** — email or text receipt (no printer at a trade show)
|
|
|
|
### Build Strategy
|
|
|
|
**Phase 1 — PWA:**
|
|
- Build as a web app with mobile-optimized UI
|
|
- "Add to Home Screen" for app-like experience
|
|
- Camera scanning via `navigator.mediaDevices` + barcode detection library
|
|
- Local network card reader for payments
|
|
- No App Store, no developer cert, works on any device
|
|
|
|
**Phase 2 — Native (if needed):**
|
|
- React Native with Expo
|
|
- Reuses `@lunarfront/shared` schemas and business logic
|
|
- Native Bluetooth for card reader pairing
|
|
- Native camera for faster barcode scanning
|
|
- Requires Apple Developer ($99/year) and Google Play ($25 one-time)
|
|
- Only worth it if PWA limitations become blockers (Bluetooth, offline, performance)
|
|
|
|
|
|
|
|
# 3. Shared Infrastructure
|
|
|
|
All frontends share:
|
|
|
|
| Layer | Package | What it provides |
|
|
|-------|---------|------------------|
|
|
| Validation | `@lunarfront/shared` | Zod schemas — same validation client and server |
|
|
| Types | `@lunarfront/shared` | TypeScript interfaces for all domain entities |
|
|
| Business logic | `@lunarfront/shared` | Formatters, calculators, state normalization |
|
|
| API | Backend REST | Same endpoints, same auth, same response format |
|
|
| Auth | JWT | Same token works across all frontends |
|
|
| Permissions | RBAC | Same permission checks — POS user doesn't need admin access |
|
|
|
|
This means:
|
|
- A bug fix in a Zod schema fixes validation everywhere
|
|
- A new API endpoint is immediately available to all frontends
|
|
- No data format translation between frontends
|
|
- One test suite covers the API regardless of which frontend calls it
|
|
|
|
|
|
|
|
# 4. Permission Mapping
|
|
|
|
Different frontends surface different permissions:
|
|
|
|
| Permission | Admin | POS | Floor |
|
|
|------------|-------|-----|-------|
|
|
| `accounts.view/edit/admin` | Yes | View only (customer lookup) | View only |
|
|
| `inventory.view/edit/admin` | Yes | View only (product search) | Full access |
|
|
| `pos.view/edit/admin` | Reports only | Full access | Mobile POS mode |
|
|
| `rentals.*` | Yes | Process returns | No |
|
|
| `lessons.*` | Yes | No | No |
|
|
| `repairs.*` | Yes | Intake only | No |
|
|
| `vault.*` | Yes | No | No |
|
|
| `users.*` | Yes | No | No |
|
|
|
|
The backend enforces permissions regardless — the frontend just decides what to show.
|
|
|
|
|
|
|
|
# 5. Offline Strategy
|
|
|
|
Offline support is critical for POS (network drops) and Floor (dead zones in warehouse).
|
|
|
|
**POS offline:**
|
|
- Cache product catalog in IndexedDB (refresh periodically)
|
|
- Queue transactions locally when offline
|
|
- Process card payments when back online (or show "cash only" mode)
|
|
- Sync queue when connection restores
|
|
- Visual indicator: "Offline — transactions will sync when connected"
|
|
|
|
**Floor offline (inventory):**
|
|
- Cache current inventory counts
|
|
- Store count entries locally
|
|
- Sync on reconnect
|
|
- Conflict resolution: last-write-wins with audit log of discrepancies
|
|
|
|
**Admin:**
|
|
- No offline support needed. Back-office work requires connectivity.
|
|
|
|
|
|
|
|
# 6. Deployment
|
|
|
|
| Frontend | Hosting | Notes |
|
|
|----------|---------|-------|
|
|
| Admin | Same server as backend (Caddy/nginx serves static files) | Single origin, no CORS |
|
|
| POS | Same server or dedicated POS terminal | Kiosk mode browser, auto-launch on boot |
|
|
| Floor | PWA from same server, or App Store | Phone/tablet accesses over store WiFi or cellular |
|
|
|
|
For on-prem: all frontends are served from the same LunarFront server. One box, one URL, multiple apps at different paths (`/admin`, `/pos`, `/floor`).
|
|
|
|
|
|
|
|
# 7. Implementation Order
|
|
|
|
1. **Admin UI** — current, continue until core management is complete
|
|
2. **POS UI** — next major effort after admin is stable. This is the revenue-critical surface.
|
|
3. **Floor App (Inventory mode)** — PWA first. Enables stock receiving and cycle counts.
|
|
4. **Floor App (Mobile POS mode)** — add-on to floor app. Trade show / floor sales.
|
|
5. **Floor App (Native)** — only if PWA hits limitations. Bluetooth reader, performance, offline.
|
|
|
|
Each frontend is independently deployable. Stores can run just Admin + POS if they don't need mobile inventory. Modules control which features are available, not which frontends are installed.
|