Skip to content
NACRE

Case study

A clinic website
with a real booking
system
behind it.

NACRE is a fictional cosmetic dentistry practice, built end to end to demonstrate what a production clinic system looks like: premium front end, database-enforced scheduling, staff dashboard, and integration architecture for Google Calendar and WhatsApp.

What is real, and what is simulated

Fully working

  • The booking flow, end to end, with a unique reference per booking
  • Availability computed from clinician hours, breaks, durations and lead time
  • Double-booking protection enforced in the data layer, not in the browser
  • Patient view / reschedule / cancel via a signed, unguessable link
  • The admin dashboard, its filters, search and every status action
  • Server-side validation, rate limiting and admin session protection

Simulated in this deployment

  • Google Calendar — the full call sequence runs against a simulated provider. No event exists on any real calendar.
  • WhatsApp Cloud API — messages are composed exactly as production would send them, then logged. Nothing is transmitted.
  • Storage — an in-memory driver seeded with a realistic diary. Add DATABASE_URL and the PostgreSQL driver takes over.
  • Every simulated call is written to the integration log in the dashboard, labelled as simulated.

This distinction is deliberate. A demonstration that claims to have sent a message it did not send is worth nothing to a client evaluating whether the architecture is sound. Adding credentials and setting DEMO_MODE=false switches both channels to live without touching application code.

01

The challenge

Most clinic websites are brochures with a contact form bolted on. The receptionist retypes every enquiry into a paper diary, double bookings happen, and no-shows are chased by hand. The brief here was the opposite: build the thing a clinic would actually run on, and make it look like a brand worth trusting with someone’s face.

That means two problems, not one. A premium editorial front end that does not look like a template, and a booking system whose guarantees hold up when two people click the last slot at the same moment.

02

The solution

A single Next.js application covering three audiences: patients browsing, patients booking and managing, and clinic staff running the diary. One codebase, one data model, one set of business rules — availability is computed by exactly one module, and the public site, the patient management page and the staff dashboard all read from it.

The integration layer is written as interfaces with two implementations each: live and simulated. Switching a deployment from a demo into production is a change of environment variables, not a rewrite.

03

Design direction

The brand is NACRE — mother-of-pearl. It was chosen because nacre is exactly what a good ceramist imitates when layering a veneer: translucency built in depth rather than a flat white surface. The identity follows from that idea rather than being decoration applied on top of it.

Porcelain ground, ink text, one brass accent and one deep jade. A variable serif (Fraunces) for display, Inter for interface. Numbered sections, hairline rules, generous white space, and a single motion primitive — one entrance, one easing curve, one distance — reused everywhere so the page feels composed rather than animated.

04

The booking experience

Seven steps: treatment, clinician, date, time, details, review, confirmation. Each step only shows what is genuinely possible — the clinician list is filtered by who performs that treatment, the calendar greys out days with no slot long enough, and the time grid is generated from the clinician’s working hours minus their breaks, minus existing appointments, minus the minimum lead time.

Changing an earlier answer clears everything downstream rather than silently keeping a slot that no longer applies. The client is a convenience layer: the server recomputes availability from the same engine before it writes anything.

Preventing double bookings

Two layers. In PostgreSQL, an exclusion constraint using btree_gist rejects any row whose (doctor, time range) overlaps an existing pending, confirmed or completed appointment — enforced by the database, not the application. In front of it, a transaction-scoped advisory lock keyed on doctor and day makes competing requests queue instead of race. The demo driver serialises writes through a single promise chain and re-checks inside the critical section; that guarantee holds for one instance, which is the honest limit of a zero-configuration demo.

05

The admin dashboard

A working diary rather than a chart gallery. Views for today, upcoming, awaiting confirmation, past and everything; search across name, phone, email and reference; filters by status and clinician; day-by-day navigation. Each row expands to full patient and booking detail with the actions a receptionist actually performs: confirm, reschedule, mark completed, mark no-show, cancel.

