Example app spec: a booking app

THE WHOLE IDEA WE TYPED

“A booking app for a small dog-grooming business”

Generated by SpecWise with the Balanced model (3 credits) · 3,087 words · unedited

Want one for your idea?

Describe your idea in a sentence and get a spec like this in about a minute.

Generate my spec

7-day free trial · no card needed

Pawfect Groom - Product Requirements Document

1) Summary

Pawfect Groom is a booking app for a small dog-grooming business run by one groomer from one shop. Customers pick a service, choose a free time slot, enter their dog's details and book without an account; the groomer opens a private /admin page to see the day's appointments, confirm, complete, cancel or add bookings by hand, and set opening hours. Data lives in Supabase Postgres, because a customer on one device and the groomer on another must see the same calendar.

  • Assumption: There is one groomer and one table, so at most one appointment occupies any given time; there are no staff or rooms.
  • Assumption: "Slot" means a start time on a 15-minute grid where the full service duration fits before closing and does not overlap another non-cancelled appointment, not a fixed pre-made time block.
  • Assumption: All times are the shop's local time, Europe/London; a "day" is a local calendar date and dates are stored as YYYY-MM-DD plus HH:mm strings.
  • Assumption: Customers have no accounts; a booking is identified by a 6-character reference and managed with the reference plus the phone number.
  • Assumption: A booking is accepted on request and starts as pending; it blocks its slot immediately, and the groomer confirms it.
  • Assumption: Dog size sets duration and price: small under 10 kg, medium 10-25 kg, large over 25 kg (see 4).
  • Assumption: Platform is a responsive browser app, phone-first and usable at 1280 px; it is not installable and sends no reminders.
  • Assumption: No payments are taken online; the price is shown and paid at the shop.

2) Tech Stack

  • Framework → Next.js 14 (App Router) → server routes keep the admin password and service key off the browser
  • Language → TypeScript → typed booking and slot logic
  • Database → Supabase Postgres → shared calendar for customers and groomer
  • Auth → Single admin password, checked server-side, stored in an httpOnly cookie → the only privileged user is the groomer
  • Styling → Tailwind CSS → fast mobile-first layout
  • Dates → date-fns → slot arithmetic and formatting
  • Deployment → Vercel → zero-config Next.js hosting

3) Users & Screens

RoleCanCannot
Customer (anonymous)Browse services, see free slots, book, look up and cancel own bookingSee other customers' data, change prices or hours
Groomer (admin)View all bookings, change status, add and cancel bookings, edit opening hours, block daysTake payment, message customers

Screens:

ScreenReached fromShowsEmpty / loading / errorBack / close goes to
Home /DirectShop name, service cards, Book nowLoading: skeleton cards; error: Couldn't load services. Try again.none
Book /bookBook now4-step form (6.1)see 6.1Home
Confirmation /booking/[reference]Successful booking, lookupBooking summary and statusUnknown reference: We couldn't find that booking.Home
Find booking /manageHome footer linkReference + phone formsee 6.3Home
Admin login /admin/login/admin when signed outPassword fieldWrong password.Home
Admin day view /adminLoginDate picker, day timeline, listsee 6.4Login
Admin hours /admin/hoursAdmin header linkWeekly hours, closed datessee 6.5Admin day view

Navigation: customer pages have a header with the shop name linking Home; admin pages have a header with Today, Hours, Sign out.

4) Data Model & Logic

Service (seeded, not editable in V1): id, name, base_minutes (int), price_small, price_medium, price_large (int pence).

ServiceMinutes (small/medium/large)Price (small/medium/large)
Bath & Brush45 / 60 / 75£30 / £38 / £48
Full Groom75 / 90 / 120£48 / £60 / £78
Nail Trim15 / 15 / 15£10 / £10 / £10

Booking: id (uuid), reference (6 chars from ABCDEFGHJKLMNPQRSTUVWXYZ23456789, unique), service_id, size (small|medium|large), date (YYYY-MM-DD), start (HH:mm), end (HH:mm, computed), price (pence, copied at booking), dog_name (1-30 chars), breed (0-40 chars), owner_name (1-60), phone (7-15 digits, spaces and + allowed), notes (0-300), status, created_at.

OpeningHours: weekday (0 = Sunday … 6), open, close (HH:mm), is_closed (bool). Default: Mon-Fri 09:00-17:00, Sat 09:00-13:00, Sun closed.

ClosedDate: date, reason (0-60 chars).

Slot logic:

