POS UI (Renderer)
1. Document Control
| Property | Value |
|---|---|
| Package | @nx-app/sale-renderer |
| Version | 0.0.1-0 |
| Type | Web Application (SPA) |
| Runtime | Tauri WebView / Browser |
| Framework | React 19 + Vite 7 |
2. Scope & Objectives
2.1. Scope
The POS UI is the visual and interactive layer of the Point-of-Sale system. It runs inside the WebView provided by the Tauri host process (sale-main) and handles all user interactions, business flows, and visual feedback.
2.2. Objectives
- Touch Optimization: UI elements sized for touch interaction (minimum 44px target).
- Speed: Instant feedback for cart operations.
- Clarity: High-contrast design for various lighting conditions.
- Offline Support: Functional core features without network.
- Real-time Updates: WebSocket integration for order status.
3. Technology Stack
3.1. Core Framework
| Technology | Version | Purpose |
|---|---|---|
| React | ^19.2.0 | UI Component Library |
| React DOM | ^19.2.0 | DOM Rendering |
| React Router | ^7.11.0 | Client-side Routing |
| TypeScript | ~6.0.2 | Type Safety |
3.2. State Management
| Technology | Version | Purpose |
|---|---|---|
| Redux Toolkit | ^2.11.2 | Global State Management |
| React Redux | ^9.2.0 | React-Redux Bindings |
| TanStack Query | ^5.90.12 | Server State & Caching |
| ra-core | ^5.13.4 | Admin Framework Core |
3.3. UI Components
| Technology | Version | Purpose |
|---|---|---|
| radix-ui | ^1.4.3 | Accessible Primitives (unified package) |
| Tailwind CSS | ^4.1.18 | Utility-first Styling |
| Lucide React | ^0.562.0 | Icon Library |
| shadcn | ^3.6.2 | Component Templates |
| cmdk | ^1.1.1 | Command Palette |
| vaul | ^1.1.2 | Drawer Component |
| sonner | ^2.0.7 | Toast Notifications |
3.4. Tauri Integration
| Technology | Version | Purpose |
|---|---|---|
| @tauri-apps/api | ^2.9.1 | IPC Communication |
| @tauri-apps/plugin-os | ^2.3.2 | OS Information |
| @skipperndt/plugin-machine-uid | ^0.1.3 | Device Identification |
3.5. Forms
| Technology | Version | Purpose |
|---|---|---|
| React Hook Form | ^7.69.0 | Form State Management |
| @hookform/resolvers | ^5.2.2 | Schema resolver integration |
3.6. Data Visualization
| Technology | Version | Purpose |
|---|---|---|
| Recharts | 2.15.4 | Charts & Graphs |
| react-qr-code | ^2.0.18 | QR Code Generation |
| react-day-picker | ^9.13.0 | Date Selection |
3.7. Real-time Communication
| Technology | Version | Purpose |
|---|---|---|
| socket.io-client | ^4.8.3 | WebSocket Communication |
| RxJS | ^7.8.2 | Reactive Streams |
3.8. Layout & Interaction
| Technology | Version | Purpose |
|---|---|---|
| react-grid-layout | ^2.2.2 | Dashboard Layouts |
| react-resizable | ^3.1.3 | Resizable Panels |
| react-virtuoso | ^4.17.0 | Virtualized Lists |
| @tanstack/react-virtual | ^3.13.13 | Virtual Scrolling |
3.9. Build & Development
| Technology | Version | Purpose |
|---|---|---|
| Vite | ^7.2.4 | Build Tool |
| @vitejs/plugin-react | ^5.1.1 | React Integration |
| lightningcss | ^1.30.2 | CSS Processing |
| ESLint | ^9.39.1 | Code Linting |
| Prettier | ^3.8.1 | Code Formatting |
4. Architecture
4.1. Application Layers
4.2. IPC Communication
The renderer communicates with the Tauri backend via a custom IPC service:
export class IpcRendererService {
async invoke<TPayload, TResponse>(opts: {
command: string;
payload: TPayload;
}): Promise<TResponse> {
const res = await invoke(command, { payload });
return { data: res } as TResponse;
}
}4.3. Data Provider Pattern
Uses a custom TauriIpcDataProvider that extends the REST provider pattern:
export class TauriIpcDataProvider extends DefaultRestDataProvider {
override send<TResponse>(opts: {
resource: string;
params: ISendParams;
}): Promise<ISendResponse<TResponse>> {
return ipcService.invoke({
command: resource,
payload: params,
});
}
}5. Project Structure
apps/sale-renderer/
├── src/
│ ├── application/ # DI Container & Providers
│ │ ├── decorators/ # Custom Decorators
│ │ ├── providers/ # Auth & IPC Providers
│ │ │ ├── auth.provider.ts # Authentication provider
│ │ │ └── ipc-data.provider.ts # Tauri IPC data provider
│ │ └── services/ # API Services
│ │ ├── apis/ # Domain API clients (commerce / finance / identity / invoice / payment / sale)
│ │ ├── environment.service.ts
│ │ └── ipc.service.ts # Tauri IPC wrapper
│ ├── components/ # Reusable UI Components
│ │ └── ui/ # shadcn/ui components
│ ├── constants/ # Application Constants
│ ├── helpers/ # Utility Functions
│ ├── hooks/ # Custom React Hooks
│ ├── interfaces/ # TypeScript Interfaces
│ ├── layout/ # Layout Components
│ ├── libs/ # External Library Configs
│ ├── redux/ # Redux Store
│ │ └── slices/ # Redux Slices (16 slices)
│ ├── screens/ # Screen Components (14 modules)
│ └── socket/ # WebSocket Integration
│ ├── client/ # Socket client
│ └── server/ # Socket server handlers
├── scripts/ # Build Scripts
└── public/ # Static Assets6. Screens & Routing
6.1. Screen Modules
Routes are declared in App.tsx (public), screens/authenticated and screens/restaurant (nested). Directories under src/screens:
| Module | Route | Description |
|---|---|---|
| home | / | Merchant home / dashboard |
| sale-v2 | /sale/:saleOrderId | Main POS interface (FnB catalog + cart) |
| order-checkout | /order-checkout/:id | Order checkout & payment stepper |
| invoice | /invoice, /invoice/:id/show | Invoice list & detail |
| kitchen | /kitchen | Kitchen display (KDS) |
| restaurant-table | /restaurant/* | Floor / table map & reservations |
| settings | /setting | Application settings |
| customer | /customer | Customer-facing display (second screen) |
| sign-in | /login | Authentication |
| privacy-policy | /privacy-policy | Privacy policy |
| terms-and-conditions | /terms-and-conditions | Terms & conditions |
| errors | /authentication-error, /access-denied, * | 401 / 403 / 404 / 500 / 503 / crash pages |
| authenticated | (wrapper) | Auth-guarded route tree |
| restaurant | (wrapper) | Restaurant route wrapper (loads floor layout) |
6.2. Sale Screen Components
The sale screen (screens/sale-v2) is the primary interface with complex sub-components:
screens/sale-v2/
├── Sale.screen.tsx # Main sale screen
├── SaleMain.tsx # Sale layout
├── SaleOrderAlertDialog.tsx # Order alert dialog
├── index.tsx
├── fnb/ # Food & Beverage catalog
│ ├── category/ # Category navigation
│ ├── product/ # Product list
│ ├── product-bundle/ # Bundle selection
│ └── product-variant/ # Product variant selection
├── cart/ # Shopping cart
│ ├── context/ # Cart context
│ ├── items/ # Cart item list
│ │ ├── header/ # Cart header
│ │ └── item/ # Individual item
│ ├── detail/ # Cart detail view
│ │ ├── customer/ # Customer association
│ │ └── sale-channel/ # Sale channel selection
│ └── summary/ # Cart summary
│ ├── details/ # Price breakdown
│ └── actions/ # Cart actions
└── shift/ # Shift / POS session controls7. State Management
7.1. Redux Store Configuration
const appReducer = combineReducers({
temporary: temporaryReducer,
common: commonReducer,
userProfile: userProfileReducer,
order: orderReducer,
sale: saleReducer,
product: productReducer,
payment: paymentReducer,
invoice: invoiceReducer,
setting: settingReducer,
kitchen: kitchenReducer,
broadcast: broadcastReducer,
session: sessionReducer,
configuration: configurationReducer,
restaurant: restaurantReducer,
shift: shiftReducer,
financeAccount: financeAccountReducer,
});7.2. Redux Slices
| Slice | Purpose |
|---|---|
| order | Active sale order (the cart): line items, quantities & price totals |
| sale | Sale workspace state |
| product | Product catalog browsing state |
| payment | Payment flow state |
| invoice | Invoice list / detail state |
| setting | Application settings state |
| kitchen | Kitchen display (KDS) state |
| broadcast | Real-time broadcast / signal channel state |
| session | POS session state |
| configuration | Merchant / commerce configuration state |
| restaurant | Restaurant floor plan & table state |
| shift | Shift controls state |
| financeAccount | Finance account (payment method) state |
| userProfile | Authenticated user profile |
| common | Shared UI state |
| temporary | Ephemeral / transient UI data |
7.3. Order State Structure
The cart is backed by the order slice (IOrderState). Items are keyed sale-order-item records, and price totals are recomputed on every mutation:
interface IOrderState {
saleOrderCount: number;
saleOrderItemCount: number;
saleOrderItems: { [saleOrderItemId: string]: ISaleOrderItem };
itemToSaleOrderItemsMap: { [itemId: string]: string };
lastModifySaleOrderItem: string;
// Price calculations
totalPrice: number;
totalPurchaseVoucherPrice: number;
totalDiscountVoucherPrice: number;
totalTaxPrice: number;
totalPaymentPrice: number;
taxPercentage: string;
paymentMethod: string;
financeAccount: IFinanceAccount | null;
}7.4. Order Actions
| Action | Description |
|---|---|
resetOrder | Reset to initial state |
resetOrderCheckout | Clear order items & checkout info |
resetSaleOrderItems | Clear sale-order items only |
loadSaleOrderItems | Load items from backend |
addSaleOrderItem | Add item to the order |
removeSaleOrderItem | Remove item from the order |
updateQuantitySaleOrderItem | Update item quantity |
updateTaxPercentage | Set tax rate |
8. API Services
8.1. IPC API Clients
All API clients communicate over Tauri IPC and extend BaseCrudApiService (CRUD plus domain-specific commands). They live under application/services/apis, organized by backend domain:
| Domain | API Clients |
|---|---|
| identity | AuthApi, PinAuthApi, UserApi |
| commerce | CategoryApi, ProductApi, ProductVariantApi, MerchantApi, OrganizerApi, SaleChannelApi, ConfigurationApi, DeviceApi, ReceiptTemplateApi, SettingApi, AssetApi, AllocationLayoutApi, AllocationUnitApi, AllocationZoneApi |
| sale | SaleOrderApi, SaleOrderItemApi, SaleCustomerApi, ReservationApi, AllocationUsageApi, KitchenTicketApi, KitchenTicketItemApi, SalesReportApi, ShiftApi |
| finance | FinanceAccountApi, FinanceAssetApi, FinanceCategoryApi, FinanceWalletApi |
| payment | PaymentApi, PaymentAttemptApi |
| invoice | InvoiceApi |
The cart's add/remove/clear and checkout operations are served by SaleOrderApi / SaleOrderItemApi (mapped to the host's sale_order / sale_order_item commands), not a dedicated cart client.
9. WebSocket Integration
9.1. Socket Architecture
socket/
├── client/ # Client-side socket
│ ├── index.ts # Socket client exports
│ ├── socket-connection-manager.ts # Connection management
│ └── socket-subscription-manager.ts # Subscription handling
├── server/ # Server message handlers
│ ├── base/ # Base socket classes
│ │ ├── base-socket.ts # Base socket class
│ │ └── base-socket-subscription-manager.ts
│ ├── messages/ # Message handlers
│ │ └── market-data-socket-message-handler.ts
│ └── order-socket.service.ts # Order socket service
├── constants.ts # Socket constants
├── helper.ts # Socket utilities
├── types.ts # Socket types
└── index.ts # Main exports9.2. Socket Features
- Order Status Updates: Real-time order state changes
- Market Data: Live pricing updates
- Connection Management: Auto-reconnect on disconnect
- Subscription System: Subscribe/unsubscribe to channels
10. Radix UI Components
10.1. Unified Package
Radix primitives are consumed via the single unified radix-ui package (^1.4.3) rather than individual @radix-ui/react-* packages. Each primitive is imported from a named export, e.g.:
import { Dialog, DropdownMenu, Tooltip } from 'radix-ui';The shadcn/ui components under src/components/ui wrap these primitives (Accordion, Alert Dialog, Avatar, Checkbox, Collapsible, Context Menu, Dialog, Dropdown Menu, Label, Popover, Radio Group, Scroll Area, Select, Separator, Slot, Switch, Tabs, Tooltip, and more).
11. The Cart System
11.1. Cart Features
- Item Aggregation: Group identical items by variant
- Quantity Management: Increment/decrement with validation
- Price Calculations: Real-time subtotal, tax, discounts
- Payment Methods: Cash, QR, Card support
- Tax Handling: Configurable tax percentage
11.2. Price Calculation Logic
// Calculate totals
totalPrice = items.reduce((sum, item) =>
sum + item.unitPrice * item.quantity, 0);
// Apply discounts and tax
const taxableAmount = Math.max(0, totalPrice - discount);
totalTaxPrice = Math.round(taxableAmount * (taxRate / 100));
totalPaymentPrice = totalPrice
- totalDiscountVoucherPrice
- totalPurchaseVoucherPrice
+ totalTaxPrice;12. Checkout Flow
12.1. Flow Diagram
12.2. Checkout Steps
- Cart Review: Final verification of items
- Customer Association: Optional customer linking
- Payment Selection: Choose Cash, QR, or Card
- Transaction Execution: Process via Tauri IPC
- Receipt Generation: Format for printer
- Order Completion: Clear cart and update state
13. Integration Points
13.1. Upstream (Tauri Backend)
- IPC Commands: Invoke Rust functions via
@tauri-apps/api - Events: Listen for
init_ready,init_error,migration_error - Window API: Manage windows, external display
13.2. Lateral (Shared Packages)
- @nx-app/core: Shared utilities and locales
- @minimaltech/ra-core-infra: Base service infrastructure
- @venizia/ignis-inversion: IoC container
13.3. Downstream (Backend API)
- WebSocket: Real-time order updates
- Telemetry: Error logging and analytics
14. Build & Scripts
14.1. Available Scripts
| Script | Command | Purpose |
|---|---|---|
dev | vite --mode dev | Development server |
build | sh ./scripts/build.sh | Production build |
build:develop | sh ./scripts/rebuild.sh development | Dev build |
build:production | sh ./scripts/rebuild.sh production | Prod build |
preview | vite preview | Preview build |
lint | sh ./scripts/lint.sh | ESLint check |
prettier | prettier '**/*.{js,ts,jsx,tsx}' --write | Format code |
The dev server runs on port 3002 (strictPort, from vite.config.ts), which the Tauri host loads via devUrl: http://localhost:3002/.
14.2. Build Output
The build is consumed by the Tauri host process:
- Output directory:
dist/ - Served by Tauri's localhost plugin
- Embedded in desktop/mobile app
15. Code Statistics
| Metric | Count |
|---|---|
| Screen Modules | 14 |
| Redux Slices | 16 |
| API Clients | commerce / finance / identity / invoice / payment / sale domains |
| Radix UI | unified radix-ui package |
| Socket Handlers | 5+ |
| Custom Hooks | 10+ |