140 Commits

Author SHA1 Message Date
belviskhoremk
ba4d022a6a docs: expand README and add CLAUDE.md
README now covers site structure, storage buckets, admin user
creation, and deployment. CLAUDE.md documents project conventions
(French UI copy, styling tokens, data-fetching patterns, migration
style) and working constraints (no git remote, manual deploys/db
pushes require confirmation).

Claude-Session: https://claude.ai/code/session_01NhxA4npAXAxmCDEJt6kxSH
2026-08-25 11:02:44 +02:00
belviskhoremk
3b718f7123 fix: center footer logo with the contact/navigation columns
The logo was much taller than the text columns and top-aligned,
making the footer look lopsided. Vertically centers the grid columns
and shrinks the logo so it's proportionate to the rest of the footer.

Claude-Session: https://claude.ai/code/session_01NhxA4npAXAxmCDEJt6kxSH
2026-08-25 10:57:07 +02:00
belviskhoremk
41b6fa7292 feat: use the branded logo in nav/footer, update favicon
Replaces the plain text wordmark with the client-provided logo: a
compact icon+wordmark lockup in the nav, and the full crown/wordmark/
tagline mark (recolored for the dark footer) in the footer. Background
made transparent by color-keying the flat cream backdrop out of the
source artwork.

Claude-Session: https://claude.ai/code/session_01NhxA4npAXAxmCDEJt6kxSH
2026-08-25 10:55:34 +02:00
belviskhoremk
cf2fc1aa42 feat: collect hair intake details and reference photos when booking
Adds hair length/type, scalp sensitivities, wet/dry state, desired
style, who provides extensions, guest count, and two optional
reference photos to the booking form. Photos upload to a new private
booking-photos storage bucket; admin can view intake details and
signed photo URLs per booking.

Claude-Session: https://claude.ai/code/session_01NhxA4npAXAxmCDEJt6kxSH
2026-08-25 10:42:58 +02:00
belviskhoremk
efce78ea93 feat: add SEO basics (meta tags, robots.txt, sitemap, per-page titles)
lang=fr, meta description, Open Graph/Twitter tags, and a lightweight
usePageTitle hook wired into each public page.

Claude-Session: https://claude.ai/code/session_01NhxA4npAXAxmCDEJt6kxSH
2026-08-25 10:42:52 +02:00
belviskhoremk
cca472d72c feat: display salon rules as bullet points instead of dense paragraphs
Each rule's body text is split into sentences and rendered as a dash
list, matching Mira's request for less dense, easier-to-scan rules.
2026-08-15 19:22:10 +02:00
belviskhoremk
c5dcfc0f6e feat: add a month calendar view to the admin bookings page
Shows a badge with the booking count on each day; clicking a date
filters the table below to that day's bookings, with a button to
clear the filter and see everything again.
2026-08-14 13:19:01 +02:00
belviskhoremk
396b5cf0e5 fix: show/enter slot duration in hours instead of minutes in admin schedule
Storage stays in minutes (slot_minutes); the admin form now takes
hours (0.25 step), matching the service-duration input.
2026-08-14 13:12:57 +02:00
belviskhoremk
ca2e7b8f4e fix: show/enter service duration in hours instead of minutes in admin panel
Storage stays in minutes (duration_minutes); the admin form now takes
hours (0.25 step) and the list shows the formatted "Xh Ym" duration.
2026-08-14 09:42:29 +02:00
belviskhoremk
1661c24545 fix: include site-content and testimonial images in seed script upload
Extended uploadImages() call to include the 4 image files referenced by
the site_content and testimonials migrations: about/mira-portrait.jpg
and testimonials/{amara,keisha,nadia}.jpg. These paths are seeded in
the migration but were not being uploaded to the salon-images bucket,
causing broken image links on deploy.

Verified files exist in public/ and match migration seed paths.

Claude-Session: https://claude.ai/code/session_01NhxA4npAXAxmCDEJt6kxSH
2026-08-13 09:53:03 +02:00
belviskhoremk
0060cabfd4 fix: repair tsc/lint breakage introduced across Tasks 6, 11-13
Two independent plan gaps, both surfaced only now because no prior task
in this plan ran a full tsc/lint pass (only scoped vitest per task):