duration = service.minutes[size]
for t in range(open, close - duration, step 15 min):
  slot free if no booking b (status != cancelled) on date with t < b.end and t+duration > b.start
  and date not in ClosedDate and not weekday.is_closed
  and (date > today or t >= now + 60 min)

Customers can book 1 to 60 days ahead (today counts only if a slot is ≥ 60 min away).

Worked example: Tuesday 14 May, hours 09:00-17:00. Existing: Rex (Full Groom, large) 09:00-11:00; Bella (Nail Trim) 11:00-11:15; Max (Bath & Brush, small) cancelled 13:00-13:45. A customer picks Full Groom, medium (90 min). Free starts: 11:15, 11:30 … up to 15:30 (ends 17:00). 09:00-10:45 are blocked by Rex; 11:00 overlaps Bella. Max's slot is free again because it is cancelled. Booking 11:15 gives end = 12:45, price = £60. A second customer now sees the earliest Nail Trim at 12:45. Edge: Full Groom large (120 min) on Saturday (09:00-13:00) has last start 11:00.

Status transitions:

FromEventToSide effect
(new)Customer submitspendingSlot blocked
pendingGroomer taps Confirmconfirmednone
pending/confirmedCustomer or groomer cancelscancelledSlot freed
confirmedGroomer taps Completecompletedterminal
pending/confirmedGroomer taps No-showno_showterminal, slot stays in history
cancellednonenoneterminal

Edits and edge cases: Slot availability is re-checked in a database transaction on submit; if taken meanwhile, the booking fails (6.1). Customers cancel only up to 2 hours before start. Changing opening hours never moves existing bookings; the admin day view flags bookings outside current hours with Outside hours. Two admins confirming at once is harmless (idempotent). Duplicate bookings (same phone, date, start) are rejected.

5) External Services

Supabase

Used for: Postgres storage of services, bookings, hours, closed dates. Calls: supabase-js from('bookings').select/insert/update, and an RPC create_booking that runs the overlap check and insert in one transaction. Fields read: all booking fields (admin), date,start,end,status only (public slot query via server route). Limits: free tier 500 MB; 1 request per slot query is enough at this scale. Failure: server routes return 503; UI shows Something went wrong. Please try again in a minute. Keys: SUPABASE_SERVICE_ROLE_KEY server only; NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_ANON_KEY browser-safe (anon key has no table access; all reads and writes go through server routes).

6) Core Features

6.1 Book an Appointment

User Flow:

  1. Home: tap Book now. /book opens at step 1 of 4, with a progress label Step 1 of 4.
  2. Step 1 Choose a service: tap a card (name, from £30); then pick Small (under 10 kg), Medium (10-25 kg) or Large (over 25 kg). Next stays disabled until both are chosen. The summary line shows duration and price, e.g. 90 min · £60.
  3. Step 2 Pick a day and time: a horizontal strip of the next 14 days (closed days greyed out and not tappable); tapping a day loads free slot chips (09:00, 09:15 …). Tap one to select it.
  4. Step 3 About your dog: fields Dog's name, Breed (optional), Your name, Phone number, Notes for the groomer (optional).
  5. Step 4 Review: summary of all choices. Tap Request booking. The server runs create_booking.
  6. On success go to /booking/[reference] showing the status and the reference. Back on any step keeps entered data.

UI Copy & Colors:

  • Page title: Book a grooming, text #0F172A
  • Selected card/chip: background #0F766E, text #FFFFFF
  • Primary button: Next / Request booking, #0F766E on #FFFFFF
  • Confirmation heading: Request received! with Your reference is {REF}. We'll confirm shortly.

Error & Empty States:

  • No free slot that day → No free times on this day. Try another day. Recovery: pick another day.
  • Slot taken at submit → Sorry, that time was just booked. Please pick another. Recovery: returns to step 2 with refreshed slots, other data kept.
  • Invalid phone → Enter a phone number with 7-15 digits.
  • Empty required field → This field is required.
  • Server failure → Something went wrong. Please try again in a minute. Recovery: Request booking stays enabled.

Acceptance Checks:

  • ✓ Full Groom, medium, Tue 14 May with Rex 09:00-11:00 and Bella 11:00-11:15 existing → first chip is 11:15, last chip 15:30.
  • ✓ Nail Trim selected on Saturday (09:00-13:00) → last chip 12:45.
  • ✓ Choose Bath & Brush, small → summary shows 45 min · £30.
  • ✓ Two customers submit 11:15 simultaneously → exactly one gets Request received!, the other sees Sorry, that time was just booked. Please pick another.

