Commit Graph

65 Commits

Author SHA1 Message Date
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
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
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
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
3eb361f57f feat: translate admin panel to French
Translates all user-visible text in the admin panel (login, nav,
products, services, schedule, bookings, inquiries, and the shared
friendlyError helper) to plain, direct French for Mira's internal
use. Database CHECK-constraint enum values (product category,
booking/inquiry status) and column names are left untouched in code;
French display labels are added via lookup maps instead. Corresponding
test assertions updated to match.
2026-08-12 18:51:53 +02:00
belviskhoremk
b84db38096 feat: translate public site to French
Translates all user-visible English text on the public-facing pages
(Home, Shop, Book, Rules, Nav, Footer, InquiryModal, StepProgress,
ServiceCard, ProductCard) plus product and service data to French,
matching the tone/register of the already-French rules.ts. Category
enum values, IDs, route paths, and admin pages are left untouched.
Updates matching test assertions to the new French copy.

Claude-Session: https://claude.ai/code/session_01NhxA4npAXAxmCDEJt6kxSH
2026-08-12 18:51:26 +02:00
belviskhoremk
3504790216 fix: address final whole-branch review findings for Phase 2B admin panel
Consolidated fix wave for 1 Critical + 6 Important findings from the final
review before merge:

- Critical: marking a booking completed no longer frees its booked_slots
  mirror row or the unique-slot index, closing a double-booking hole
  (new migration 20260812110508, with regression pgTAP tests added first
  and confirmed failing against the old trigger/index).
- uploadSalonImage() now returns the storage-relative path instead of an
  absolute public URL, so products/services.image stays one consistent
  format.
- The product/service Id field is disabled while editing, preventing
  accidental duplicate rows from an upsert keyed on a changed id.
- Cancel / Mark completed in AdminBookings now require window.confirm,
  matching the existing delete-confirmation pattern.
