WingZone is a multimedia, multi-platform F&B group-ordering ecosystem built as a university project (Chapter 5 Multimedia Project). It consists of a native Android customer app, a React-based admin/kitchen web dashboard, and a Firebase serverless backend with ToyyibPay payment integration.
- Project Objective & Problem Statement
- System Architecture
- Tech Stack & Frameworks
- Authentication & Security
- Firestore Database Schema
- Key Features & Workflows
- Firebase Cloud Functions
- Payment Gateway — ToyyibPay
- Admin Dashboard Pages
- Android App Screens
- Current Known Problems
- Future Enhancements
- Documentation Index
Build a synchronous F&B group-ordering system that solves the chaotic, error-prone experience of placing food orders as a group at a dine-in restaurant. The system must:
- Allow a group host to open a Lobby and share a join code / QR code / deep link with friends.
- Let each member browse the menu independently and add items to their own sub-order in real time.
- Aggregate all sub-orders into a single master kitchen ticket with a printed packaging sticker per seat.
- Support both cash and online banking (FPX via ToyyibPay) payment flows.
- Give kitchen and admin staff a real-time web dashboard to manage orders, inventory, and settings.
Conventional dine-in ordering forces one person to collect everyone's choices verbally, increasing errors and wait times. Existing F&B apps (GrabFood, Foodpanda) target delivery only and do not support synchronised dine-in group ordering with per-seat identification or split-bill workflows. WingZone bridges this gap with a purpose-built Lobby system, thermal label output for each seat, and a real-time kitchen dashboard.
┌──────────────────────────────────────────────────────────────┐
│ CLIENT LAYER │
│ │
│ ┌─────────────────┐ ┌──────────────────────────┐ │
│ │ Android App │ │ Admin / Kitchen Web │ │
│ │ (Kotlin / │ │ Dashboard (React + │ │
│ │ Jetpack │ │ TypeScript / Vite) │ │
│ │ Compose) │ │ │ │
│ └────────┬────────┘ └────────────┬─────────────┘ │
└────────────┼───────────────────────────────┼───────────────┘
│ Firebase SDK (real-time) │ Firebase SDK
▼ ▼
┌──────────────────────────────────────────────────────────────┐
│ FIREBASE BACKEND LAYER │
│ │
│ ┌─────────────┐ ┌──────────────┐ ┌────────────────────┐ │
│ │ Firestore │ │ Firebase │ │ Firebase Storage │ │
│ │ (NoSQL DB) │ │ Auth │ │ (Images/Sounds/ │ │
│ │ │ │ (Email/Pwd) │ │ Certs) │ │
│ └─────────────┘ └──────────────┘ └────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Cloud Functions (Node.js / TypeScript) │ │
│ │ - autoProgressIndividualOrders │ │
│ │ - onOrderConfirmed (30s delay → preparing) │ │
│ │ - autoDeliverReadyOrders (5 min → delivered) │ │
│ │ - autoProgressGroupOrders │ │
│ │ - onGroupOrderConfirmed │ │
│ │ - autoDeliverReadyGroupOrders │ │
│ │ - fixStuckOrders (HTTP — admin utility) │ │
│ │ - joinLobby (HTTP — QR / link redirect) │ │
│ │ - createToyyibPayBill (payment initiation) │ │
│ │ - paymentCallback (ToyyibPay webhook) │ │
│ └─────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ PHYSICAL / HARDWARE LAYER │
│ │
│ Thermal Label Printer (QZ Tray + jsrsasign) │
│ Sticker Output: 40 mm × 30 mm — Seat ID + QR + Name │
└──────────────────────────────────────────────────────────────┘
Deep-link scheme used throughout: wz://join?code=<LOBBY> and wz://payment/success?order_id=<ID>.
| Concern | Library / Tool |
|---|---|
| Language | Kotlin |
| UI | Jetpack Compose + Material 3 |
| Architecture | Single-Activity, MVVM, NavHost (Navigation Compose) |
| Concurrency | Kotlin Coroutines + Flow |
| Image loading | Coil (coil-compose, coil-svg) |
| QR generation | ZXing (com.google.zxing:core 3.5.3) |
| QR scanning | ML Kit Barcode Scanning + CameraX |
| Background tasks | WorkManager |
| Firebase | BOM 33.7.0 — Auth, Firestore, Storage, Messaging, Analytics |
| Animations | Lottie (planned) |
| Min / Target SDK | 27 / 36 |
| Application ID | wingzone.zenith |
| Concern | Library / Tool |
|---|---|
| Language | TypeScript |
| Framework | React 18 |
| Build tool | Vite 7.2.6 |
| UI components | Reactstrap (Bootstrap 5) |
| Styling | SCSS Modules + Bootstrap Icons |
| Animations | GSAP (@gsap/react) |
| Image cropping | react-easy-crop, react-image-crop |
| Alerts | SweetAlert2 |
| Charts | (Bootstrap Icon-based stat cards — full Recharts planned) |
| Firebase | Firestore, Auth, Storage (JS SDK v9) |
| Thermal print | QZ Tray + jsrsasign |
| Concern | Library / Tool |
|---|---|
| Runtime | Node.js (TypeScript) |
| Functions SDK | firebase-functions v2 |
| Admin SDK | firebase-admin |
| File parsing | busboy (multipart) |
| Payment | ToyyibPay REST API (Malaysian FPX gateway) |
| Max instances | 10 (global) |
- Android app — Firebase Email/Password (
FirebaseAuth). On first sign-up, a/users/{uid}document is created in Firestore withrole: "user". - Admin dashboard — Same Firebase Email/Password. After sign-in the
AuthServicereads the/users/{uid}document and rejects access unlessrole == "admin"orrole == "kitchen". - Session persistence — Dashboard stores the user object in
localStorage; Android restores theFirebaseAuthcurrent user on app restart.
| Role | Access |
|---|---|
user (customer) |
Own orders, own profile, lobby actions |
kitchen |
Admin dashboard read + order status updates only |
admin |
Full admin dashboard access + Firestore writes |
- Custom Auth claim
request.auth.token.admin == trueis checked first (zero extra reads); falls back tousers/{uid}.role == "admin". - Users can only read/write their own document. They cannot escalate their own
role. menuItems— public read, admin-only write.orders/groupOrders— authenticated create, admin update (status), owner read.appSettings— admin read/write only.homeBanners— public read, admin write.notifications— admin read/write.reviews— authenticated create, admin moderation update.
AndroidManifest.xml declares two intent-filter entries with autoVerify="true":
wz://join— lobby QR/link joinwz://payment— ToyyibPay payment callback
/menuItems/{id}
name, description, category, price, imageUrl,
isAvailable, requiresCustomization, displayOrder,
flavors[], sizes[], addOns[],
customizationOptions: {
requiresFlavor, requiresBeverage, requiresDippingSauce,
requiresBoneType, availableBoneTypes[],
allowFriesExchange, friesExchanges[]
},
kitchenIngredients: { ingredients[] }
/orders/{id} ← individual orders
userId, userName, items[], total,
status: pending|confirmed|preparing|ready|delivered|cancelled,
paymentMethod: cash|online,
paymentStatus: unpaid|pending|paid,
orderType, location, tableId,
createdAt, confirmedAt, preparingAt, deliveredAt
/groupOrders/{id} ← lobby / group orders
code, hostId, status,
members[{ userId, name, items[], subtotal }],
paymentSplit: { type, perMember },
createdAt, confirmedAt, preparingAt, deliveredAt
/users/{uid}
email, displayName, role,
wzBalance, wzPoints, createdAt
/notifications/{id}
type: order|system|info,
title, message, orderId, orderType,
orderTotal, customerName, groupOrderCode,
createdAt, read
/appSettings/general
taxRate (decimal), deliveryFee, minimumOrderAmount, updatedAt
/appSettings/adminPreferences
darkMode, notificationSound, autoRefreshOrders,
autoPrintReceipts, printerName, useThermalPrinter
/appSettings/notificationSound
enabled, soundType, customSounds[], volume
/homeBanners/{id}
title, subtitle, description, imageUrl,
backgroundColor, accentColor, order, enabled
/reviews/{id}
orderId, userId, userName, rating, comment,
menuItemIds[], createdAt, isEnabled,
moderationStatus: approved|pending|rejected
/availability/current
flavors[], beverages[], sides[], dippingSauces[], boneTypes[]
Customer browses menu
→ Selects item → Customisation dialog (flavor / bone type / fries exchange / dipping sauce)
→ Cart → Order Summary (subtotal + tax + delivery)
→ Checkout dialog: pick payment method
┌─ Cash → Order doc created immediately (paymentStatus: unpaid)
│ Admin confirms payment manually
└─ Online Banking → ToyyibPay bill created via Cloud Function
→ WebView opened with payment URL
→ On success: wz://payment/success?order_id=xxx
→ Order doc created (paymentStatus: paid)
→ Cloud Functions auto-progress: confirmed → preparing (30s) → [admin marks ready] → delivered (5 min)
→ Order Tracking Screen shows real-time status
Host creates Lobby → gets 6-char code + QR code + shareable link
→ Friends scan QR / tap link (wz://join?code=xxx) / enter code manually
→ Each member sees shared lobby, browses menu independently
→ Host locks ordering → group order submitted
→ Cloud Functions auto-progress: confirmed → preparing (30s) → [admin marks ready] → delivered (5 min)
→ Admin kitchen dashboard shows aggregated kitchen ingredients list
→ Thermal sticker printed per seat (Seat ID + QR + member name)
- New order arrives → push/web notification fires (bell animation + toast + sound).
- Admin views individual or group orders tab on Orders page (grouped by date).
- Status changed via dropdown: Pending → Confirmed → Preparing → Ready → Delivered / Cancelled.
- Receipt modal or packaging sticker modal opened for printing.
- Kitchen ingredient aggregation shows raw materials needed per group order.
- Admin adds/edits/deletes menu items with image upload (crop → Firebase Storage).
- Categories: Combo Meals, Wings, Tenders, Burgers & Sandwiches, Local Favorites, Salads, Sides, Beverages.
- Flavors (15 Wing Zone official sauces), bone types, fries exchange options, dipping sauces all configurable per item.
isAvailabletoggled; mobile app listeners update in real time.
Admins toggle specific flavors, beverages, sides, dipping sauces, and bone types ON/OFF from the Availability page. State is persisted to /availability/current and reflected in the Android app's customisation dialog immediately via onSnapshot.
Admin sets tax rate (%), delivery fee, and minimum order in Settings → App Configuration. Saved to appSettings/general. Android FirebaseCartRepository has a live snapshot listener — cart totals recalculate in real time on all connected devices.
Admin uploads and crops home-screen promotional banners via Banners page (stored in homeBanners collection + Firebase Storage). Banners have title, subtitle, colour scheme, display order, and enabled toggle.
Users submit star ratings + comments after an order. Admin sees all reviews on the Reviews page with GSAP-animated cards. Actions: approve / reject / toggle visibility. moderationStatus field controls display in the app.
- Web dashboard uses
onSnapshoton/orders— new orders trigger bell animation, toast pop-up, and configurable alert sound (9 built-in + custom upload via Firebase Storage). - Sound types: Default Beep, Bell, Chime, Alert, Urgent Alert, Kitchen Bell, Alarm, Siren, Custom.
- Volume 0–100%; settings persisted to
appSettings/notificationSound. - Android app uses Firebase Cloud Messaging (FCM) for push notifications.
- Admin dashboard connects to a local QZ Tray instance over WebSocket.
- Authentication via RSA/SHA-512 certificate (
public/certs/digital-certificate.txt+private-key.pem). - Prints 40 mm × 30 mm sticker labels: Seat ID, member name, order items, QR code.
ThermalPrinterServiceis a singleton withconnect(),printLabel(),disconnect().
| Function | Trigger | Behaviour |
|---|---|---|
autoProgressIndividualOrders |
orders/{id} created |
Skips cash/unpaid; immediately sets status: confirmed for paid orders |
onOrderConfirmed |
orders/{id} updated |
Waits 30 s then moves to preparing if still confirmed |
autoDeliverReadyOrders |
orders/{id} updated |
Waits 5 min then moves to delivered if still ready |
autoProgressGroupOrders |
groupOrders/{id} created |
Immediately sets status: confirmed |
onGroupOrderConfirmed |
groupOrders/{id} updated |
Waits 30 s then moves to preparing |
autoDeliverReadyGroupOrders |
groupOrders/{id} updated |
Waits 5 min then moves to delivered |
fixStuckOrders |
HTTP GET | Utility — moves all pending paid orders to confirmed; skips cash orders |
joinLobby |
HTTP GET ?code= |
Returns HTML page that fires wz://join?code= deep link with a fallback UI |
createToyyibPayBill |
HTTP POST | Creates a ToyyibPay bill and returns the payment URL |
paymentCallback |
HTTP POST (webhook) | Receives ToyyibPay confirmation, updates order paymentStatus: paid |
ToyyibPay is a Malaysian FPX (online banking) aggregator.
CartScreen (online banking selected)
→ HTTP POST to createToyyibPayBill Cloud Function
→ Function calls ToyyibPay API → returns bill_code / payment URL
→ PendingOrderManager stores order in SharedPreferences (24 hr expiry)
→ App navigates to PaymentWebViewScreen
→ User completes FPX payment in WebView
→ ToyyibPay redirects to callback URL:
https://us-central1-wingzone-app.cloudfunctions.net/paymentCallback
→ Webhook updates Firestore
→ Deep link wz://payment/success?order_id=xxx fires
→ MainActivity creates order document, clears cart, navigates to Order Tracking
- Secret Key: stored in Cloud Function source (⚠ move to Secret Manager for production)
- Category Code:
r90repsm - Functions URL:
https://us-central1-wingzone-app.cloudfunctions.net
| Method | Flow |
|---|---|
| Cash | Order created immediately; paymentStatus: unpaid; admin manually confirms |
| Online Banking (FPX) | ToyyibPay WebView; order created only after successful callback |
| Page | Route key | Purpose |
|---|---|---|
| Login | — | Firebase Email/Password; role check (admin/kitchen only) |
| Dashboard | dashboard |
Real-time stats (today's orders, revenue, pending, active group orders); recent orders table |
| Menu | menu |
CRUD for menu items; image crop/upload; flavor/bone/fries/sauce config |
| Orders | orders |
Individual & group order tabs; status dropdown; receipt & sticker modals; kitchen ingredients view; date-grouped list |
| Availability | availability |
Toggle flavors, beverages, sides, dipping sauces, bone types on/off |
| Users | users |
Real-time list of all registered customers from Firestore |
| Banners | banners |
Home-screen banner CRUD with image crop (react-easy-crop) and Firebase Storage upload |
| Reviews | reviews |
Approve / reject / hide user reviews; GSAP-animated cards |
| Settings | settings |
Dark mode, notification sound (with custom upload), printer config, tax/delivery/min-order sync |
| Notification Sound Settings | notification-sounds |
Pick from 9 sounds, adjust volume, upload custom sound |
| Seed Menu | seed |
One-time data seeder for initial menu population |
| Screen | File | Description |
|---|---|---|
| Main / Launcher | MainActivity.kt |
Single-activity host; NavHost; deep-link handler; payment flow listeners |
| Home / Menu | MenuPage / menu composables |
Browse categories; item cards with images |
| Entree Customisation | EntreeCustomizationDialog.kt |
Flavor chips, bone type, fries exchange, dipping sauce, size; Add to Cart |
| Cart | CartScreen.kt |
Cart items, subtotal, tax, order summary, payment method picker, Checkout |
| Payment WebView | PaymentWebViewScreen.kt |
Embedded WebView for ToyyibPay; URL-monitoring callback handler |
| Order Tracking | OrderTrackingScreen.kt |
Real-time status updates for user's orders; status colour indicators |
| Group Order | GroupOrderScreen.kt |
Lobby creation/joining (QR / code / link); member list; shared cart |
| Account | AccountScreen.kt |
User profile (edit partially implemented) |
| # | Area | Problem | Status |
|---|---|---|---|
| 1 | Cloud Functions | createToyyibPayBill and paymentCallback code is ready but not yet deployed to Firebase |
Pending deploy |
| 2 | ToyyibPay credentials | Secret key is hard-coded in function source — must be moved to Firebase Secret Manager before production | Security debt |
| 3 | Thermal printer | QZ Tray certificates (digital-certificate.txt, private-key.pem) missing from public/certs/; printer will not connect without them |
Needs certs |
| 4 | Alert sounds | Built-in MP3 files (urgent-alert.mp3, kitchen-bell.mp3, alarm.mp3) not yet added to public/sounds/ |
Assets missing |
| 5 | User profile editing | AccountScreen.kt exists but edit functionality (username, password, profile photo) is not implemented |
Partial — EditProfileDialog.kt TODO |
| 6 | Split-bill engine | Group order payment splitting (per-member calculation, lock until all paid) is not implemented | Placeholder only |
| 7 | Receipt / Invoice | ReceiptModal and PackagingStickerModal exist in admin; Android PrintReceiptDialog.kt not created |
Partial |
| 8 | Group order full redesign | GroupOrderScreen.kt exists but the complete UX redesign (bottom sheets, QR share, real-time member updates) from LOBBY-UX-REDESIGN.md is not finished |
In progress |
| 9 | FCM push notifications | Firebase Cloud Messaging is declared in the Android manifest and firebase-messaging-ktx is a dependency, but server-side FCM send logic is not implemented in Cloud Functions |
Not wired |
| 10 | Availability sync race condition | If admin marks a flavor unavailable while a customer is on the customisation screen, the UI does not re-validate the already-open dialog | Minor UX gap |
| Category | Enhancement |
|---|---|
| Ordering | Delivery option with address input and live location tracking |
| Payment | Split-bill engine — lock group order until every member's payment is confirmed |
| Payment | E-wallet support (Touch 'n Go eWallet, Boost) via ToyyibPay or alternate gateway |
| Loyalty | WZ Points / WZ Balance redemption at checkout (fields exist in user model) |
| Analytics | Full revenue analytics dashboard with Recharts / Tremor — sales by category, hourly heatmap, top items |
| Notifications | Server-side FCM push to Android — new order status updates, promotions |
| Profile | Edit username, change password, upload profile photo (EditProfileDialog.kt) |
| Menu | Stock count management — low-stock alerts to admin; auto-mark unavailable at zero |
| Banners | Scheduled banner activation/expiry (start date / end date) |
| Reviews | In-app review prompt after order delivered; response from admin |
| Group Order | Lottie animations for lobby wait screen; member-ready indicators |
| Kitchen Display | Dedicated KDS (Kitchen Display System) view on a tablet — auto-refresh, sound alerts, swipe-to-complete |
| Offline Support | Firestore offline persistence for Android (already enabled for Auth); show cached menu when offline |
| Internationalisation | Bahasa Malaysia / English toggle across both platforms |
| Accessibility | Content descriptions on all Compose composables; contrast ratio audit on admin dashboard |
| CI/CD | GitHub Actions — Android APK build + Firebase App Distribution; Functions deploy on merge to main |
| File | Contents |
|---|---|
| ADMIN-ENHANCEMENTS-GUIDE.md | Custom notification sounds, direct image upload setup |
| ADMIN-SETTINGS-GUIDE.md | Tax, delivery fee, minimum order configuration |
| COMPLETE-IMPLEMENTATION-STATUS.md | Checklist of all 16 implementation issues and their state |
| FIXES-2025-01-23.md | Notification overflow, fries exchange, dine-in UX, scroll fixes |
| LOBBY-UX-REDESIGN.md | Full UX spec for Group Order / Lobby redesign (ZUS-style) |
| NOTIFICATION-SYSTEM.md | Admin dashboard real-time notification animations and Firebase structure |
| PAYMENT-GATEWAY-INTEGRATION.md | Code changes required for CartScreen → PaymentWebView split |
| PRODUCTION-READY-PLAN.md | Phased plan of critical, dashboard, and UX fixes |
| RECENT-FIXES-DEC-2025.md | December 2025 fixes — checkout button, add-to-cart visibility, bone type, admin config |
| TAX-CONFIGURATION-GUIDE.md | How tax rate flows from admin settings to mobile cart |
| TOYYIBPAY-INTEGRATION.md | ToyyibPay setup, credentials, Android config, deploy steps |
| TOYYIBPAY-STATUS.md | Current integration completion checklist |
| CartScreen-Checkout-Logic-Update.kt | Kotlin snippet for cash vs online banking checkout split |
Last updated: May 26, 2026
/notifications
- Real-time notifications
- Fields: type, title, message, createdAt, read
/orders
- Customer orders
/settings
- App configuration
/availability
- Item availability toggles
---
## 📖 How to Use This Documentation
1. **For Developers**: Start with technical implementation docs
2. **For Admins**: Check the Admin Dashboard Guide
3. **For Users**: Refer to the Android App Guide
4. **For Issues**: Check Issues Resolved document
---
## 🆘 Support
If you encounter any issues:
1. Check the relevant documentation file
2. Review the Issues Resolved document
3. Check Firebase console for data issues
4. Review browser/Android console for errors
---
## 📝 Contributing to Documentation
When adding new features:
1. Document in appropriate .md file
2. Update this README index
3. Save all .md files in /docs folder
4. Keep README.md in root for project overview
---
Last Updated: December 5, 2025