Files

228 lines
7.0 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Guide 02 — Waiter PWA (React + Vite)
## Overview
A Progressive Web App installed on waiters' personal phones. Minimal, fast, touch-optimized. Works only when connected to the restaurant LAN. No offline functionality needed — show a clear error if backend is unreachable.
---
## Project Structure
```
waiter_pwa/
├── public/
│ ├── manifest.json # PWA manifest (name, icons, display: standalone)
│ └── icons/ # App icons (192x192, 512x512)
├── src/
│ ├── main.jsx
│ ├── App.jsx
│ ├── api/
│ │ └── client.js # Axios instance pointed at LOCAL backend IP
│ ├── store/
│ │ └── authStore.js # Zustand store for auth state
│ ├── pages/
│ │ ├── LoginPage.jsx
│ │ ├── TableListPage.jsx
│ │ ├── TableDetailPage.jsx
│ │ ├── AddItemsPage.jsx
│ │ └── OfflinePage.jsx
│ ├── components/
│ │ ├── PinPad.jsx
│ │ ├── TableCard.jsx
│ │ ├── OrderSummary.jsx
│ │ ├── ProductPicker.jsx
│ │ ├── ItemOptionsModal.jsx
│ │ └── ConnectionBanner.jsx
│ └── service-worker.js # Minimal SW — caches app shell only
├── vite.config.js
└── package.json
```
---
## PWA Setup
### manifest.json
```json
{
"name": "TableServe",
"short_name": "TableServe",
"start_url": "/",
"display": "standalone",
"background_color": "#0f172a",
"theme_color": "#0f172a",
"icons": [
{ "src": "/icons/icon-192.png", "sizes": "192x192", "type": "image/png" },
{ "src": "/icons/icon-512.png", "sizes": "512x512", "type": "image/png" }
]
}
```
### vite.config.js
Use `vite-plugin-pwa` for service worker generation:
```js
import { VitePWA } from 'vite-plugin-pwa'
export default {
plugins: [
VitePWA({
registerType: 'autoUpdate',
workbox: {
globPatterns: ['**/*.{js,css,html,ico,png,svg}']
// Cache app shell only — no API responses cached
}
})
]
}
```
---
## API Client (client.js)
```js
import axios from 'axios'
// This base URL must point to the LOCAL backend static IP
// It should be configurable — read from an env variable at build time
const BASE_URL = import.meta.env.VITE_API_URL || 'http://192.168.1.10:8000'
const client = axios.create({ baseURL: BASE_URL })
// Attach token to every request
client.interceptors.request.use(config => {
const token = localStorage.getItem('token')
if (token) config.headers.Authorization = `Bearer ${token}`
return config
})
// On 402/423 — show license error screen
// On network error — show offline screen
client.interceptors.response.use(
res => res,
err => {
if (!err.response) {
// Network error = backend unreachable
window.dispatchEvent(new Event('backend-offline'))
}
return Promise.reject(err)
}
)
export default client
```
---
## Auth Flow
### First-time login (no saved user)
1. Show username input + PIN pad
2. POST `/api/auth/login` → store token + username in localStorage
3. Redirect to Table List
### Returning user (username saved)
1. Show "Welcome back, [Name]" + PIN pad only
2. Same login flow
3. "Not you?" link → clears saved username → shows full login
### PIN Pad Component
- 10 digit buttons (09) + backspace + confirm
- Large touch targets (minimum 64px)
- Dots display (●●●●) as PIN is entered
- No keyboard shown — native keyboard is clunky for PINs
---
## Pages
### LoginPage
- Logo / app name at top
- If username saved: greeting + PIN pad
- If no username: username text input + PIN pad
- "Logout / Switch User" link at bottom
### TableListPage
- Header: waiter's name + logout icon
- `ConnectionBanner` shown if backend unreachable
- Grid of `TableCard` components
- Each card shows: table number, status badge (Free / Active / Your Order)
- Filter tabs: All | My Tables | Free Tables
- Tap a table → `TableDetailPage`
### TableDetailPage
- Header: "Table #X" + back button
- If no active order: large "Open Order" button
- If active order exists and waiter is assigned:
- Order summary (list of items, quantities, prices)
- Total at bottom
- "Add Items" button
- "Mark as Paid" button (can select specific items for partial payment)
- "Close Order" button (only enabled when all items are paid)
- If active order exists but waiter is NOT assigned: read-only view, no actions
### AddItemsPage
- Accessed from TableDetailPage → "Add Items"
- Category tabs at top (scrollable horizontal)
- Product grid/list below
- Tap product → `ItemOptionsModal` (select options, remove ingredients, add note, set quantity)
- Staging area at bottom: items added so far (like a cart)
- "Send Order" button — submits entire batch to backend, triggers printing
### ItemOptionsModal (bottom sheet)
- Product name + base price
- Options list (radio or checkbox depending on type) with price adjustments shown
- Ingredients list with toggle to remove each
- Freetext note input
- Quantity stepper (+/-)
- "Add to Order" button
### OfflinePage
- Shown when backend is unreachable
- Simple message: "Cannot reach the system. Please check your WiFi connection."
- Retry button that pings `/api/system/health`
---
## UI Design Direction
- **Theme**: Dark. Deep navy/slate background (`#0f172a`). This is a working tool used in dim restaurant lighting.
- **Accent**: Warm amber or teal — something that reads clearly as "action" on dark backgrounds.
- **Typography**: Clean, highly legible. Large touch targets. No tiny text.
- **Table cards**: Color-coded by status. Free = subtle/muted. Active = accented. Your table = highlighted.
- **Touch targets**: All interactive elements minimum 48px height. Prefer 64px for primary actions.
- **Transitions**: Subtle slide transitions between pages. No heavy animations — this is a tool, not a showcase.
---
## State Management (Zustand)
### authStore
```js
{
user: null, // { id, username, role }
token: null,
savedUsername: null, // persisted in localStorage
login(user, token),
logout(),
}
```
### No complex global state needed beyond auth.
- Table list: fetched on mount, local component state
- Active order: fetched when opening TableDetailPage
- AddItems cart: local component state, discarded on submit or back navigation
---
## Key UX Rules
1. Every destructive or confirm action requires a **second tap** (e.g. "Close Order" → confirmation sheet)
2. After "Send Order" succeeds, immediately navigate back to TableDetailPage and show updated order
3. If "Send Order" fails (network), show error toast — do NOT clear the staged items
4. Loading states on all async actions — buttons disabled while in-flight
5. "Add Items" should show a badge count of staged items so waiter doesn't lose track
---
## Installation Instructions (for deployment)
Include a simple printed QR code at the restaurant pointing to `http://[LOCAL_IP]:5173`
- iOS: Open in Safari → Share → Add to Home Screen
- Android: Open in Chrome → Menu → Add to Home Screen