WORK INDEX
EMAIL ME
OWN PRODUCT  ·  IN PRODUCTION  ·  2026

Enquiries, quotesand bookings.

Follow each enquiry from first contact to a quote and booking, with a visible record of reminders.

FOR TATTOO STUDIOS MANAGING ENQUIRIES AND BOOKINGS

An independent product. My role: design, development and deployment.

View the internal tool package · from $5,000 →
PRODUCT
Stencil
ROLE
Design, build & launch
TENANCY
Row level security
STACK
Next.js · Supabase
DATA
Postgres
Stencil request pipeline
01  THE PROBLEM

A tattoo studio is several businesses sharing a calendar.

Artists take their own enquiries, quote their own work, keep their own clients and their own income, and share a studio, a diary and a front desk. Software that treats the studio as one account gets the calendar right and the money wrong. Software that treats each artist as an island cannot show the studio a week.

That makes permissions part of the workflow: artists need access to their work, while the studio needs an overview. The database policies enforce those access boundaries.

The constraint I set: no query written in the app should be able to return another studio's row, even if I write it badly.
02  ARCHITECTURE

No API tier, on purpose.

A Next.js app talking to Postgres through Supabase, with no service of my own in between. That only works because the authorisation lives in the database: without row level security this shape would be one forgotten filter away from a leak, and I would have had to build the tier back. Keeping the permission rules in Postgres lets this application stay focused on the studio's workflow.

FIG. 02.1 / SYSTEM
CLIENT
next.js
SUPABASE
typed client
RLS
35 policies
POSTGRES
source of truth
AUTHORISATION IS A POLICY ON THE TABLE, NOT A CLAUSE IN A QUERY
13 TABLES, ALL WITH RLS ON, 35 POLICIES BETWEEN THEM
03  DATA MODEL

Thirteen tables, and the database says no.

Every table carries its own policies, its own checks and its own foreign keys with the deletion behaviour spelled out. A booking outlives the enquiry it came from; a client outlives the payment that referenced them.

inquiries
Seven states. The funnel is the product, so the states earn their own type rather than living as booleans.
bookings
Its own five-state life, linked to the enquiry that produced it but surviving that enquiry being deleted.
clients
Deduplicated on a generated, digits-only copy of the phone number, indexed per artist.
payments
Integer cents, checked positive, with the method recorded as cash, transfer, card or other. Money is recorded here, not processed.
reminders
One row per intended message with a scheduled time and a pending, sent or failed status, so a message that never went is visible rather than assumed.
04  STACK MANIFEST
CLIENT
Next.js · React · Tailwind · shadcn
FORMS
react-hook-form · Zod
DATA
Postgres · Supabase · RLS
SERVICES
Resend · Vercel · next-intl
Need a booking or operations tool?