From 510847eac243be91c7c6ee628a16409071ace54e Mon Sep 17 00:00:00 2001 From: belviskhoremk Date: Thu, 13 Aug 2026 08:12:37 +0200 Subject: [PATCH] docs: add Phase 2D design spec (site content CMS + admin redesign) Claude-Session: https://claude.ai/code/session_01NhxA4npAXAxmCDEJt6kxSH --- ...-site-content-and-admin-redesign-design.md | 308 ++++++++++++++++++ 1 file changed, 308 insertions(+) create mode 100644 docs/superpowers/specs/2026-08-13-phase2d-site-content-and-admin-redesign-design.md diff --git a/docs/superpowers/specs/2026-08-13-phase2d-site-content-and-admin-redesign-design.md b/docs/superpowers/specs/2026-08-13-phase2d-site-content-and-admin-redesign-design.md new file mode 100644 index 0000000..9fd8434 --- /dev/null +++ b/docs/superpowers/specs/2026-08-13-phase2d-site-content-and-admin-redesign-design.md @@ -0,0 +1,308 @@ +# Phase 2D: Site Content Management & Admin Panel Redesign — Design + +## Purpose + +Two problems, addressed together because they both live in the admin panel: + +1. **Missing editability.** Several pieces of public-site content are hardcoded in + the frontend and can't be changed without a code deploy: the homepage hero + headline/tagline, the "About Mira" bio and portrait, testimonials, the + featured-products selection on the homepage, footer contact info/hours, and + the salon rules page. Mira needs to edit all of these from the admin panel. +2. **Unprofessional admin UI.** The Phase 2B admin panel was built with zero + design attention. It needs a visual pass: sidebar, tables, forms, buttons — + consistent spacing, typography, and color, reading as a professional + back-office tool. + +## Scope + +**In scope:** +- New `site_content` singleton table (hero, about, contact/hours) + one admin + edit page. +- New `testimonials` table (full CRUD + reorder) + one admin page. +- New `rules` table (full CRUD + reorder, icon picked from the existing + 6-icon set) replacing the hardcoded `src/data/rules.ts`, and its own admin + page. +- `featured` / `featured_order` columns added to the existing `products` + table, with featured toggle + reorder controls added to the existing + AdminProducts page. +- Public site changes: `Home.tsx`, `Footer.tsx`, and `Rules.tsx` read from + Supabase instead of hardcoded values/imports. +- Visual redesign of the admin panel: `AdminLayout` (sidebar/topbar), shared + table and form styling, buttons — applied to all 8 admin pages (5 existing + + 3 new). + +**Out of scope:** +- Uploadable/custom rule icons (stays a fixed 6-icon dropdown). +- Redesigning the public-facing site (already has design attention from an + earlier phase; only the 3 pages above get new *data*, not new *look*). +- Multi-admin support (unchanged from the single fixed admin UUID model). +- Any change to the booking/inquiry/availability logic from Phase 2C. + +## Data Model + +Four schema changes, one migration, following the project's established +public-read/admin-write RLS pattern (`(select auth.uid()) = ''`, +using the current admin UUID `2b88676b-f901-459a-9147-b914190c4f81`). + +### `site_content` (new, singleton — same pattern as `schedule_template`) + +```sql +create table public.site_content ( + id text primary key default 'default', + hero_headline text not null, + hero_tagline text not null, + about_bio text not null, + about_portrait_image text not null, + contact_email text not null, + contact_hours text not null +); +``` + +One row, `id = 'default'`. `about_portrait_image` and any avatar/logo-style +image fields store a Storage path, resolved client-side via the existing +`resolveImageUrl` helper — same convention as `products.image` and +`services.image`. + +RLS: `site_content_public_read` (select, `true`), `site_content_admin_write` +(all, admin UUID check). + +Seeded with the current hardcoded values (from `Home.tsx` and `Footer.tsx`) +so the migration doesn't blank the live site. + +### `testimonials` (new) + +```sql +create table public.testimonials ( + id uuid primary key default gen_random_uuid(), + quote text not null, + author text not null, + avatar_image text not null, + sort_order integer not null +); +``` + +RLS: `testimonials_public_read` (select, `true`), +`testimonials_admin_write` (all, admin UUID check). + +Seeded with the 3 current hardcoded testimonials (Amara T., Keisha R., Nadia +F.), `sort_order` 0/1/2. + +### `rules` (new, replaces `src/data/rules.ts`) + +```sql +create table public.rules ( + id uuid primary key default gen_random_uuid(), + title text not null, + body text not null, + icon_key text not null, + sort_order integer not null +); +``` + +`icon_key` is one of the 6 existing keys in `RuleIcon.tsx` +(`deposit`, `hair-prep`, `extensions`, `lateness`, `guests`, `payment`) — the +admin form offers these 6 as a dropdown, no free text. `RuleIcon` changes +from taking a `ruleId` prop to an `iconKey` prop and falls back to +`deposit`'s icon for any unrecognized value (matches its current fallback +behavior). + +RLS: `rules_public_read` (select, `true`), `rules_admin_write` (all, admin +UUID check). + +Seeded with the current 6 hardcoded rules and their existing icon mapping, +`sort_order` 0–5. + +### `products` (altered) + +```sql +alter table public.products add column featured boolean not null default false; +alter table public.products add column featured_order integer; +``` + +`featured_order` is `null` when `featured = false`. When `featured = true`, +it holds the display position among featured products (0-based, no gaps +enforced at the DB level — the admin UI is responsible for keeping it dense, +same as the existing `sort_order` columns above). No RLS change needed — +`products` already has public-read/admin-write policies covering all +columns. + +## Frontend: Public Site + +### `src/data/siteContent.ts` (new) + +```ts +export interface SiteContent { + heroHeadline: string + heroTagline: string + aboutBio: string + aboutPortraitImage: string + contactEmail: string + contactHours: string +} + +export async function fetchSiteContent(): Promise +``` + +Queries `site_content` where `id = 'default'`, maps snake_case → camelCase, +resolves `about_portrait_image` via `resolveImageUrl`. Matches the existing +`fetchProducts`/`fetchServices` shape and error-handling convention (throws +on Supabase error; callers already have a `try/catch` + `error` state +pattern established in `Home.tsx`/`Book.tsx`). + +### `src/data/testimonials.ts` (new) + +```ts +export interface Testimonial { + id: string + quote: string + author: string + avatarImage: string +} + +export async function fetchTestimonials(): Promise +``` + +Queries `testimonials` ordered by `sort_order`, resolves `avatar_image` via +`resolveImageUrl`. + +### `src/data/rules.ts` (rewritten) + +Currently exports a static `rules` array. Becomes: + +```ts +export interface RuleSection { + id: string + title: string + body: string + iconKey: string +} + +export async function fetchRules(): Promise +``` + +Queries `rules` ordered by `sort_order`. + +### `src/data/products.ts` (extended) + +`fetchProducts()`'s existing row mapping gains `featured: boolean` and +`featuredOrder: number | null` on the returned `Product` type. No new +function — `Home.tsx` filters/sorts the existing `fetchProducts()` result +client-side (`products.filter(p => p.featured).sort((a, b) => a.featuredOrder! - b.featuredOrder!)`), +replacing today's `products.filter((p) => p.inStock).slice(0, 3)`. + +### `Home.tsx` + +- Replace the hardcoded `TESTIMONIALS` array with `fetchTestimonials()`, + loaded alongside `fetchServices()`/`fetchProducts()` in the existing + `Promise.all` in the load effect. +- Replace the hardcoded hero headline/tagline and About-Mira bio/portrait + with fields from `fetchSiteContent()`, loaded in the same effect. +- Replace `products.filter((p) => p.inStock).slice(0, 3)` with the + featured-products filter/sort described above. +- Loading/error states extend the existing pattern (already has + `loading`/`error` state covering the services+products fetch — the two + new fetches join the same `Promise.all`). + +### `Footer.tsx` + +Currently a static component with no data fetching. Gains its own +`fetchSiteContent()` call (own `useEffect`/`useState`, matching this +project's established no-shared-cache convention — every page/component +that needs Supabase data fetches it independently; there's no existing +context/cache layer to hook into, and introducing one is out of scope) to +render `contact_email` and `contact_hours` instead of the hardcoded values. + +### `Rules.tsx` + +Replaces the static `import { rules } from '../data/rules'` with a +`fetchRules()` call in a `useEffect`, following the same +loading/error/render pattern as `Shop.tsx`. `RuleIcon` takes `iconKey` +instead of `ruleId`. + +## Frontend: Admin Panel + +### New page: `AdminContent` (`/admin/content`, nav label "Contenu") + +Single form editing the `site_content` singleton — same structural pattern +as `AdminSchedule`'s template-editing half (fetch on mount, controlled +inputs, one "Enregistrer" button, `saving`/`error` state). Fields: hero +headline, hero tagline, about bio (textarea), about portrait (image +upload, reusing the existing image-upload component/pattern from +`AdminProducts`/`AdminServices`), contact email, contact hours. + +### New page: `AdminTestimonials` (`/admin/testimonials`, nav label "Témoignages") + +Table + create/edit form, same shape as `AdminServices`: list existing +testimonials (quote, author, avatar thumbnail), create/edit/delete, plus +up/down buttons per row that swap `sort_order` with the adjacent row and +persist both rows in one update. (Deliberately not drag-and-drop — up/down +buttons are simpler to build, test, and use correctly for a handful of +rows, and this project has no existing drag-and-drop dependency.) + +### New page: `AdminRules` (`/admin/rules`, nav label "Règlements") + +Same shape as `AdminTestimonials`: table + create/edit form (title, body +textarea, icon dropdown showing all 6 icons rendered via `RuleIcon`), +up/down reorder. + +### `AdminProducts` (extended) + +Each row gains a "Vedette" checkbox/toggle. When checked, the row also +shows up/down reorder controls (same swap-and-persist mechanism as +testimonials/rules) scoped to just the featured subset. Unchecking clears +`featured_order` to `null`. + +### `AdminLayout` / `AdminApp` (extended) + +`NAV_ITEMS` gains 3 entries (Contenu, Témoignages, Règlements) alongside +the existing 5. `AdminApp`'s route table gains 3 matching `` entries. + +## Redesign + +Scope: **CSS, layout, and shared component structure only** — no page's +data-fetching logic changes as part of the redesign (only as part of the +content-editability work described above, where a page is touched for that +reason anyway). + +- **`AdminLayout`**: proper sidebar (fixed width, clear active-state + styling, grouped nav), consistent page-content padding/max-width, a + simple topbar or page-title convention so every admin page has a clear + heading area. +- **Shared table styling**: consistent row height, borders, hover state, + action-button alignment — applied to every admin page with a table + (Produits, Services, Réservations, Demandes, and the 2 new list pages). +- **Shared form styling**: consistent label/input spacing, button styling + (primary "Enregistrer"/"Ajouter" actions vs. secondary "Annuler"/ + "Supprimer"), validation-error display — applied to every admin page with + a form. +- **Color/type system**: a small, deliberate palette and type scale distinct + from the public site's editorial fonts/colors — functional and + professional rather than decorative. Reuses CSS custom properties/module + pattern already established in this codebase (CSS Modules throughout); + introduces admin-specific tokens rather than reusing the public site's. + +This does not introduce a new CSS framework or design-system dependency — +it's a styling pass within the project's existing CSS Modules convention. + +## Testing + +Same conventions as every prior phase: Vitest + React Testing Library for +components/hooks/data functions (mocking `../lib/supabase` directly, not +`vi.importActual`), pgTAP for the new RLS policies (add +`site_content`/`testimonials`/`rules` read/write cases and the `products` +featured-column cases to `supabase/tests/database/rls.test.sql`, matching +the existing per-table pattern in that file). + +## Migration & Rollout + +One new migration file adds the 3 tables + 2 `products` columns + RLS +policies + seed data (seeding directly in the migration, since these are +fixed initial values the site currently already shows — not sample/test +data, so `scripts/seed.ts` is not the right place; `scripts/seed.ts` +already only owns products/services/schedule). + +Local verification (`supabase db reset` + `supabase test db`) before +pushing to the live project, then `supabase db push`, matching every prior +phase's rollout process. Frontend changes go through the same +build/lint/test/deploy verification as Phase 2C.