- /admin and any unmatched /admin/* path now redirect to /admin/products
  instead of rendering blank.
- Public signup is disabled in supabase/config.toml, and ProtectedRoute
  adds a defense-in-depth check that the session belongs to the admin
  user id, not just that a session exists.
- Product/service delete now surfaces a friendly message for FK-violation
  errors (23503) via a new friendlyError() helper, instead of a raw
  Postgres error string.

Claude-Session: https://claude.ai/code/session_01NhxA4npAXAxmCDEJt6kxSH
2026-08-12 13:12:41 +02:00
belviskhoremk
540b8b407b feat: wire up /admin/* routing
Adds AdminApp as a lazy-loaded route subtree in App.tsx, mounting
ProtectedRoute, AdminLogin, AdminLayout, and all five CRUD pages built
in Tasks 3-11. Mounted as a sibling of the public Layout route (not
nested under it), since the admin panel has its own AdminLayout
sidebar rather than the public nav/footer.

Manually verified end-to-end against the local Supabase stack: sign-in
redirect guard, all five pages rendering real seeded data, creating and
cancelling a real booking through the UI (confirming the booked_slots
cleanup trigger from the merged Phase 2A follow-up fixes correctly
frees the slot), and sign-out. No console errors across the flow.

Claude-Session: https://claude.ai/code/session_01NhxA4npAXAxmCDEJt6kxSH
2026-08-12 12:52:25 +02:00
belviskhoremk
85de1cf92e feat: add admin inquiries page with mark-as-read action
Claude-Session: https://claude.ai/code/session_01NhxA4npAXAxmCDEJt6kxSH
2026-08-12 11:49:53 +02:00
belviskhoremk
5bcc5ed6c8 feat: add admin bookings page with status actions 2026-08-12 11:45:58 +02:00
belviskhoremk
e86d863773 feat: add admin schedule template and exceptions page
Claude-Session: https://claude.ai/code/session_01NhxA4npAXAxmCDEJt6kxSH
2026-08-12 11:41:26 +02:00
belviskhoremk
feb4c254e2 feat: add admin services CRUD page 2026-08-12 11:37:44 +02:00
belviskhoremk
4c2c558fca feat: add admin products CRUD page 2026-08-12 11:33:39 +02:00
belviskhoremk
795635b4fa fix: use accurately-typed mock matching real Supabase API
Replace variadic spread pattern with properly-typed single-argument mock.
storage.from() only takes bucketId string argument, so fromMock type
accurately reflects the actual API. No suppression needed - TypeScript
and ESLint both pass cleanly.
2026-08-12 11:28:05 +02:00
belviskhoremk
ae373cac85 fix: minimal TypeScript suppression for vitest mock compatibility
Use @ts-expect-error on vi.mock line only - vitest mock function calls
require spreading unknown[] which TypeScript can't verify as type-safe
without explicit suppression or use of 'any' type. This is a known
limitation of TypeScript with generic mock frameworks.
2026-08-12 11:23:42 +02:00
belviskhoremk
f7b1a718c8 fix: resolve TypeScript and linting issues in storage test 2026-08-12 11:16:35 +02:00
belviskhoremk
0f4b95a6dd feat: add salon-images upload helper 2026-08-12 11:13:02 +02:00
belviskhoremk
27ef99c76d fix: only navigate on sign-out success, not on error 2026-08-12 11:10:28 +02:00
belviskhoremk
55481b69c0 feat: add admin layout with sidebar nav and sign out 2026-08-12 11:06:40 +02:00
belviskhoremk
16d5503193 feat: add admin login page 2026-08-12 11:02:27 +02:00
belviskhoremk
5a7e0f2195 fix: rename mock variable to avoid ESLint react-hooks/rules-of-hooks false positive
Renamed useAdminAuthMock to adminAuthMock in ProtectedRoute.test.tsx to prevent
ESLint from flagging it as an invalid hook call. The 'use' prefix naming heuristic
was causing a react-hooks/rules-of-hooks rule violation, though it was not actually
a hook call context. This change maintains all test behavior while passing lint.
2026-08-12 10:59:40 +02:00
belviskhoremk
bc9efdbbd9 feat: add admin auth hook and protected route guard 2026-08-12 10:56:35 +02:00
belviskhoremk
d98d66b066 feat: add Supabase CLI scaffold and client SDK setup
Initializes the local Supabase project (supabase/config.toml) and adds
a thin src/lib/supabase.ts wrapper around @supabase/supabase-js that
reads connection details from Vite env vars, tested with a mocked
createClient.
2026-08-08 12:46:42 +00:00
belviskhoremk
7d8d89d0fc chore: remove Firebase backend code, replaced by Supabase (see 2026-08-08 spec)
Deleted all Firebase-specific files and directories:
- Configuration: .firebaserc, firebase.json, firestore.* files, storage.rules
- Cloud Functions: functions/ directory (package.json, source, config)
- Tests: firebase-tests/, vitest.rules.config.ts
- Scripts: scripts/seed.ts
- Source: src/lib/firebase.ts and .test.ts
- Environment: .env.example

Removed dependencies:
- firebase (main package)
- firebase-admin, firebase-tools (dev)
- @firebase/rules-unit-testing (dev)

Removed scripts:
- test:rules (Firebase emulator tests)
- seed (Firestore seed script)

Updated package.json via npm install (729 packages removed)
Updated .gitignore (removed functions/ entries)
Updated vite.config.ts (removed firebase-tests and functions exclusions)

All tests, builds, and lints pass cleanly. No Firebase references remain in
the codebase (only in historical spec documents in docs/).

Claude-Session: https://claude.ai/code/session_01NhxA4npAXAxmCDEJt6kxSH
2026-08-08 12:39:39 +00:00
belviskhoremk
257e47c2a1 feat: add Firebase client SDK setup
- Install firebase package
- Add environment variable configuration (.env.example)
- Create firebase.ts module that initializes Firebase app, Firestore, Auth, and Storage
- Add comprehensive test coverage for Firebase initialization with mocked Firebase modules
- Update .gitignore to exclude local environment files
2026-08-04 23:59:59 +00:00
belviskhoremk
42c0cf0f46 Redesign footer to fix layout and add visual hierarchy
The old flex layout with justify-content: space-between pushed its
two children to the far edges on wide screens, leaving a large dead
void in the middle. Replaced with a centered, max-width three-column
grid (brand, contact, navigate) with a script wordmark, gold section
labels, a divider separating it from the banner above, and a
copyright line.
2026-08-04 00:26:35 +00:00
belviskhoremk
1e9609ce23 Replace placeholder salon rules with Mira's real policy text
Kept in French to match how Mira actually messages clients. Updated
RuleIcon's icon set to match the real policy categories (deposit,
hair prep, extensions, lateness, guests, payment) in place of the
placeholder categories.
2026-08-03 23:48:54 +00:00
belviskhoremk
c4180a90bf fix: address final review findings — code-split Home's 3D bundle, bound card widths, fix Book step transition, cleanup
- Lazy-load Home in App.tsx so three/@react-three/fiber (~238 kB gzip) only ships to the
  Home route, not to Shop/Book/Rules.
- Add max-width: 260px to ProductCard/ServiceCard so real images won't collapse Home/Book
  grid layouts.
- Restore AnimatePresence mode="wait" in Book.tsx to eliminate the exit/enter double-stack
  during step transitions, updating the three Book.test.tsx assertions that raced the
  transition to findBy* queries.
- Hero3DScene: drop the meaningless "static" className string in favor of a dedicated
  data-testid, add aria-hidden to the reduced-motion fallback branch, and remove the
  unused @react-three/drei dependency (adding @types/three directly since it was only
  present transitively via drei).
2026-08-03 23:40:32 +00:00
belviskhoremk
585c77ba42 feat: add per-rule icons and staggered scroll-in to Rules page
Claude-Session: https://claude.ai/code/session_01NhxA4npAXAxmCDEJt6kxSH
2026-08-03 23:15:44 +00:00
belviskhoremk
801c4c028f feat: add step progress indicator and animated transitions to Book
Adds StepProgress component showing 4 labeled booking steps (Service,
Date & Time, Contact, Confirmed) and wires it into Book.tsx alongside
framer-motion AnimatePresence/motion.div step transitions.

Deviates from literal brief in two ways, both to avoid known issues:
- Guards the transition with useReducedMotion() (duration: 0 when
  prefers-reduced-motion is set), matching the fix pattern already
  applied to Home.tsx and Shop.tsx after prior review round-trips.
- Drops AnimatePresence's mode="wait" in favor of the default (sync)
  mode: with mode="wait", the incoming step's DOM doesn't mount until
  the outgoing step's exit animation finishes, which broke existing
  synchronous Testing Library queries in Book.test.tsx (real 250ms
  exit animations in jsdom, no fake timers). Sync mode mounts the new
  step immediately while both fade, preserving test synchronicity.

Also updates Book.test.tsx's confirmation assertion since the old
"You're booked!" heading is gone (page now always shows the shared
"Book an Appointment" heading and StepProgress; only the body swaps).
2026-08-03 23:11:29 +00:00
belviskhoremk
3f82e759cb fix: make Shop's sliding filter pill respect prefers-reduced-motion 2026-08-03 23:07:06 +00:00
belviskhoremk
7cd84f1c66 feat: animate Shop's active category filter with a sliding pill indicator 2026-08-03 23:04:38 +00:00
belviskhoremk
e24c1c7be7 fix: make Home's portrait parallax respect prefers-reduced-motion
The About section's portrait image now checks useReducedMotion() and
disables parallax scroll motion when the user has set OS-level reduced
motion preferences.

Claude-Session: https://claude.ai/code/session_01NhxA4npAXAxmCDEJt6kxSH
2026-08-03 23:02:21 +00:00
belviskhoremk
726893ab87 feat: add portrait with parallax, real product cards, and testimonial avatars to Home 2026-08-03 22:59:25 +00:00
belviskhoremk
c690c8292a feat: render service images with hover-zoom in ServiceCard
Claude-Session: https://claude.ai/code/session
2026-08-03 22:56:12 +00:00
belviskhoremk
8be76f77a4 feat: render product images with hover-zoom in ProductCard 2026-08-03 22:53:28 +00:00
belviskhoremk
12beaa990e feat: replace CSS hero motif with a real WebGL 3D braided-crown scene
Adds Hero3DScene, a procedurally-built rotating braid mesh rendered via
@react-three/fiber, replacing the flat CSS-circle Hero3D component. Falls
back to a static gradient circle under prefers-reduced-motion, matching the
old component's fallback behavior.

Also adds a ResizeObserver mock to the test setup, required by
@react-three/fiber's Canvas (via react-use-measure) in jsdom, which doesn't
implement it natively.
2026-08-03 22:49:24 +00:00
belviskhoremk
9f3e037cdc fix: address final review findings — Hero3D static class, InquiryModal a11y, date guards, shared email validation, README
Claude-Session: https://claude.ai/code/session_01NhxA4npAXAxmCDEJt6kxSH
2026-08-03 20:40:16 +00:00
belviskhoremk
251a6558fb polish: add responsive breakpoints for nav, home, and shop 2026-08-03 20:30:09 +00:00
belviskhoremk
cbcefbc559 feat: add Home landing page assembling all sections 2026-08-03 20:20:22 +00:00
belviskhoremk
7502c160eb feat: add Rules page 2026-08-03 20:15:56 +00:00