Four figures sit at the top, each of which changes a decision: how full today is, the week’s load in chair hours, what is unconfirmed, and the month’s cancellation rate. Popular treatments and per-clinician load are shown as plain bars. Nothing is graphed for the sake of it.

06

Google Calendar architecture

A CalendarProvider interface with four operations: create, update, delete and listBusy. The live implementation authenticates as a service account using the OAuth2 JWT-bearer flow, signing an RS256 assertion with node:crypto rather than pulling the whole googleapis client into a serverless bundle. Tokens are cached until expiry.

Creating a booking creates an event and stores its calendar_event_id on the booking row. Rescheduling patches that event, and recreates it if it was deleted upstream. Cancelling removes it. Before slots are shown, freeBusy is queried so time blocked directly in the calendar disappears from public availability. Every call is wrapped: a calendar failure downgrades the booking’s sync state and is logged — it never loses the appointment.

07

WhatsApp integration

A MessagingProvider interface with one operation and four message kinds: confirmation, reminder, reschedule and cancellation. Because business-initiated messages outside the 24-hour service window must use an approved template, the live implementation always sends a template — the name comes from configuration and the ordered body parameters come from the same builder that renders the demo preview.

Every message contains the patient name, treatment, clinician, date, time and booking reference. Credentials live in environment variables read only on the server; no token, app secret or API key is ever exposed to the browser bundle.

08

Security

Server-side validation with Zod on every endpoint, using the same schema the client uses for its courtesy checks. Patient management links are stateless HMAC tokens — a booking reference, which is read out over the phone, cannot open one; only the hash of the signature is stored, so a database dump does not hand out working links.

Admin routes are gated by middleware and re-verified server-side with a signed, HttpOnly session cookie. Rate limiting on booking creation, appointment management and admin login. A honeypot field on the booking form. Security headers set at the framework level, and no-store caching on every authenticated surface.

09

Responsive design

Mobile is designed, not shrunk. The progress rail becomes a compact bar with a progress line; the calendar keeps full-size touch targets; before/after runs on pointer events so mouse, pen and touch share one path, with vertical scrolling preserved through touch-action; the dashboard collapses its clinician column and moves detail into an expanding panel.

The 3D object never loads on phones at all — narrow viewports, reduced-motion users, low-core devices and data-saver connections get a hand-drawn SVG fallback that is the design on those devices, not a placeholder.

10

Technology stack

Framework

  • Next.js 16 (App Router)
  • React 19
  • TypeScript (strict)

Styling & motion

  • Tailwind CSS v4
  • Motion (Framer Motion)
  • Custom design tokens

3D

  • three.js
  • Custom GLSL iridescence shader
  • Lazy, guarded, disposable

Data

  • PostgreSQL (Supabase / Neon)
  • node-postgres
  • In-memory demo driver

Validation

  • Zod schemas
  • Shared client/server rules
  • HMAC tokens (node:crypto)

Integrations

  • Google Calendar API v3
  • Meta WhatsApp Cloud API
  • Provider interfaces

12

Routes

public, patient, admin and API

4

Integration providers

two live, two simulated

4

External runtime deps

next, react, three, motion

Going from demonstration
to production.

  1. 01

    Provision PostgreSQL (Supabase or Neon) and apply db/schema.sql. Set DATABASE_URL.

  2. 02

    Create a Google Cloud service account, enable the Calendar API, and share the clinic calendar with the service account address. Set the three GOOGLE_ vars.

  3. 03

    Create a Meta app with WhatsApp, register the business number, submit the four message templates for approval, and set the WHATSAPP_ vars.

  4. 04

    Set a strong BOOKING_TOKEN_SECRET and ADMIN_PASSWORD, then set DEMO_MODE=false.

  5. 05

    Deploy to Vercel. Nothing in the application code changes.

Full instructions are in SETUP.md in the repository. Back to the site.