Overview
A local café needed to move from paper tickets to a digital ordering pipeline. KAFE-LIYA Connect is a full-stack application that connects three roles in a single real-time system: customers browse and order from a public storefront, staff manage incoming orders on a live kanban board, and admins monitor analytics and export sales data.
Key Features
- Public storefront — Menu browsing, cart management, and checkout with order confirmation
- Real-time order tracking — Customers see their order status update without refreshing
- Staff kanban board — Orders flow through four stages: Received → Preparing → Ready → Done
- Admin analytics dashboard — Revenue charts, order volume, and CSV export
- Role-based access control — Three distinct roles (admin, staff, customer) with separate views
- Row-level security — Customers can only read their own orders, enforced at the database layer
Architecture
The frontend is React 18 + Vite with shadcn/ui components and TanStack React Query for server state. Supabase provides the entire backend: PostgreSQL for storage, Auth for session management, Row-Level Security for data isolation, and Realtime channels for push updates.
// Real-time order subscription for staff board
const channel = supabase
.channel('orders')
.on(
'postgres_changes',
{ event: '*', schema: 'public', table: 'orders' },
(payload) => {
queryClient.invalidateQueries({ queryKey: ['orders'] });
}
)
.subscribe();Playwright handles end-to-end testing across all three authentication roles, covering the full order lifecycle from placement to completion.
Technical Challenges
1. Real-time state sync across multiple staff devices without polling
Multiple staff members could be updating the same order simultaneously. Polling would cause stale reads and conflict. The solution was Supabase Realtime channels: every mutation invalidates the shared React Query cache, so all connected clients immediately reflect the latest state. Optimistic UI updates keep the experience responsive while the network roundtrip completes.
2. Row-level security ensuring customers only see their own orders
Without RLS, a motivated customer could query all orders in the database. Supabase RLS policies enforce ownership at the Postgres level — no application-layer guard can be bypassed. The policy auth.uid() = user_id on the orders table means the database rejects unauthorized reads before they reach the API.
What I Learned
Working with Supabase RLS taught me to treat the database as a security boundary, not just a storage layer. Business rules encoded in policies are harder to accidentally remove than application-level guards. I also developed a Playwright testing pattern for multi-role apps: separate browser contexts per role, shared fixtures for common flows, and assertions that verify isolation (staff cannot see customer-only views, and vice versa).