Skip to content
ME
All work
Hospitality · Ops2026

Alpenglow Retreat

Booking site and staff operations dashboard on one database

Role: Full-stack: schema, RLS, RPCs and UI

Alpenglow Retreat public site hero over a lake at dusk
Postgres functions
11
cabins, publicly readable
15
rows anon can read from bookings
0
lines across 96 source files
~11.7k

Overview

A public marketing website and a staff operations dashboard for a fifteen-cabin retreat, sharing one Postgres database, one design language and one deploy.

The public site carries a full-bleed hero, the estate's story, fifteen rooms with client-side filtering and sorting, a season guide, and an enquiry form whose limits are read live from the hotel's own settings row: change the minimum stay in the dashboard and the public form changes with it.

The dashboard runs the business: a triage inbox for enquiries, server-paginated bookings with every filter in the URL, check-in and check-out, cabins with photo upload, team management and settings. Four Postgres RPCs do the dashboard aggregation, so the client does no maths.

The screens

15Cabin listing with filtering by guests, nights and features

Captured from the running system. Records shown are demonstration data.

The interesting part

The browser only ever holds an anon key.

Almost no authorization lives in the React. Row-level security, column grants and database functions decide what anyone can read or write. Probed live against the REST API with the anon key, an anonymous visitor sees 15 published cabins and one settings row, and zero rows in bookings, guests, staff and reservation requests. The enquiry insert deliberately does not chain `.select()`, because anon has insert-but-not-read on that table and asking for the row back would be rejected. Converting an enquiry into a booking is a single transaction inside `hotel_convert_request`.

What it does

  • Public site: home, rooms index with six filter chips and four sorts, room detail, the estate, contact
  • Enquiry pipeline from anonymous insert through staff triage to a real booking
  • Dashboard KPIs over 7, 30 or 90 days with a gap-filled sales series and stay-length donut
  • Server-paginated bookings with whitelisted sorts and debounced guest search, all URL-driven
  • Check-in with breakfast pricing and an explicit payment attestation
  • Cabin CRUD with photo upload that rolls back if the row insert fails
  • Team management behind an edge function holding the service role
  • Three-way light / dark / system theming that stays live with the OS
  • Build-time sitemap generated from the live cabins table
  • WCAG 2.1 AA: 12 axe violation groups across 173 elements, taken to zero

Built with

Framework
  • React
  • Vite
  • React Router
UI
  • Styled Components
  • Recharts
  • React Toastify
Data
  • Supabase
  • PostgreSQL
  • TanStack Query
Forms
  • React Hook Form
Backend
  • Row Level Security
  • 11 Postgres functions
  • Edge Functions
  • Storage
Tooling
  • Image pipeline (sharp)
  • Sitemap generator

Next project

Moonlight Store

E-commerce client over a REST API