Skip to content

PRD 09 — Storefront & Customer Booking

Status: draft · Priority tier: P0 · Size: L · Depends on: Foundation/auth (existing), Locations (PRD 01), Classes (PRD 02), Schedule (PRD 03), Appointments (PRD 04), Courses (PRD 05), Pricing Plans (PRD 06), Customers (PRD 07), Sales & Payments (PRD 08), Notifications (PRD 11), Website Builder (PRD 12)

1. Summary

The public, customer-facing site and the core customer loop: browse (home, timetable, classes, appointments, courses, packages/memberships, team, outlets, blog, FAQ, contact, legal), sign up / log in, then book a class session, book an appointment slot, or buy a package/membership, and manage it all from an account area. Without it the product has no customers; every admin PRD feeds this one.

2. Problem & goals

Customers must find a session or plan and pay/book with as few steps as possible, on a phone first. Goals:

  • Guest reaches the login gate from the timetable in 1 click (original: 1).
  • Signed-up customer books a free class in <= 3 clicks from the timetable after login (original: not observed).
  • Public pages show only published, upcoming, store-visible content; zero admin-only data leaks.
  • Pages usable at 390 px width with no horizontal scroll. Non-goals: theme/page editing (PRD 12); admin-side booking and sales (PRDs 03, 04, 08); reviews, referrals, cashback, loyalty; native apps; embed widget (PRD 15).

3. Users & roles

  • Guest: browses all public pages; blocked at Book Now / Buy now.
  • Customer: logged-in account holder; books, buys, cancels, edits profile.
  • Family member (v2): dependant managed by a parent account.
  • Owner/admin: configures content and rules elsewhere; sees results in Customers/Sales.

4. Scope

In (v1)

  • must: public pages: home, timetable, classes list, packages, memberships, locate us (outlets), contact form.
  • must: package/membership cards with Quick View and Buy now.
  • must: customer signup (email, password rules, profile) and login using existing auth (assumption A1); login gate on Book Now / Buy now with return to the intended action.
  • must: book a class session: free, with package/membership credits, or drop-in payment.
  • must: buy a package/membership with a payment method, promo code, and (if enabled) pay later.
  • must: account area: profile, upcoming/past bookings, cancel booking, credits/plans, purchases.
  • must: mobile layout (menu drawer + fixed bottom bar); timezone chip in the footer.
  • should: appointments page + slot booking; courses page; team; blog list/post; FAQ; legal pages (terms, privacy, refund and cancellation policies).
  • should: waivers to sign before booking; mobile-number requirement/verification rules; bot check on signup/login/contact.
  • should: class info popover on the timetable; filters (class, instructor, level); slots-left display.
  • could: family accounts; wallet balance; tracking pixels/UTM; floating WhatsApp button. Later (v2+)

  • should: waitlist ("Waitlisted" legend exists in the original timetable); class detail page; membership activation-date choice; saved cards.

  • could: reviews on class/appointment pages (toggles exist in PRD 02/04). Out

  • Shared cross-studio customer identity ("RezervPass" style): single-tenant product, no partner network; reason: privacy and scope.

  • Mobile-OTP password reset as the only recovery path: replaced by existing email-OTP/Google login (A1).
  • Branded native mobile app and ClassPass bookings.

5. User stories

  • US-09-01 As a visitor, I want to browse the timetable, classes, plans, team and outlets without an account so that I can decide before committing.
  • US-09-02 As a visitor, I want to sign up with email and a short profile so that I can book.
  • US-09-03 As a customer, I want to log in and return to the thing I was about to book so that I do not start over.
  • US-09-04 As a customer, I want to book a class using my package credit, or pay for a drop-in, so that I pay in the way I bought.
  • US-09-05 As a customer, I want to pick an appointment slot (location, service, staff, time) so that I can book a private session.
  • US-09-06 As a customer, I want to compare packages and memberships, preview details, and buy one with a promo code so that I get the best deal.
  • US-09-07 As a customer, I want to see upcoming and past bookings and cancel one so that I stay in control.
  • US-09-08 As a customer, I want to see my remaining credits, active plans, purchases and signed waivers so that I know what I own.
  • US-09-09 As a parent, I want to book for my family members so that I manage everyone in one place.
  • US-09-10 As a mobile visitor, I want a bottom bar for My Bookings, Book now, Packages and Memberships so that key actions are one tap away.
  • US-09-11 As a visitor, I want to send the studio a message so that I can ask a question before booking.
  • US-09-12 As a visitor in another timezone, I want to see the studio's timezone so that I read session times correctly.