1. Task 6 added required featured/featuredOrder fields to the Product
   type but only updated products.test.ts's fixture. ProductCard.test.tsx
   and InquiryModal.test.tsx also construct Product-typed fixtures and
   were never updated, breaking `tsc -b`/`npm run build`.

2. Tasks 11-13's plan text called the extracted load*() function
   directly from a bare `useEffect(() => { void load*() }, [])`, which
   trips this repo's react-hooks/set-state-in-effect rule. This
   project's established convention (already used in AdminSchedule.tsx)
   wraps the call in an effect-local inner async function instead.

Claude-Session: https://claude.ai/code/session_01NhxA4npAXAxmCDEJt6kxSH
2026-08-13 09:43:46 +02:00
belviskhoremk
2093b9ca3a style: apply shared admin design to Services/Schedule/Bookings/Inquiries pages
Apply the shared admin design system (adminShared.module.css) to the 4 remaining
legacy admin pages via CSS composition. All buttons in these 4 pages now use the
shared button classes (button, buttonSecondary, buttonDanger) for consistent styling.

- AdminServices: 3 buttons (Modifier, Supprimer, Enregistrer le service)
- AdminSchedule: 3 buttons (Enregistrer l'horaire, Retirer, Ajouter une exception)
- AdminBookings: 2 buttons (Annuler, Marquer comme terminée)
- AdminInquiries: 1 button (Marquer comme lue)

All tests pass unchanged (127 tests). No data-fetching or event-handler logic changed.

Claude-Session: https://claude.ai/code/session_01NhxA4npAXAxmCDEJt6kxSH
2026-08-13 09:39:52 +02:00
belviskhoremk
af4f0b3a52 feat: wire AdminContent/AdminTestimonials/AdminRules into admin nav and routes 2026-08-13 09:35:48 +02:00
belviskhoremk
b72acd8a26 feat: add featured toggle/reorder to AdminProducts; apply shared admin design 2026-08-13 09:32:14 +02:00
belviskhoremk
194144fa79 feat: add AdminRules page with CRUD, reordering, and icon picker 2026-08-13 09:28:58 +02:00
belviskhoremk
28b103cf2c feat: add AdminTestimonials page with CRUD and reordering 2026-08-13 09:25:42 +02:00
belviskhoremk
bdf8d9cc3a feat: add AdminContent page for editing site_content
Implement new admin page for editing the site_content singleton table. Includes:
- Form with fields for hero headline/tagline, about bio, portrait image, contact email, and hours
- Image upload via uploadSalonImage with about/ path prefix
- Data persistence via supabase.from('site_content').update().eq('id', 'default')
- Composable styles from adminShared.module.css
- Full test coverage: loading content and saving edits

Claude-Session: https://claude.ai/code/session_01NhxA4npAXAxmCDEJt6kxSH
2026-08-13 09:21:06 +02:00
belviskhoremk
8806ed2f04 feat: wire Rules page to live fetchRules data
- Replace static rules import with async fetchRules() function
- Update RuleIcon prop from ruleId to iconKey
- Add error state handling for data loading failures
- Add .error CSS class for error message styling

Closes Task 5's deferred compilation breakage: Rules.tsx now imports fetchRules (async) instead of the deleted static rules export, and uses the renamed iconKey prop on RuleIcon.

Claude-Session: https://claude.ai/code/session_01NhxA4npAXAxmCDEJt6kxSH
2026-08-13 09:18:16 +02:00
belviskhoremk
96c8578c2c feat: wire Footer contact info to live site content 2026-08-13 09:14:40 +02:00
belviskhoremk
36c042bb90 feat: wire Home page to live site content, testimonials, and featured products
Claude-Session: https://claude.ai/code/session_01NhxA4npAXAxmCDEJt6kxSH
2026-08-13 09:11:46 +02:00
belviskhoremk
004ba924b2 feat: extend Product with featured/featuredOrder 2026-08-13 09:08:31 +02:00
belviskhoremk
35b28f6a2a feat: replace static rules array with fetchRules; RuleIcon takes iconKey
- Rewrite src/data/rules.ts: export async fetchRules() instead of static rules array
- Fetch from 'rules' table, map icon_key to iconKey in RuleSection interface
- Create src/data/rules.test.ts with 2 tests (fetch mapping, error handling)
- Rename RuleIcon prop from ruleId to iconKey for consistency with new data shape
- Update RuleIcon.test.tsx to use iconKey prop name

Claude-Session: https://claude.ai/code/session_01NhxA4npAXAxmCDEJt6kxSH
2026-08-13 09:05:25 +02:00
belviskhoremk
2aee242a01 feat: add fetchTestimonials data function 2026-08-13 09:01:46 +02:00
belviskhoremk
c5a2b9dfae feat: add fetchSiteContent data function 2026-08-13 09:01:18 +02:00
belviskhoremk
728fc31e83 feat: introduce admin design tokens and redesign AdminLayout/AdminLogin
Create AdminApp.module.css with admin-specific design tokens (--admin-bg, --admin-surface, --admin-border, --admin-text, --admin-text-muted, --admin-primary, --admin-primary-hover, --admin-danger, --admin-radius, --admin-shadow, --admin-font, --admin-space-xs/sm/md/lg/xl) scoped to .adminRoot wrapper class to avoid collisions with public site tokens.

Create adminShared.module.css with composable component classes (page, card, table, form, error, button, buttonSecondary, buttonDanger) that all later admin pages will build against.

Wrap AdminApp Routes in div with adminRoot class to establish token scope.

Redesign AdminLayout.module.css to use new admin tokens with improved sidebar styling (240px width, better spacing, hover states, active link highlighting).

Redesign AdminLogin.module.css to use new admin tokens with improved form styling (better card styling, focus states, proper typography).

No changes to AdminLayout.tsx or AdminLogin.tsx needed — existing JSX structure matches new CSS selectors.

Claude-Session: https://claude.ai/code/session_01NhxA4npAXAxmCDEJt6kxSH
2026-08-13 08:58:00 +02:00
belviskhoremk
a5b210f309 feat: add site_content, testimonials, and rules tables; featured products 2026-08-13 08:51:41 +02:00
belviskhoremk
fe33d15ddc docs: fix wrong task-number reference in Phase 2D plan (Task 8 -> Task 9)
Claude-Session: https://claude.ai/code/session_01NhxA4npAXAxmCDEJt6kxSH
2026-08-13 08:38:29 +02:00
belviskhoremk
64a0ee5a84 docs: add Phase 2D implementation plan
Claude-Session: https://claude.ai/code/session_01NhxA4npAXAxmCDEJt6kxSH
2026-08-13 08:24:13 +02:00
belviskhoremk
510847eac2 docs: add Phase 2D design spec (site content CMS + admin redesign)
Claude-Session: https://claude.ai/code/session_01NhxA4npAXAxmCDEJt6kxSH
2026-08-13 08:12:37 +02:00
belviskhoremk
f41c6f5812 feat: replace admin account with admin@braidsbymira.com
Swaps the single fixed admin UUID that every admin-write/admin-read RLS
policy and the frontend's ProtectedRoute check against, moving off the
personal belviskhoremk@gmail.com account onto a dedicated
admin@braidsbymira.com login created via the Auth admin API. Updates
the RLS test suite's admin-impersonation UUID to match.

Claude-Session: https://claude.ai/code/session_01NhxA4npAXAxmCDEJt6kxSH
2026-08-12 23:51:49 +02:00
belviskhoremk
56679b872b chore: ignore .vercel and local .env files
Added by `vercel link`/`vercel --prod` during Task 12's live deployment
verification.

Claude-Session: https://claude.ai/code/session_01NhxA4npAXAxmCDEJt6kxSH
2026-08-12 23:28:24 +02:00
belviskhoremk
43f4f5f46e fix: block duration-aware slot overlaps, not just exact time matches
booked_slots previously recorded only (date, time), so availability
checking excluded an exact time match but not a real overlap — a
4-hour service booked at 9:00 AM didn't block 11:00 AM or 1:00 PM.
Add duration_minutes to booked_slots (populated by sync_booked_slot()
from the booked service's own duration) and switch client-side
computeAvailableSlots to a half-open-interval overlap check, also
rejecting candidates that wouldn't finish before closing time.

Claude-Session: https://claude.ai/code/session_01NhxA4npAXAxmCDEJt6kxSH
2026-08-12 23:15:05 +02:00
belviskhoremk
4ca40bf2b5 fix: address final whole-branch review findings for Phase 2C
Critical: computeAvailableSlots looped forever when slot_minutes was
0 or negative -- reachable in production, since AdminSchedule's
slot-length input has no validation and clearing it yields Number('')
=== 0. Guarded the computation, added min="5"/required to the admin
input as defense in depth, and added a regression test.

Important fixes:
- Removed the one eslint-disable that slipped into the branch
  (InquiryModal.test.tsx), replacing it with the explicit vi.fn<...>
  generic type pattern already used everywhere else. Also cleaned up
  a similar unused-var workaround in useBooking.test.ts for
  consistency.
- Added a submitting/in-flight guard to both booking and inquiry
  submission, so a double-click can no longer produce a false
  "slot already taken" error after a booking actually succeeded (or
  a duplicate inquiry row).
- Added loading states to Home.tsx (services/products) and Book.tsx
  (services list, date/slot step) -- previously only Shop.tsx had
  one, though the spec named all three pages.
- Restored the full date -> slot -> contact -> confirmation ->
  persisted-booking test in Book.test.tsx, which had been replaced
  rather than supplemented by a narrower slot-fetching test.

Deferred to a follow-up (explicit decision, not silently dropped):
duration-aware slot conflicts. The current model only blocks an exact
(date, time) match, so a 4-hour service booked at 9:00 AM doesn't
block 11:00 AM even though they'd physically overlap. Inherited from
the Phase 2A data model; this branch is what makes it reachable by
real customers, so it needs a recorded decision before go-live rather
than silent inheritance.

Claude-Session: https://claude.ai/code/session_01NhxA4npAXAxmCDEJt6kxSH
2026-08-12 23:03:32 +02:00
belviskhoremk
5a4581c137 feat: persist real inquiries to Supabase 2026-08-12 22:49:21 +02:00
belviskhoremk
4ba6f3e178 feat: persist real bookings to Supabase with slot-conflict handling
Make useBooking's submit() function async and integrate with Supabase bookings table.
Add submitError field for displaying conflict/error messages to users.
Handle SQLSTATE 23505 (duplicate key) by returning to slot selection.

Claude-Session: https://claude.ai/code/session_01NhxA4npAXAxmCDEJt6kxSH
2026-08-12 22:43:08 +02:00
belviskhoremk
930a080ccf feat: wire Book page to live services/availability data
Replaces static services import and mock slot/date functions with
fetchServices() (Task 4) and useAvailability() (Task 5).

Fixes two real bugs surfaced by this task's cross-task integration:

1. Book.test.tsx's useAvailability mock returned fresh inline-wrapped
   functions every render, causing Book.tsx's exhaustive-deps-correct
   effect to re-fire every render (infinite loop, hung the test
   indefinitely). Fixed by returning the already-stable vi.fn()
   references directly.

2. useAvailability.ts's fire-and-forget `void load()` had no
   try/catch, so a synchronous throw from a mocked/unexpected
   supabase.from() call became an unhandled promise rejection
   (surfaced via Home.test.tsx rendering Book with an unmocked
   schedule_template/schedule_exceptions query). Added try/catch;
   pure robustness fix, no production behavior change since the real
   client never throws synchronously.

Also fixed a react-hooks/set-state-in-effect lint error (moved the
slot effect's guard clause inside its async closure, same pattern
used throughout Phase 2B) and the resulting exhaustive-deps warning,
by memoizing useAvailability's return value so Book.tsx can safely
depend on the whole `availability` object without risking the same
re-render-loop class of bug this task started with.

Claude-Session: https://claude.ai/code/session_01NhxA4npAXAxmCDEJt6kxSH
2026-08-12 22:35:12 +02:00
belviskhoremk
f45a5bf8ec feat: wire Home page to live Supabase product and service data
Claude-Session: https://claude.ai/code/session_01NhxA4npAXAxmCDEJt6kxSH
2026-08-12 20:37:55 +02:00
belviskhoremk
6a27127208 feat: wire Shop page to live Supabase product data 2026-08-12 20:32:43 +02:00
belviskhoremk
c8d24a2a3a docs: warn that re-seeding reverts admin-panel edits 2026-08-12 20:27:15 +02:00
belviskhoremk
2dfc4af249 feat: upload product/service images to Supabase Storage during seeding 2026-08-12 20:23:29 +02:00
belviskhoremk
375dc5eed8 feat: add useAvailability hook for real schedule/booking data 2026-08-12 20:17:24 +02:00
belviskhoremk
a0e8a2ca60 feat: fetch services from Supabase instead of static mock data 2026-08-12 20:12:53 +02:00
belviskhoremk
063874b60c docs: proactively fix the vi.fn spread-args TS2556 pattern in remaining tasks
Task 3's implementer independently hit and fixed this exact bug
(a vi.fn() given a zero-arg implementation, then invoked via a spread
of unknown[] args inside a vi.mock factory, fails tsc -b with TS2556).
Applied the same fix (explicit vi.fn<...> generic type) to the other
occurrences of this pattern still written the old way in this plan's
Task 3 and Task 4 sections, so those implementers don't have to
rediscover it from scratch. Task 5, 10, and 11's occurrences were
checked and found to already use safe patterns (single literal-arg
calls, or bare vi.fn() with no implementation) that don't trigger this.

Claude-Session: https://claude.ai/code/session_01NhxA4npAXAxmCDEJt6kxSH
2026-08-12 20:11:15 +02:00
belviskhoremk
72fa3e0f90 docs: fix plan's Task 7-9 test mocking approach
Task 3's review surfaced that the plan's original vi.importActual-based
mocking for Shop.test.tsx/Home.test.tsx/Book.test.tsx would execute the
real data-layer modules, which import the real Supabase client and
construct it at module-load time via createClient(VITE_SUPABASE_URL,
...) -- throwing "supabaseUrl is required" in a test env with no .env
file, before the test even runs.

Switched all three to mock ../lib/supabase directly instead, matching
the pattern Task 3's own products.test.ts already uses successfully.
Also fixed 3 occurrences of `err instanceof Error ? err.message :
String(err)` in the planned Shop.tsx/Home.tsx/Book.tsx error handling
-- fetchProducts/fetchServices throw the raw Postgrest error object
(not necessarily an Error instance), so this would have rendered
"[object Object]" instead of the real message in the mocked test
scenario.

Claude-Session: https://claude.ai/code/session_01NhxA4npAXAxmCDEJt6kxSH
2026-08-12 20:09:10 +02:00
belviskhoremk
79d05ca62d feat: fetch products from Supabase instead of static mock data
The fromMock in the test uses vi.fn's explicit generic type parameter
instead of inferring from a zero-arg implementation, since the brief's
literal (...args) => fromMock(...args) wrapper otherwise fails to
typecheck (TS2556) against tsc -b.
2026-08-12 20:01:49 +02:00
belviskhoremk
1cf94a1a2c feat: replace mock availability generator with real schedule computation 2026-08-12 19:56:16 +02:00
belviskhoremk
631ec0b567 feat: add resolveImageUrl helper for salon-images public URLs 2026-08-12 19:52:54 +02:00
belviskhoremk
80f634d93a docs: add Phase 2C public site live data implementation plan
Claude-Session: https://claude.ai/code/session_01NhxA4npAXAxmCDEJt6kxSH
2026-08-12 19:50:40 +02:00
belviskhoremk
3a64090a39 docs: add Phase 2C public site live data design spec
Claude-Session: https://claude.ai/code/session_01NhxA4npAXAxmCDEJt6kxSH
2026-08-12 19:28:12 +02:00
belviskhoremk
9b85218a61 fix: add SPA rewrite rule for Vercel deployment
Without this, Vercel serves a 404 for any client-side route accessed
directly (e.g. /admin/login, /shop) since it has no server-side route
matching those paths — only index.html exists as a static file. React
Router then never gets a chance to handle the path client-side.

Claude-Session: https://claude.ai/code/session_01NhxA4npAXAxmCDEJt6kxSH
2026-08-12 19:05:01 +02:00