diff --git a/PROGRESS.md b/PROGRESS.md index f95ade7..90036a5 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -9,7 +9,7 @@ - Image optimization pipeline (AVIF/WebP/JPEG responsive variants) - Shop pages (home, collections, products, cart, about, contact, error, delivery, privacy, terms) - Mobile-first design with bottom navigation -- 1284 tests passing, 100% PageSpeed score +- 1309 tests passing, 100% PageSpeed score - SQLite production tuning (IMMEDIATE transactions, mmap, WAL journal limit) - Variant selector with color swatches and size buttons - Session-based cart with real variant data (add/remove/quantity, cross-tab sync) @@ -458,7 +458,7 @@ See: [plan](docs/plans/shipping-sync.md) for implementation details See: [docs/plans/analytics-v2.md](docs/plans/analytics-v2.md) for v2 plan ### Page Editor -**Status:** In progress — Stage 5 of 9 complete, 1284 tests +**Status:** In progress — Stage 7 of 9 complete, 1320 tests Database-driven page builder. Every page is a flat list of blocks stored as JSON — add, remove, reorder, and edit blocks on any page. One generic renderer for all pages (no page-specific render functions). Portable blocks (hero, featured_products, image_text, etc.) work on any page. Page-specific blocks (product_hero, cart_items, etc.) are restricted to their native page. Block data loaders dynamically load data based on which blocks are on the page. ETS-cached page definitions. Mobile-first admin editor with live preview, undo/redo, accessible reordering (no drag-and-drop), inline settings forms, and "reset to defaults". CSS-driven page layout (not renderer-driven). @@ -468,17 +468,19 @@ Database-driven page builder. Every page is a flat list of blocks stored as JSON 3. ~~Wire simple pages — Home, Content (x4), Contact, Error~~ ✅ 4. ~~Wire shop pages — Collection, PDP, Cart, Search~~ ✅ 5. ~~Wire order pages + theme preview — CheckoutSuccess, Orders, OrderDetail, theme editor~~ ✅ -6. **Next →** Admin editor — page list + block management (reorder, add, remove, duplicate, save) -7. Admin editor — inline block settings editing -8. Live preview — split layout with real-time preview +6. ~~Admin editor — page list + block management~~ ✅ (`660fda9`) +7. ~~Admin editor — inline block settings editing~~ ✅ +8. **Next →** Live preview — split layout with real-time preview 9. Undo/redo + polish — history stacks, keyboard shortcuts, animations **Key files created:** - `lib/berrypod/pages.ex` — context (CRUD + cache + load_block_data) - `lib/berrypod/pages/` — Page schema, BlockTypes (26 types), Defaults (14 pages), PageCache (ETS) - `lib/berrypod_web/page_renderer.ex` — generic renderer dispatching blocks to existing shop components +- `lib/berrypod_web/live/admin/pages/` — Index (page list) + Editor (block management) - `test/berrypod/pages_test.exs` — 34 tests - `test/berrypod_web/page_renderer_test.exs` — 18 tests +- `test/berrypod_web/live/admin/pages_test.exs` — 36 tests See: [docs/plans/page-builder.md](docs/plans/page-builder.md) for full plan diff --git a/assets/css/admin/components.css b/assets/css/admin/components.css index 21419a9..7b10760 100644 --- a/assets/css/admin/components.css +++ b/assets/css/admin/components.css @@ -1163,9 +1163,6 @@ } .block-card { - display: flex; - align-items: center; - gap: 0.5rem; padding: 0.5rem 0.75rem; border: 1px solid var(--t-border-default); border-radius: 0.5rem; @@ -1318,4 +1315,47 @@ font-size: 0.8125rem; } +/* Block card header + expanded state */ + +.block-card-header { + display: flex; + align-items: center; + gap: 0.5rem; + width: 100%; +} + +.block-card-expanded { + background: var(--t-surface-sunken); +} + +.block-edit-btn-active { + color: var(--t-accent); +} + +/* Block settings panel */ + +.block-card-settings { + padding: 0.75rem 0.75rem 0.25rem; + padding-left: 2.75rem; + border-top: 1px solid var(--t-border-default); +} + +.block-settings-fields { + display: flex; + flex-direction: column; + gap: 0.625rem; +} + +.block-settings-json { + font-family: ui-monospace, "SF Mono", "Cascadia Mono", Menlo, monospace; + font-size: 0.75rem; + opacity: 0.7; +} + +.block-settings-hint { + font-size: 0.75rem; + color: color-mix(in oklch, var(--t-text-primary) 50%, transparent); + margin-top: 0.25rem; +} + } /* @layer admin */ diff --git a/docs/plans/page-builder.md b/docs/plans/page-builder.md index 62105bb..c7bc426 100644 --- a/docs/plans/page-builder.md +++ b/docs/plans/page-builder.md @@ -1,6 +1,6 @@ # Page builder plan -Status: In progress (Stage 5 complete) +Status: In progress (Stage 7 complete) ## Context @@ -661,44 +661,37 @@ Each stage is a commit point. Tests pass, all pages work, nothing is broken. Pic --- -### Stage 6: Admin editor — page list + block management +### Stage 6: Admin editor — page list + block management ✅ -**Goal:** admin can see all pages and reorder/add/remove blocks. No inline editing yet. Save persists to DB. +**Status:** Complete (commit `660fda9`) -- [ ] Create `Admin.Pages` LiveView with `:index` and `:edit` live_actions -- [ ] Routes: `/admin/pages` and `/admin/pages/:slug` -- [ ] Add "Pages" nav link to admin sidebar -- [ ] Page list: grouped cards (Marketing, Legal, Shop, Order, System) -- [ ] Block list: ordered cards with position number, icon, name -- [ ] Move up/down buttons with accessible UX (focus follows, ARIA live region, disabled at edges) -- [ ] Remove block button -- [ ] "+ Add block" picker showing allowed blocks for this page, with search/filter -- [ ] Duplicate block button -- [ ] "Reset to defaults" with confirmation -- [ ] Save → persist to DB, invalidate cache, flash -- [ ] `@dirty` flag + DirtyGuard hook for unsaved changes warning -- [ ] Admin CSS for page editor -- [ ] Integration tests: list pages, reorder, add, remove, duplicate, reset, save - -**Commit:** `add admin page editor with block reordering and management` - -**Verify:** `mix test` passes, can reorder blocks on home page, save, refresh shop — layout changed +- [x] `Admin.Pages.Index` — page list with 5 groups (Marketing, Legal, Shop, Orders, System), icons, block counts +- [x] `Admin.Pages.Editor` — block cards with position numbers, icons, names +- [x] Routes: `/admin/pages` and `/admin/pages/:slug`, "Pages" nav link in admin sidebar +- [x] Move up/down with ARIA live region announcements, disabled at edges +- [x] Remove block (with confirmation), duplicate block (copies settings) +- [x] "+ Add block" picker with search/filter, enforces `allowed_on` per page +- [x] Save → persist to DB, invalidate cache, flash. Reset to defaults with confirmation +- [x] `@dirty` flag + DirtyGuard JS hook for unsaved changes warning +- [x] Admin CSS for page list, block cards, block picker modal +- [x] 25 integration tests covering list, reorder, add, remove, duplicate, reset, save, dirty flag +- [x] Regenerated admin icons (81 rules) with `@layer admin` wrapping fix in mix task +- [x] Added `:key` to renderer block loop for correct LiveView diffing +- [x] 1309 tests pass, `mix precommit` clean --- -### Stage 7: Admin editor — inline block editing +### Stage 7: Admin editor — inline block editing ✅ -**Goal:** admin can edit block settings (hero text, product count, etc). Full editing workflow complete. +**Status:** Complete -- [ ] Inline settings form generated from `settings_schema` -- [ ] Form field types: text, textarea, select, number, json -- [ ] Cancel/Apply on each block's settings form -- [ ] Block collapse/expand (icon + name one-liner vs expanded card) -- [ ] Integration tests: edit hero title, save, verify on shop - -**Commit:** `add inline block settings editing to page editor` - -**Verify:** `mix test` passes, edit home hero title in admin, save, see it on the shop +- [x] Inline settings form generated from `settings_schema` — text, textarea, select, number, json (read-only) +- [x] Block expand/collapse with `@expanded` MapSet, edit button (cog icon) on blocks with settings +- [x] `phx-change` updates working state instantly, no Cancel/Apply (page-level Save/Reset handles it) +- [x] Number type coercion (form params → integers), schema defaults merged for missing keys +- [x] Full ARIA: `aria-expanded`, `aria-controls`, live region announcements, unique field IDs +- [x] Debouncing: 300ms on text/textarea, blur on select/number +- [x] 11 new tests (36 total in pages_test), 1320 tests total, `mix precommit` clean --- diff --git a/lib/berrypod_web/live/admin/pages/editor.ex b/lib/berrypod_web/live/admin/pages/editor.ex index 1756f05..31c975c 100644 --- a/lib/berrypod_web/live/admin/pages/editor.ex +++ b/lib/berrypod_web/live/admin/pages/editor.ex @@ -19,6 +19,7 @@ defmodule BerrypodWeb.Admin.Pages.Editor do |> assign(:dirty, false) |> assign(:show_picker, false) |> assign(:picker_filter, "") + |> assign(:expanded, MapSet.new()) |> assign(:live_region_message, nil)} end @@ -109,6 +110,58 @@ defmodule BerrypodWeb.Admin.Pages.Editor do end end + def handle_event("toggle_expand", %{"id" => block_id}, socket) do + expanded = socket.assigns.expanded + block = Enum.find(socket.assigns.blocks, &(&1["id"] == block_id)) + block_name = block_display_name(block) + + {new_expanded, action} = + if MapSet.member?(expanded, block_id) do + {MapSet.delete(expanded, block_id), "collapsed"} + else + {MapSet.put(expanded, block_id), "expanded"} + end + + {:noreply, + socket + |> assign(:expanded, new_expanded) + |> assign(:live_region_message, "#{block_name} settings #{action}")} + end + + def handle_event("update_block_settings", params, socket) do + block_id = params["block_id"] + new_settings = params["block_settings"] || %{} + + # Find the block and its schema to coerce types + block = Enum.find(socket.assigns.blocks, &(&1["id"] == block_id)) + + if block do + schema = + case BlockTypes.get(block["type"]) do + %{settings_schema: s} -> s + _ -> [] + end + + coerced = coerce_settings(new_settings, schema) + + new_blocks = + Enum.map(socket.assigns.blocks, fn b -> + if b["id"] == block_id do + Map.put(b, "settings", Map.merge(b["settings"] || %{}, coerced)) + else + b + end + end) + + {:noreply, + socket + |> assign(:blocks, new_blocks) + |> assign(:dirty, true)} + else + {:noreply, socket} + end + end + def handle_event("show_picker", _params, socket) do {:noreply, socket @@ -225,6 +278,7 @@ defmodule BerrypodWeb.Admin.Pages.Editor do block={block} idx={idx} total={length(@blocks)} + expanded={@expanded} />