Appearance
Frontend Applications Overview
Upvendo has three separate Vue 3 frontend applications, each targeting a different user persona and use case. All three communicate with the backend through the Cloudflare Workers proxy.
Application Summary
| App | Repository | Framework | UI Library | State Management | Target User |
|---|---|---|---|---|---|
| Backoffice | upvendo-backoffice | Vue 3 | Vuetify 3 | Vuex | Merchant staff |
| Kiosk | upvendo-kiosk | Vue 3 | Tailwind CSS | Pinia | End customers (self-service) |
| Online Ordering | upvendo-online-ordering | Vue 3 | Tailwind CSS | Pinia | End customers (web) |
Backoffice Application
Purpose: The merchant management dashboard. Used by business owners, managers, and staff to configure their restaurants, manage menus, view transactions, handle customers, and manage all aspects of their Upvendo setup.
Technology Stack
| Component | Technology |
|---|---|
| Framework | Vue 3 (Composition API + Options API mix) |
| UI Library | Vuetify 3 (Material Design) |
| State Management | Vuex 4 (modular stores) |
| Routing | Vue Router 4 |
| HTTP Client | Axios |
| Permissions | CASL (Attribute-Based Access Control) |
| Mobile Wrapper | Capacitor (iOS/Android) |
| Build Tool | Vite |
Architecture Patterns
Vuex Modular Store: The Vuex store is split into domain-specific modules that mirror the backend service structure:
auth-- Authentication state, JWT token managementvendor-- Current vendor datalocation-- Active location selectionmenu-- Menu managementitem-- Item/product managementcategory-- Category managementmodifier-- Modifier and modifier group managementtransaction-- Transaction/order datacustomer-- Customer managementdevice-- Device managementsettings-- Application settingsloyalty-- Loyalty program configurationoffer-- Offer management
Each module typically includes: state, mutations, actions (for API calls), and getters.
CASL Permissions: The backoffice uses CASL for fine-grained permission checking. Permissions are loaded from the backend based on the user's roles and define what actions (create, read, update, delete) are allowed on which resources.
Permissions flow:
- User logs in -> JWT token received
- Permissions fetched from backend based on user roles
- CASL ability instance created with permission rules
- Components use
v-if="can('update', 'Item')"to conditionally show/hide UI - Route guards check permissions before navigation
Multi-Location Support: The backoffice supports multi-location merchants. The active location is stored in state and many API calls include a location_id parameter. Location switching updates the store and refreshes data.
Capacitor Mobile Wrapper: The backoffice is wrapped in Capacitor for iOS and Android deployment. The CapacitorApiKey header is sent with API requests for device identification. Native features accessed through Capacitor include:
- Push notifications (Firebase)
- Camera access (for photo studio)
- Biometric authentication
- App update management
API Integration
All API calls go through an Axios instance configured with:
- Base URL pointing to the Cloudflare Workers proxy
- JWT bearer token in Authorization header
X-Capacitor-API-Keyheader for mobile clients- Response interceptors for 401 handling (token refresh or logout)
- Request interceptors for adding tenant context
Key Features
- Menu and item management (CRUD, drag-and-drop ordering)
- Transaction history and reporting
- Customer management and loyalty programs
- Device and kiosk management
- Branding and visual customization
- Multi-language content management
- Third-party integration configuration (Square, Deliveroo, UberEats, Shopify)
- Photo studio for item images
- Team member management with role-based permissions
- Tax rate and billing configuration
- Guided setup wizard for new merchants
Kiosk Application
Purpose: Self-service ordering application running on kiosk hardware (tablets/touchscreens) in physical restaurant locations. Customers use it to browse the menu, customize orders, and pay.
Technology Stack
| Component | Technology |
|---|---|
| Framework | Vue 3 (Composition API) |
| UI Library | Tailwind CSS |
| State Management | Pinia |
| Routing | Vue Router 4 |
| HTTP Client | Axios |
| Mobile Wrapper | Capacitor (Android primarily) |
| Device SDK | Custom device SDK integration |
| Push Notifications | Firebase Cloud Messaging |
| Build Tool | Vite |
Architecture Patterns
Pinia Stores: The kiosk uses Pinia for state management, organized by domain:
useAuthStore-- Device authentication stateuseCartStore-- Shopping cart with items, modifiers, quantitiesuseMenuStore-- Active menu, display groups, itemsuseLocationStore-- Current location data, business hoursuseSettingsStore-- Kiosk configuration, branding, languageusePaymentStore-- Payment flow stateuseOrderStore-- Order submission and trackinguseLoyaltyStore-- Customer loyalty points and rewardsuseStockStore-- Real-time stock availability (via D1 proxy)
Device-First Design: The kiosk is designed for touchscreen interaction:
- Large touch targets
- No keyboard input (on-screen keyboard for customer info)
- Idle timeout with screensaver/attract mode
- Hardware integration (payment terminals, receipt printers)
- Custom tap recognizer (
v-kiosk-tap) for all interactive elements -- Android WebView suppressesclickevents on long presses, so the kiosk app replaces@clickwith a custom Vue directive that listens to raw touch events. See Kiosk Tap Directive for the full rationale and rules.
Cart Management: The cart store handles:
- Adding/removing items with modifier selections
- Quantity adjustments
- Stock reservation via the D1 proxy (prevents overselling)
- Price calculation including modifiers, taxes, and discounts
- Loyalty reward application
- Dining option selection (for here / takeout)
Payment Flow: Payment integration varies by terminal:
- Stripe Terminal (physical card reader)
- Viva Wallet Terminal
- The payment store orchestrates the terminal communication through the backend
Firebase Integration: Used for:
- Push notifications for order status updates
- Remote configuration updates
- Analytics
API Integration
The kiosk communicates through two proxy paths:
/api/*-- Standard backend API calls for authentication, menu data, order submission/d1-api/*-- Direct D1 calls for real-time stock checking and reservation
Stock flow:
- Menu loads -> stock levels fetched from D1 for all items
- Customer adds item to cart -> reservation created in D1
- Customer completes order -> reservations released, stock decremented
- Customer abandons cart -> reservations expire after 10 minutes
Key Features
- Full menu browsing with categories and display groups
- Item detail view with allergen/ingredient information
- Modifier selection with pricing
- Variant group selection (e.g., sizes)
- Shopping cart with real-time stock validation
- Loyalty program integration (scan/enter loyalty card)
- Multiple dining options (for here, takeout)
- Integrated payment terminal support
- Receipt printing (Star Micronics)
- Multi-language support (language selection at start)
- Idle timeout and attract mode
- Accessibility considerations for touchscreen use
Online Ordering Application (Zestidoo)
Purpose: Customer-facing web application for online ordering. Customers access it through custom branded domains (e.g., zestidoo.be/merchant-slug) to place delivery or pickup orders.
Technology Stack
| Component | Technology |
|---|---|
| Framework | Vue 3 (Composition API) |
| UI Library | Tailwind CSS |
| State Management | Pinia |
| Routing | Vue Router 4 |
| HTTP Client | Axios |
| Build Tool | Vite |
Architecture Patterns
Multi-Merchant Routing: The online ordering app serves multiple merchants from a single deployment. Merchant identification works through:
- Slug-based routing: URLs contain the merchant slug (e.g.,
/acme-restaurant/menu) - Domain-based routing: Custom domains resolve to specific merchants
- Tenant resolution: The
SetTenantDatabase:online-orderingmiddleware on the backend uses the slug to find the correct tenant database
Pinia Stores:
useMerchantStore-- Current merchant data, branding, locationsuseMenuStore-- Active menu, categories, itemsuseCartStore-- Shopping cart managementuseCustomerStore-- Customer authentication and profileuseOrderStore-- Order placement and trackingusePaymentStore-- Online payment flow (Stripe)useLocationStore-- Selected location, delivery zones, business hours
Branding System: Each merchant has a custom visual identity applied at runtime:
- Primary/secondary colors from BrandingProfile
- Logo and cover images from Cloudflare Images
- Custom fonts (if configured)
- The branding is loaded during merchant resolution and applied as CSS variables
Order Flow:
- Customer arrives at merchant page (via slug or custom domain)
- Location selection (if merchant has multiple locations)
- Menu browsing with real-time availability
- Cart building with modifier selection
- Dining option selection (delivery / pickup)
- Customer authentication (login or guest checkout)
- Delivery address entry (for delivery orders)
- Payment via Stripe
- Order confirmation with real-time status tracking
API Integration
The online ordering app primarily uses /api/* through the proxy. Key differences from backoffice/kiosk API usage:
- No JWT for initial load -- Menu and merchant data is loaded without authentication
- Slug-based tenant resolution -- The merchant slug is passed in the URL, not in a JWT
- Customer authentication -- Separate auth flow (phone + OTP, not email + password)
- Stock via proxy -- Stock availability comes through the D1 proxy for real-time data
Multi-Domain Deployment
The app is deployed to multiple domains per country:
| Domain | Country |
|---|---|
zestidoo.com | Global |
zestidoo.be | Belgium |
zestidoo.nl | Netherlands |
zestidoo.fr | France |
zestidoo.de | Germany |
zestidoo.co.uk | UK |
Each domain serves the same application but may have country-specific defaults (currency, language, etc.).
Key Features
- Merchant landing page with branding
- Location selection with map view
- Full menu browsing with search and categories
- Item detail with allergens, ingredients, dietary info
- Modifier and variant selection
- Shopping cart with subtotal calculation
- Guest checkout or authenticated ordering
- Delivery address management with geocoding
- Pickup time selection
- Stripe online payment
- Order tracking with real-time status updates
- Order history for authenticated customers
- Multi-language support
- Responsive design (mobile-first)
- SEO-friendly rendering
Shared Patterns Across All Frontends
API Layer Architecture
All three apps follow the same API integration pattern:
- Axios instance with base URL pointing to the Cloudflare Workers proxy
- Request interceptors for adding authentication headers
- Response interceptors for handling 401 (unauthorized) responses
- Service modules that wrap API calls with typed parameters and return values
Multi-Language Support
All apps support multiple languages:
- Language data is fetched from the backend per tenant
- Items, categories, and menus use the
detailsobject with language-keyed names/descriptions - The UI itself uses Vue i18n (or equivalent) for static strings
- Language selection is stored in local state
Branding Integration
Each app loads branding from the backend:
- Colors (primary, secondary, accent)
- Logo images (via Cloudflare Images with variant presets)
- The kiosk and online ordering apps apply branding as CSS custom properties
- The backoffice shows branding previews in the settings UI
Error Handling
Common error handling approach:
- Network errors show toast/snackbar notifications
- Validation errors are mapped to form field errors
- 401 responses trigger re-authentication flow
- 500 errors show generic error messages with retry options
Environment Configuration
All apps use Vite environment variables for configuration:
VITE_API_BASE_URL-- Points to the correct proxy environmentVITE_ENVIRONMENT-- Current environment (production, staging, testing)- Environment-specific
.envfiles for each deployment target
Key Patterns for AI Bug Fixing
Check which state management system the app uses. Backoffice uses Vuex (mutations + actions), while Kiosk and Online Ordering use Pinia (direct state mutation in actions).
Permission bugs in the backoffice often relate to CASL ability definitions. Check the permission rules loaded from the backend and the
can()checks in components.Stock discrepancies between what the customer sees and actual availability usually involve the D1 proxy layer. Check if stock reservations are being created/released correctly.
Multi-tenant context in online ordering depends on the slug being correct. If a customer sees the wrong merchant's data, check the slug resolution in the route and the
SetTenantDatabase:online-orderingmiddleware.Branding not loading usually means the BrandingProfile is missing or the Cloudflare Image ID is invalid. Check the vendor's
branding_profile_idand the tenant database.Payment flow failures span multiple systems (frontend store -> proxy -> backend -> Stripe/Viva). Trace the flow from the payment store action through to the backend webhook handler.
Capacitor-specific bugs in the backoffice only occur on mobile. Check for
Capacitor.isNativePlatform()guards and ensure native plugin calls are wrapped in try/catch.