6.2 Booking Confirmation Page

User Flow:

  1. /booking/[reference] shows reference, status badge, service, dog name, date as EEEE d MMMM, time HH:mm - HH:mm, price.
  2. Shows the shop address line 12 High Street, Anytown and Pay at the shop.
  3. Button Cancel booking appears while status is pending or confirmed and start is more than 2 h away.
  4. Tapping Cancel booking opens a dialog Cancel this booking? with Keep it and Yes, cancel. Yes, cancel sets cancelled.

UI Copy & Colors:

  • Badges: Pending #B45309 on #FEF3C7; Confirmed #166534 on #DCFCE7; Cancelled #991B1B on #FEE2E2; Completed #334155 on #E2E8F0; No-show #334155 on #E2E8F0.

Error & Empty States:

  • Unknown reference → We couldn't find that booking. Recovery: link Find my booking.
  • Cancel inside 2 h → button hidden; text Too late to cancel online. Please call the shop.
  • Already cancelled → page shows the Cancelled badge and no button.

Acceptance Checks:

  • ✓ Booking Fri 17 May 10:00 viewed at 07:30 same day → Cancel booking visible; viewed at 08:30 → hidden with the call text.
  • ✓ After Yes, cancel, the badge reads Cancelled and the 10:00 slot appears in the slot list.
  • ✓ Time shown as 10:00 - 11:30 for a 90-min service starting 10:00.

6.3 Find My Booking

User Flow:

  1. /manage shows fields Booking reference and Phone number, and button Find booking.
  2. Reference is uppercased as typed. Phone is compared on digits only.
  3. A match navigates to /booking/[reference]; the phone digits are held in sessionStorage for that visit so the page may show the owner's details.

UI Copy & Colors:

  • Heading Find your booking; button #0F766E on #FFFFFF.

Error & Empty States:

  • No match → No booking matches that reference and phone number. Recovery: retype.
  • 5 failed attempts in 10 min from one IP → Too many tries. Please wait 10 minutes.

Acceptance Checks:

  • ✓ Reference k7m2xq typed lowercase with phone 07700 900123 for a booking stored +44 7700 900123: matches on digits 7700900123 suffix and opens the page.
  • ✓ Right reference, wrong phone → No booking matches that reference and phone number.
  • ✓ 6th wrong try within 10 min → Too many tries. Please wait 10 minutes.

6.4 Admin Day View

User Flow:

  1. /admin/login: enter the password, tap Sign in. A correct password sets an httpOnly cookie valid 12 h.
  2. /admin opens on today. A date control has ‹, a date picker and ›, plus Today.
  3. Below, a list of the day's non-cancelled bookings in time order; each row shows HH:mm - HH:mm, dog name, breed, service, size, owner name, a tap-to-call phone link, notes and a status badge.
  4. Row actions by status: pending → Confirm, Cancel; confirmed → Complete, No-show, Cancel. Cancel asks Cancel this booking? with Keep it / Yes, cancel.
  5. Add booking opens the same 4-step form as 6.1 with the notes Booked by phone prefilled and status set directly to confirmed; admin can book inside 60 min and on closed days after a warning.
  6. A toggle Show cancelled reveals cancelled rows struck through.

UI Copy & Colors:

  • Row Confirm button #0F766E on #FFFFFF; Cancel text #DC2626; row Outside hours tag #B45309 on #FEF3C7.

Error & Empty States:

  • No bookings → Nothing booked for this day.
  • Wrong password → Wrong password. Recovery: retry; 5 fails → Too many tries. Please wait 10 minutes.
  • Cookie expired → redirect to login with Please sign in again.
  • Action fails → toast Couldn't update the booking. Try again.

Acceptance Checks:

  • ✓ A pending booking tapped Confirm shows Confirmed badge and buttons change to Complete, No-show, Cancel.
  • ✓ Tapping Complete on a booking removes its action buttons (terminal).
  • ✓ Admin adds Nail Trim small at 12:00 on a day closed by ClosedDate after warning → row appears with Confirmed.
  • ✓ After a customer cancels 13:00, toggling Show cancelled shows that row struck through.

6.5 Opening Hours & Closed Days

User Flow:

  1. /admin/hours lists Monday to Sunday; each row has an Open toggle and two time inputs Opens and Closes (15-min steps).
  2. Save hours stores all seven rows.
  3. Below, Closed days: a date input, a Reason field and Add closed day; existing entries list with Remove.
  4. Saving shows Hours saved.

