01Context and problem
Many barbershop websites only show a WhatsApp button, and booking stays manual. This project simulates a complete barbershop to show how a website can take bookings on its own, without time conflicts and without requiring customers to sign up.
02Goal
- Booking in a few steps, with no account needed.
- Prevent two customers from booking the same time with the same barber.
- A restricted area where staff can see the bookings.
- Access rules guaranteed by the database, not only by the interface.
03Solution
A React and TypeScript SPA (Vite) connected to Supabase. Services, barbers, schedules and bookings live in PostgreSQL with Row Level Security. A visitor's booking goes through an SQL function that validates the data before saving.
04My role
An individual project: interface and visual identity, database modeling, access policies, SQL functions, versioned migrations and deployment on Vercel.
05Technologies and architecture
06Key features
- Booking: service, barber (or anyone available), a day among the next opening days and a time.
- No sign-up: the customer only enters a name and WhatsApp number.
- Same-day times: times that have already passed, with a 30-minute margin, are not offered.
- Staff area: login through Supabase Auth, available only to admin profiles, listing bookings with service and barber.
- Business website: services with price and duration, barbers, gallery and location.
07Technical challenges and solutions
Two customers at the same time
Solution: a partial unique index on (barbeiro_id, data, horario) that ignores cancelled bookings. The database rejects the second booking, and the interface turns error 23505 into “This time has already been booked. Choose another one.”
Accepting visitor bookings without opening the table
Solution: visitors never insert directly into appointments. They call the create_guest_appointment function, which requires name and WhatsApp, rejects past dates and checks that the service and barber are active before saving with a pending status.
Separating public data from staff data
Solution: Row Level Security on every table. The public can only read active services and barbers; bookings are visible only to their own customer or to admins, checked by the is_admin() function. Even with the public Supabase key in the browser, the database enforces the rules.
08Demo
The demo is live and lets you try the full booking flow (in Portuguese). The barbershop, barbers and phone number are fictional.
09Lessons learned and next steps
The main lesson was to put business rules where they cannot be bypassed: in the database. The interface improves the experience, but the index, the function and the RLS policies are what guarantee there are no time conflicts or unauthorized access.
Next steps
- Validate in the database that the time belongs to the barber's schedule (
schedules) and block past times on the server, not only in the interface. - Split the main component into smaller components and add tests.
- Let staff confirm, complete or cancel bookings from the panel.
