The problem, the idea, and the naïve version that worked perfectly ,which was the problem.
For months I watched the same scene at a salon near me. The owner cutting hair with one hand, thumbing WhatsApp with the other. Three chats open, all variations of "Are you free by 4?" A paper notebook with names half-erased. And every so often, the quiet disaster: two people booked into the same chair, and the awkward shuffle of sending someone to wait at the café next door.
It's not that these businesses are behind. It's that the cost of the existing system is invisible. The notebook works — right up until it doesn't, and the failure is always silent. A no-show. A double-booking. A regular who drifts away because "I texted and nobody replied." Nobody files a bug report against a paper calendar.
I'm Nigerian, building for a market where most salons run entirely on WhatsApp Business and bank transfers. There's no Calendly-shaped tool that fits the reality here — the payment rails are different (Paystack, Monnify, raw transfers with proof-of-payment screenshots), the trust model is different, the price sensitivity is brutal. So I started SaloonBook: a multi-tenant salon booking platform in Next.js, TypeScript, Tailwind, and Postgres via Prisma.
I thought it was a CRUD app. My first booking endpoint did exactly what I told it to — validate input, look up the salon and services, sum price and duration, write a row:
_
`const totalPrice = services.reduce((sum, svc) => sum + svc.price, 0);
const totalDuration = services.reduce((sum, svc) => sum + svc.duration, 0);
const booking = await prisma.booking.create({
data: {
salonId,
serviceId: serviceIds[0],
bookingDate: new Date(bookingDate),
startTime,
endTime: calculateEndTime(startTime, totalDuration),
status: BookingStatus.PENDING,
paymentStatus: PaymentStatus.PENDING,
totalPrice,
...(staffId && { staffId }),
},
});
It computed the end time correctly:
function calculateEndTime(startTime: string, duration: number): string {
const [hours, minutes] = startTime.split(":").map(Number);
const totalMinutes = hours * 60 + minutes + duration;
const endHours = Math.floor(totalMinutes / 60) % 24;
const endMinutes = totalMinutes % 60;
return `${String(endHours).padStart(2, "0")}:${String(endMinutes).padStart(2, "0")}`;
}`
_
It worked. I booked a 45-minute haircut at 2:00 PM. Then I booked another at 2:15 PM.
The system cheerfully accepted both.
And that's where I realized I hadn't built a booking system at all. I'd built a very polite way to record collisions. Next episode: why "do these two bookings overlap?" is the wrong question.