UI Copy & Colors:

  • Success toast Hours saved. #166534 on #DCFCE7.

Error & Empty States:

  • Closes not after Opens → Closing time must be after opening time. Save is blocked.
  • Closed day already has bookings → {n} booking(s) already exist on this date. They are kept; contact the customers. Day is still closed to new bookings.
  • No closed days → No closed days set.

Acceptance Checks:

  • ✓ Setting Wednesday to 10:00-15:00 and saving → a customer's Wednesday chips run from 10:00 to the last start that fits before 15:00.
  • ✓ Adding Monday 27 May as closed → that day is greyed out in the customer date strip.
  • ✓ Opens 17:00, Closes 09:00 → Closing time must be after opening time.

7) UI/UX

Palette:

TokenHexUsed for
Primary#0F766EButtons, selected chips
Primary dark#115E59Pressed state
Accent#F59E0BHighlights, logo paw
Success#16A34AConfirmed, saved
Danger#DC2626Cancel, errors
Text#0F172ABody
Muted#64748BSecondary text
Surface#F8FAFCPage background

Typography: Inter, 16 px base; headings 24 px bold; step titles 20 px; reference code 28 px monospace; captions 13 px.

Layout: single column, mobile first, max width 480 px centred for customer pages; admin day view widens to 960 px with a two-column timeline above 1024 px. Sticky bottom bar holds Back / Next on the form.

Motion:

InteractionDurationEasing
Step change slide200 msease-out
Slot chip select120 msease-in-out
Dialog open150 msease-out
Toast in/out250 msease-in-out

Timestamp display: Dates EEEE d MMMM (Tuesday 14 May); times 24-hour HH:mm; stored times are shown unchanged, no conversion.

Accessibility: text contrast ≥ 4.5:1, tap targets ≥ 44×44 px, visible focus ring #0F766E 2 px, labels on all icon buttons, chips reachable by keyboard.

8) Infrastructure Notes

Environment variables:

  • Server only: SUPABASE_SERVICE_ROLE_KEY, ADMIN_PASSWORD, ADMIN_COOKIE_SECRET
  • Browser-safe: NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY

Client vs server: the browser only calls Next.js route handlers; slot calculation, booking creation, status changes and rate limiting run on the server; row-level security denies all direct table access to the anon key.

Platform limits:

LimitPlatforms affectedDesign answer
No push or reminders without a serviceAllNo reminders in V1; confirmation page is the record
Vercel Hobby cron once per dayVercelNo scheduled jobs needed
Cookie cleared by browserAllAdmin signs in again

9) Out of Scope (V1)

  1. Online payments or deposits: tempting to cut no-shows, but it adds Stripe, refunds and legal text; price is paid at the shop.
  2. SMS or email reminders: the obvious next ask, but it needs a provider, consent and a scheduler.
  3. Customer accounts and saved dogs: tempting for repeat clients, but the reference-plus-phone lookup is enough for one groomer.
  4. Multiple groomers or rooms: the slot logic assumes one table; parallel capacity changes every overlap rule.
  5. Editable services and prices in the admin: services are seeded; changes are made in the database.
  6. Reschedule in place: customers cancel and rebook; moving a booking adds a second overlap path.
  7. Vaccination records, photos and groom history: useful for a groomer, but not needed to take a booking.

10) Success Criteria

  • ✓ A customer completes Book now → Request booking and lands on a page showing a 6-character reference and status Pending.
  • ✓ Full Groom medium shows 90 min · £60; Bath & Brush small shows 45 min · £30; Nail Trim large shows 15 min · £10.
  • ✓ With Rex 09:00-11:00 booked, no chip earlier than 11:00 appears for any service lasting more than 0 min on that day.
  • ✓ A booking whose end would pass closing time never appears as a chip (Saturday Full Groom large last chip is 11:00).
  • ✓ Two simultaneous submissions for the same start produce one success and one Sorry, that time was just booked. Please pick another.
  • ✓ Cancelling a booking more than 2 h before start frees its slot immediately; inside 2 h the button is hidden.
  • ✓ /admin redirects to /admin/login when signed out, and a wrong password shows Wrong password.
  • ✓ Confirm turns a Pending badge into Confirmed; Complete removes the action buttons.
  • ✓ Marking Monday 27 May closed greys it out in the customer date strip.
  • ✓ No console errors during the full booking flow from Book now to the confirmation page.

Spec your own app

Describe your idea in a sentence and get a spec like this in about a minute.

Generate my spec

7-day free trial · no card needed

More examples