Commit Graph

80 Commits

Author SHA1 Message Date
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
bcc606db28 feat: allow bookings.status to be marked completed 2026-08-12 10:51:17 +02:00
belviskhoremk
acc4b02a20 feat: add public-read, admin-write RLS policies for salon-images bucket
Adds pgTAP tests and migration to create the salon-images storage bucket
with RLS policies allowing public read access and admin-only write access.
Tests verify anon users can read but not upload, and admin can upload.
2026-08-12 10:44:44 +02:00
belviskhoremk
3c7bb59421 docs: add Phase 2B admin panel implementation plan 2026-08-12 10:38:20 +02:00
belviskhoremk
ab0e338d2b docs: add Phase 2B admin panel design spec
Claude-Session: https://claude.ai/code/session_01NhxA4npAXAxmCDEJt6kxSH
2026-08-12 10:28:03 +02:00
belviskhoremk
b4f7c1dce3 fix: exclude nested worktree directories from vitest
Running the suite from the main checkout was picking up each
worktree's full copy of src/ under .claude/worktrees/, silently
running every test twice. The existing supabase/functions/** exclude
only matched at repo root, so it also missed the nested worktree's
copy of that path.

Claude-Session: https://claude.ai/code/session_01NhxA4npAXAxmCDEJt6kxSH
2026-08-12 10:19:15 +02:00
belviskhoremk
35e929c5e1 fix: harden Edge Function notification path per final review
Fixes issues raised by the whole-branch review before merge:

- Critical: the previous migration hardcoded the live production
  functions URL and anon key as the committed default, so every local
  `supabase db reset` silently pointed dev/test triggers at the real
  project. Adding a new migration instead of editing the already-applied
  one (checksum/drift safety), with local dev values as the committed
  default; production values are applied out-of-band, never committed.

- Important: verify_jwt=true plus sending the anon key as a bearer
  token didn't actually stop anyone, since the anon key is public
  (ships in the frontend bundle) — confirmed empirically, a request
  with a valid anon key but no shared secret now gets a real 401.
  Added a random shared secret known only to the database and the
  Edge Functions' secret store.

- Important: notification emails interpolated raw contact fields into
  HTML with no escaping. Added escapeHtml() and tests proving injected
  markup renders as literal text, not live markup.

- Important: the URL/key/secret functions moved from public to a
  private schema with EXECUTE revoked from anon/authenticated, closing
  an unintended PostgREST RPC endpoint (verified 404 after the fix).

Also adds a test:functions npm script now that Deno is confirmed
available and the shared email builders' tests pass (12 total incl.
the new escaping cases).

Claude-Session: https://claude.ai/code/session_01NhxA4npAXAxmCDEJt6kxSH
2026-08-12 10:15:34 +02:00
belviskhoremk
8c1522a2b4 chore: deploy Phase 2A backend to live Supabase project (Task 9)
Substitutes the real admin UID everywhere the placeholder appeared,
points the Edge Functions base URL at the deployed project, and adds
production JWT verification: trigger functions now send the anon key
as a bearer token so verify_jwt can stay enabled instead of leaving
the functions open to unauthenticated calls from the public internet.

Also adds scripts/create-admin.ts (one-off helper used to create
Mira's admin auth user), documents the seed script's non-VITE env
vars in .env.example, and closes a gitignore gap where plain .env
wasn't excluded alongside .env.local.

Claude-Session: https://claude.ai/code/session_01NhxA4npAXAxmCDEJt6kxSH
2026-08-12 09:59:33 +02:00
belviskhoremk
1181d9a3be feat: add Supabase seed script for products, services, and default schedule
The brief's literal script copies source fields verbatim, but src/data
uses camelCase (inStock, durationMinutes, priceFrom) while the DB
columns are snake_case (in_stock, duration_minutes, price_from), so
that version fails with PGRST204. Map fields explicitly instead of
spreading the rest.
2026-08-08 13:35:14 +00:00
belviskhoremk
27710e18a9 fix: extract Edge Functions base URL into a single shared SQL function
notify_booking_created() and notify_inquiry_created() each held their own
literal copy of the local Edge Functions base URL. Introduce
public.edge_functions_base_url() as the single source of truth so Task 9's
production deploy only needs to update one function definition instead of
two independent literals.
2026-08-08 13:31:07 +00:00
belviskhoremk
7e51c2df37 feat: add on-booking-created and on-inquiry-created Edge Functions with DB-trigger wiring
Wires pg_net-based database triggers on bookings/inquiries to invoke the two
Edge Functions on insert. Two empirically-discovered fixes over the brief's
literal SQL were required to make this work locally:

- ALTER DATABASE ... SET for a custom GUC needs superuser, but the local
  migration role (postgres) isn't superuser in this Supabase CLI stack (only
  supabase_admin is) - inlined the base URL as a literal constant in each
  trigger function instead.
- pg_net's http_post lives in schema `net`, not `extensions.net` - fixed the
  function calls accordingly.

Also disabled verify_jwt for both functions in config.toml since they're
invoked by pg_net (no Authorization header), not by end-user clients.

Claude-Session: https://claude.ai/code/session_01NhxA4npAXAxmCDEJt6kxSH
2026-08-08 13:25:04 +00:00
belviskhoremk
6e615113a7 feat: add Edge Functions workspace with tested email builders
Adds supabase/functions/ (deno.json, _shared/email.ts) with pure
buildBookingNotificationEmail/buildInquiryNotificationEmail functions
for Task 6's Edge Functions, tested via Deno's test runner. Excludes
supabase/functions/** from the root Vitest config, which otherwise
tried to resolve the jsr: import and failed.
2026-08-08 13:12:30 +00:00
belviskhoremk
aacb9dca12 fix: scope GRANT statements to match actual RLS-permitted access per role
Replace the blanket select/insert/update/delete grant to anon/authenticated/
service_role with privilege-scoped grants: anon/authenticated get select-only
on the public-read tables plus insert-only on bookings/inquiries; full CRUD
is reserved for authenticated (narrowed to the admin UID by RLS) and
service_role. Table grants are now a real second layer of defense instead of
mirroring RLS. Updates two pgTAP assertions whose blocking mechanism moved
from RLS-filtered-empty to grant-level permission-denied, and adds a new
assertion proving anon cannot UPDATE bookings.

Claude-Session: https://claude.ai/code/session_01NhxA4npAXAxmCDEJt6kxSH
2026-08-08 13:08:55 +00:00
belviskhoremk
5e3a77fc70 feat: add booked_slots sync trigger and pgTAP RLS/trigger tests
Includes explicit GRANTs for anon/authenticated/service_role on all
public tables: the Supabase CLI's current default no longer
auto-exposes new tables, so RLS policies from the initial schema were
unreachable without them (every request failed at the privilege check
before RLS was evaluated).
2026-08-08 13:01:08 +00:00
belviskhoremk
6c1a8a92c5 feat: add initial database schema, indexes, and RLS policies 2026-08-08 12:54:42 +00: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
0dbdee9f95 Add implementation plan for Phase 2 sub-project A on Supabase 2026-08-08 12:34:57 +00:00
belviskhoremk
de845ef6e5 Add design spec for Phase 2 sub-project A on Supabase (replaces Firebase design) 2026-08-08 12:29:21 +00:00
belviskhoremk
5821b630f6 fix: address final review findings — lint, Node engine, remaining-work doc, create-rule validation 2026-08-05 00:35:50 +00:00
belviskhoremk
9ec17d36f9 fix: exclude functions/ sub-project from the root Vitest run 2026-08-05 00:26:48 +00:00
belviskhoremk
95485ea12b feat: add Firestore seed script for products, services, and default schedule 2026-08-05 00:23:19 +00:00
belviskhoremk
5dff5346fe feat: add onBookingCreated and onInquiryCreated Cloud Functions 2026-08-05 00:19:38 +00:00
belviskhoremk
9c47805f37 feat: add Cloud Functions scaffold with tested email builders 2026-08-05 00:15:45 +00:00
belviskhoremk
ffd31ca1da test: add Firestore/Storage security rules tests
Adds emulator-backed rules-unit tests verifying the Firestore rules
committed in Task 3, including the global constraint that
bookings/inquiries are never publicly readable. Excludes
firebase-tests/ from the default vitest run since it needs the
emulator (via `npm run test:rules`) rather than jsdom.
2026-08-05 00:09:57 +00:00
belviskhoremk
88e99ae53e feat: add Firebase project config and security rules 2026-08-05 00:03:30 +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
ca1146a3a8 Add implementation plan for Phase 2 sub-project A: backend foundation 2026-08-04 23:48:03 +00:00
belviskhoremk
c06891eebb Add design spec for Phase 2 sub-project A: backend foundation 2026-08-04 23:39:33 +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
3d0a275d67 Add real product, style, portrait, and testimonial photography
Sourced from Pexels (free for commercial use, no attribution
required) to complete the image wiring added in the visual overhaul.
Fills the 18 image slots that were previously deferred (products,
signature styles, About Mira portrait, testimonial avatars).
2026-08-04 00:07:43 +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