6. Functional requirements

ID requirement priority evidence
FR-09-01 Public shell: top nav (logo/name, Home, Classes, Pricing > Packages / Memberships, Timetable, Locate us, Team, Blog, Contact, login button), footer (contact, FAQ, legal links, timezone chip). Nav items appear only when that content type has published content or is enabled. must S160, S170
FR-09-02 Home: hero, about, packages/memberships CTA, featured classes, outlets, latest blog; each section hidden (not placeholder text) when it has no content. Content is configured via PRD 12. must S160, S209zh
FR-09-03 Timetable: shows only published, upcoming sessions within the studio's timetable window (PRD 02 settings); horizontal date strip (14 days), per-day heading with count, row = time, duration, class, location/room, Book Now; empty day shows "0 classes"; empty timetable shows a friendly message. must S164, S236, S209zj
FR-09-04 Timetable filters: class, instructor, level; month label with date jump. should S236
FR-09-05 Timetable shows slots left per PRD 02 rule (always / only when <= N; optional total capacity), "Full" when 0, and a Booked marker on sessions the logged-in customer holds. must 02 S24, S236
FR-09-06 Class info popover on a timetable row shows the class description (no separate class page in v1). could S238
FR-09-07 Classes page: lists only classes flagged for store listing (PRD 02) with category tabs and filters (type, sub-category, level); card = image, name, duration, mode tag. A listed class with no upcoming session still shows. must S161, S237
FR-09-08 Appointments page: pick location, then service (name, duration, from-price), then staff (or any) and slot; only slots that satisfy PRD 04 availability and booking rules are offered. should 07 features, S209m-q
FR-09-09 Courses page: list of published courses with date, price, tickets left; ticket purchase follows the checkout in FR-09-24. should 07 features
FR-09-10 Packages and Memberships pages: grouped by category; card = name, price, payment type (one-time / recurring), credits, location access, validity, Quick View, Buy now. Only active, store-visible plans whose restriction (all / segment / specific customers / new-members-only) allows the viewer; ineligible plans are hidden or marked (open question 6). must S162, S163, S245, S209zk
FR-09-11 Quick View dialog: name, price, description, payment type, location access, validity, shareable flag, class credit count, onsite/online scope, Buy now. must S246, S209zm
FR-09-12 Team page: staff cards (photo, name, bio) for staff flagged public. Locate us: outlet cards (name, address, phone, map link). Blog: list with type filter and post page. FAQ: accordion. Each shows an empty state when no content. should S165-S169
FR-09-13 Contact form: name, email, mobile (optional), subject (select), message; bot check; success message; studio is notified by email (PRD 11). Rate-limited per IP. must S168, F64
FR-09-14 Legal pages: terms and conditions, privacy policy, refund policy, class and appointment cancellation policies (text from settings); policies are shown at payment and linked on the booking confirmation. must S155, S243
FR-09-15 Login gate: Book Now / Buy now / My Bookings for a guest opens the login dialog over the current page (URL unchanged); after login the original action resumes. No guest checkout. must S239, S247, S209zn
FR-09-16 Login uses existing auth (A1): email-OTP and Google. Dialog states: choose method, enter email, enter code, error/expired. must S171, existing auth
FR-09-17 Signup: step 1 email; step 2 profile page. With A1 the "password" step becomes email-OTP verification; if a password option is later added the rule is >= 8 characters with letters, numbers and 1 symbol, with confirm-mismatch error. must S175, S177, S178, F61
FR-09-18 Profile step fields: first name, last name (required); mobile (optional unless the studio requires it), photo (optional), date of birth (optional), gender (optional: male / female / prefer not to say), marketing consent (email + SMS, unchecked by default), legal line linking studio terms and privacy. must S179, S243
FR-09-19 The customer record is created (or matched to an existing admin-created customer by email) when the profile is submitted, not earlier; abandoned signups leave nothing behind. must F61
FR-09-20 Signup errors (invalid/undeliverable email, duplicate, server error) show inline and persist until dismissed, never as an auto-hiding toast only. Email format and domain are validated before the final step, not at Submit. must S244, F83
FR-09-21 Mobile-number rules (studio setting): not required / required at signup / verify by code at first purchase of selected plans, first booking of selected classes or appointments. Verification needs an SMS provider (open question 8). could S154
FR-09-22 Book a class (logged in): Book Now > summary (class, time, location, attendees count if group booking is on) > choose how to pay > confirm > confirmation with booking reference. Options offered are only those valid: Free (class is free), Use credits (customer holds an eligible active package/membership with enough credits and the class accepts it), Pay drop-in (class has a direct price). must 08 S209h, S209o-q, F60
FR-09-23 Booking rules applied server-side: booking window (earliest/latest, PRD 02), capacity (no overbooking under concurrency), no duplicate booking of the same session by the same customer, session not cancelled/past, required waiver signed (if enforcement = must), customer eligibility. Failure gives a specific message. must 02 S24, S152b
FR-09-24 Checkout page (plans, courses, drop-ins): order summary, promo code field (applies per PRD 06 rules, shows discount line, error for invalid/expired/used), payment method list (enabled gateway methods and manual methods with the studio's instructions), pay-later option when enabled, price total with tax per PRD 08, Pay button disabled until valid, and policy link. must S203-S207, S150, S133
FR-09-25 Pay later: if the studio enables it, the customer books/buys now with status Unpaid and a due rule (on fulfilment or within N days) and optional reminder email; unpaid plans can book only if "allow booking with unpaid plan" is on. should S150, S209t
FR-09-26 Buying a package/membership: on success the plan appears in the account with start date, expiry, credits; credits are usable immediately for eligible classes; membership activation date = purchase date in v1. A purchase rule can require an active membership before a package may be bought. must S209ze, S150, F63
FR-09-27 Payment failure/abandon: no booking or plan is created as paid; the customer sees the error and can retry; a held seat is released after a short hold (open question 3). must gap
FR-09-28 Confirmation screen + email (PRD 11): what, when, where (address, directions link), booking ref, how paid, cancellation policy, link to My Bookings. must 06 notification editor
FR-09-29 Appointment booking (logged in): slot selection (FR-09-08) > summary > payment (credits / drop-in / pay later) > confirm; same rules engine as FR-09-23 plus staff/resource availability and minimum notice. should S209m-t
FR-09-30 Account area (route behind login): Profile, Bookings, Plans & credits, Purchases, Waivers, Family (flagged). Guests are redirected to the login dialog. must gap
FR-09-31 Profile: edit name, mobile, photo, DOB, gender, marketing consent; email change through existing auth flow; delete-account request (open question 7). must S179, existing user profile
FR-09-32 Bookings: Upcoming and Past tabs; row = date/time, class or service, location, status (Booked, Completed, Cancelled, No show, Late cancelled), payment option; detail with ref and policy; empty states. must S209zf, gap
FR-09-33 Cancel booking: Cancel on an upcoming booking when self-cancel is on; confirmation dialog states whether it is late (inside the late-cancel window) and what happens to credits/payment; result updates status and returns/forfeits credits per studio rule (open question 2). Inside the window is flagged late, not blocked, in v1. must 02 S24/S33, S209z
FR-09-34 Plans & credits: each plan shows name, credits left/total, start, end, status (Active, Expired, Used up, Inactive), bookings made with it. must S209ze
FR-09-35 Purchases: list of transactions (date, number, item, method, amount, status) with receipt/invoice download and pay-now button for unpaid items. should S209zg, S207
FR-09-36 Waivers: list of studio waiver forms with signed/unsigned state; sign inline (typed name + checkbox + timestamp); unsigned required waiver blocks booking with a link to sign. should S152, S209x
FR-09-37 Family accounts (flag): parent adds dependants (name, DOB); can book/cancel/purchase on behalf and sees their bookings; dependants have no login. could S153
FR-09-38 Mobile (<= 640 px): hamburger drawer with the same nav plus login row; fixed bottom bar (My Bookings, Book now, Packages, Memberships); cards stack; login dialog is full-width; tap targets >= 44 px. Logged-in mobile account layout: open question 9. must S183, S184, S248-S250
FR-09-39 Timezone chip in the footer shows the studio timezone and offset; all session times render in the studio timezone with the zone label on booking confirmation (customer-local conversion is v2). must S160
FR-09-40 SEO and sharing: page title/description, readable slugs, 404 page; unpublished or hidden content returns 404 even by direct URL. should S100f, S22c
FR-09-41 Bot protection on signup, login code request and contact form (any captcha, studio-neutral). should S171, F62
FR-09-42 Public API endpoints are separate from admin ones, never return admin fields (internal notes, other customers, costs), and every account endpoint is behind use: [auth] and scoped to the caller's own customer record. must CLAUDE.md safety
FR-09-43 Optional tracking: Meta/GA/GTM/TikTok ids and UTM capture on bookings; floating WhatsApp button. could 07 features

7. Screens

Our screen Modelled on States
Shell: top nav, footer, timezone chip S160, S170 desktop, mobile, logged-out, logged-in
Home S160, S209zh filled, sections hidden when empty
Timetable S164, S236 loading, filled, empty day, empty week, full session, booked, filters open, error
Classes list S161, S237 empty, filled, filtered-empty
Appointments S209m-q (admin analog) location pick, service pick, slots, no slots
Courses none (not observed) list, detail
Packages / Memberships S162, S163, S245, S209zk empty, filled, restricted/ineligible
Quick View dialog S246, S209zm open
Team, Locate us, Blog, FAQ S165-S169 empty, filled
Contact S168 idle, invalid, sending, sent, error
Legal pages S155 (settings text) filled
Login / signup dialog S171, S175, S177, S239 method, email, code, error
Complete profile S179, S243 blank, invalid, submitting, error (persistent)
Booking summary + confirmation none (not observed) free, credits, drop-in, error, success
Checkout S203-S207 admin analog cart, promo applied/error, payment method, pay later, paying, failed, complete
Account: profile, bookings, plans, purchases, waivers, family S209ze-zg admin analogs empty, filled, cancel dialog
Mobile drawer + bottom bar S183, S184, S248-S250 open, closed

8. Flows

  • Guest to booking: Timetable > Book Now (1, gate) > login (email, code) > resumes at booking summary > choose pay option (1) > Confirm (1). Target: <= 3 clicks after login, plus typing the code.
  • Signup: Login > Sign up > email > code > profile (name) > Submit. Original: 6 clicks; target <= 5.
  • Buy plan: Packages > Buy now (or Quick View > Buy now) > login if guest > checkout (promo optional) > pay > confirmation with credits. Target: <= 4 clicks after login.
  • Cancel: Account > Bookings > booking > Cancel > confirm (<= 4 clicks).
  • Edge cases: session fills while the customer is in checkout (clear "sold out" message, no charge); promo invalid; credits insufficient (offer drop-in or buy plan); customer already booked; login code expired; plan expires between selecting and booking; payment pending (manual method) shows Pending approval status; admin-created customer with same email is matched on signup; session cancelled by studio (customer notified, credits returned).

9. Data model (guesses marked *)

Reuses User, Customer (PRD 07), ClassSession, Booking (PRD 02/03), Plan, PlanPurchase, Transaction (PRD 06/08). New or extended:

  • CustomerProfile(customer_id, mobile, dob, gender, photo_url, marketing_consent, consent_at, signup_source)
  • Waiver(id, title, body, enforcement) and WaiverSignature(waiver_id, customer_id, signed_at, signed_name)
  • ContactMessage(id, name, email, mobile, subject, body, created_at, handled*)
  • StorefrontPage content (hero, about, FAQ, blog, team flags): owned by PRD 12.
  • FamilyMember*(parent_customer_id, name, dob)
  • CheckoutHold(session_id, customer_id, expires_at) for seat hold. Booking gains: payment_option (free / credits(purchase_id) / drop-in(transaction_id) / unpaid), late_cancelled bool, cancelled_at, cancelled_by (customer/staff), source (store/admin).

10. Business rules & edge cases

  • Rule precedence: session override > class settings > none (PRD 02); appointment equivalents from PRD 04.
  • Only published, upcoming, store-visible items are public; hidden class, past session, draft, inactive plan return 404.
  • Slots left never reveals capacity unless "include total" is set.
  • Credits are consumed at booking and returned on in-window cancel; late cancel follows the studio rule (open question 2). Free classes cannot use credits.
  • One booking per customer per session unless group booking adds attendees.
  • Booking window and capacity are checked again at confirm, atomically.
  • Customer times: studio timezone in v1; stored UTC.
  • Signup never creates a customer from an unverified email (A1); marketing consent defaults off and is stored with timestamp.
  • Passwords (if ever enabled) are never logged; tokens live only in web/src/lib/auth.ts.
  • Payment secrets stay server-side; card data never touches our server (gateway-hosted fields).
  • Cancellation/refund policy text shown before paying; acceptance recorded on the transaction*.

11. Permissions

Action Guest Customer Staff/Admin
Browse public pages, contact form yes yes yes
Book, buy, cancel gate own records via admin PRDs
Account area redirect to login own no (admin sees via Customers)
Family dependants no own no

12. Acceptance criteria

  • Given a guest on the timetable, when they click Book Now, then the login dialog opens, the URL is unchanged, and after login the booking summary for that session opens.
  • Given a session hidden/unpublished, when requested by direct URL, then 404.
  • Given an empty studio, then Home hides empty sections and list pages show an empty state, with no placeholder text.
  • Given slots mode "<= 3 remain" and 5 left, then no count; at 3 left, "3 left"; at 0, "Full" and Book disabled.
  • Given a free class, then credit option is not offered; given a paid class and no credits, then only drop-in is offered.
  • Given a customer with 1 eligible credit, when they book, then the credit decreases by 1 and the booking shows "credits".
  • Given capacity 1 and two customers confirming at once, then exactly one booking succeeds and the other sees a sold-out message with no charge.
  • Given booking window "latest 2 hours before" and a session in 1 hour, then Book is rejected with a window message.
  • Given late_cancel_hours = 12 and a cancel 11 hours before, then the dialog warns it is late and the booking is marked late cancelled.
  • Given an invalid promo code, then an error shows and the total is unchanged; a valid one shows a discount line and the reduced total.
  • Given a failed payment, then no paid purchase or booking exists and the customer can retry.
  • Given an undeliverable email at signup, then the error appears inline before the profile page and persists.
  • Given an abandoned signup after email, then no customer record exists.
  • Given a required unsigned waiver, then booking is blocked with a sign link.
  • Given a logged-out request to any account endpoint, then 401; given customer A, then customer B's bookings are never returned.
  • Given a 390 px viewport, then the bottom bar shows four actions and the page has no horizontal scroll.
  • Given the footer, then it shows the studio timezone and offset.

13. Dependencies & open questions

Depends: PRDs 02, 03, 04, 05, 06, 07, 08, 11, 12, existing auth. Assumptions

  • A1: customers use our existing email-OTP and Google login (one identity, this studio only). The original's email + password + shared "RezervPass" account is not copied; the password rules above apply only if a password option is added.
  • A2: no guest checkout (original gates every booking/purchase behind login).
  • A3: studio timezone only in v1. Open questions (never observed in recon)

  • Payment step: methods, layout, saved cards, promo entry, tax display never observed (signup was blocked). Minimal v1 in FR-09-24; gateway choice is open in PRD 08.

  • Late cancel: credits returned or forfeited? Customer self-cancel cutoff and refund of drop-in payments? Not observed.
  • Seat hold during checkout and its length: not observed.
  • Account area (profile, bookings, plans, purchases, waivers, family, wallet) never seen; all FR-09-30 to 37 are minimal v1 guesses modelled on the admin customer tabs.
  • Booking confirmation screen and class/appointment/course detail pages never seen; original had only an info popover on the timetable.
  • Ineligible plans: hidden or shown with a warning? (release notes mention eligibility warnings and new-member-only plans.)
  • Delete-account / data export for customers (privacy law dependent).
  • SMS provider for mobile verification; mobile as login identifier (original allowed it) is not in v1.
  • Logged-in mobile layout and bottom-bar badge/active states not observed.
  • Waitlist behaviour: legend only.
  • Plan gating: family accounts, mobile verification, pay later in original were plan-gated; pricing tier: TBD.
  • Original rejected the example.com domain at signup (server-side); rule and whether it is a real deliverability check unknown.

14. Evidence

See evidence/evidence.md.