Add theme editing to the existing PageEditorHook, enabling on-site theme customisation alongside page editing. The editor panel now has three tabs (Page, Theme, Settings) and can be collapsed while keeping editing state intact. - Add theme editing state and event handlers to PageEditorHook - Add 3-tab UI with tab switching logic - Add transparent overlay for click-outside dismiss - Add mobile drag-to-resize with height persistence - Fix animation replay on drag release (has-dragged class) - Preserve panel height across LiveView re-renders - Default to Page tab on editable pages, Theme otherwise - Show unsaved changes indicator on FAB when panel collapsed - Fix handle_event grouping warning in admin theme Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
9.1 KiB
Unified On-Site Editing Mode
Status: In Progress (Phase 1-2 complete)
Replace the separate admin theme editor (/admin/theme) with an on-site editing experience. When the admin clicks "Theme" or "Edit page", they're taken to the actual shop where a sliding panel allows them to edit either page content or theme settings — seeing changes live on the real site.
Current Architecture
The codebase already has a robust pattern for on-site editing via the PageEditorHook:
PageEditorHookattaches to all shop pages, initializing ~15 editor-related assignseditor_sheetcomponent inShopComponents.Layoutrenders a FAB + sliding panelPageRendererconditionally renders editor UI when@is_adminis true- Changes preview instantly; state persists across the session
The theme editor currently lives at /admin/theme with its own LiveView and fake preview.
Approach
Extend the existing editor infrastructure to support both page editing and theme editing:
- Add theme editing state to the existing hook system
- Add a tab switcher to the editor panel (Page | Theme | Settings)
- Extract theme editor UI into a reusable component
- Route
/admin/themeto shop homepage with?edit=themeparam
User Flow
- Admin clicks "Theme" in admin sidebar → redirects to
/?edit=theme - Shop homepage loads with theme editing panel open
- Admin can switch between "Page", "Theme", and "Settings" tabs
- Admin can navigate to other shop pages while keeping editor open
- "Done" or "×" button returns to admin or closes panel
Files to Modify
| File | Change |
|---|---|
lib/berrypod_web/page_editor_hook.ex |
Add theme/settings editing assigns, tab state, event handlers |
lib/berrypod_web/page_renderer.ex |
Add tab UI to editor_sheet, dispatch to theme/page/settings content |
lib/berrypod_web/components/shop_components/layout.ex |
Update editor_sheet for 3-tab support, add new keys to layout_keys |
lib/berrypod_web/components/shop_components/theme_editor.ex |
New - Extracted theme editor panel component |
lib/berrypod_web/components/shop_components/settings_editor.ex |
New - Page/shop settings panel component |
lib/berrypod_web/router.ex |
Redirect /admin/theme to /?edit=theme |
assets/css/shop/editor.css |
New - Editor panel styling (extract from admin) |
assets/js/hooks/theme_editor.js |
New - ColorSync and other theme editor hooks |
Implementation
Phase 1: Theme Editor Hook Integration
Add theme editing state to PageEditorHook:
# New assigns in on_mount
:theme_editing -> false
:theme_settings -> nil (loaded when theme_editing activates)
:theme_active_preset -> nil
:theme_logo_image -> nil
:theme_header_image -> nil
:theme_icon_image -> nil
:theme_contrast_warning -> :ok
:theme_customise_open -> false
# New event handlers
"theme_*" events -> handled by attach_hook similar to editor_* events
Phase 2: Editor Panel Tabs
Add three tabs to the editor panel:
| Tab | Content | When available |
|---|---|---|
| Page | Block editor for custom pages | Only on editable pages (home, about, custom CMS pages) |
| Theme | Global styles, mood, typography, branding | Always |
| Settings | Page-specific settings (SEO, slug) or global shop settings | Always (content varies by page) |
Update editor_sheet component:
<div class="editor-tabs">
<button
class={["editor-tab", @editor_active_tab == :page && "active"]}
phx-click="editor_set_tab"
phx-value-tab="page"
disabled={!@page}
>
Page
</button>
<button
class={["editor-tab", @editor_active_tab == :theme && "active"]}
phx-click="editor_set_tab"
phx-value-tab="theme"
>
Theme
</button>
<button
class={["editor-tab", @editor_active_tab == :settings && "active"]}
phx-click="editor_set_tab"
phx-value-tab="settings"
>
Settings
</button>
</div>
<%= case @editor_active_tab do %>
<% :page -> %>
<.editor_sheet_content ... />
<% :theme -> %>
<.theme_editor_content ... />
<% :settings -> %>
<.settings_editor_content ... />
<% end %>
Settings tab content varies by context:
- On custom CMS pages: Page title, slug, SEO (meta title, description)
- On product pages: Read-only product info, link to admin product editor
- On collection pages: Collection settings, link to admin
- Global fallback: Shop name, description, favicon settings
Phase 3: Extract Theme Editor Component
Create lib/berrypod_web/components/shop_components/theme_editor.ex:
- Extract the sidebar content from current
/admin/themetemplate - Reuse same event names (
update_setting,toggle_setting,apply_preset, etc.) - Support image uploads via
allow_uploadpassed from hook
Key sections to extract:
- Presets grid
- Mood/typography/shape/density selectors
- Logo upload + settings
- Header background upload + settings
- Accent color pickers
- Customise accordion (advanced settings)
Phase 3b: Create Settings Editor Component
Create lib/berrypod_web/components/shop_components/settings_editor.ex:
Content varies by page type:
| Page type | Settings shown |
|---|---|
| Custom CMS page | Page title, slug, visibility, SEO meta |
| Home page | Shop name, description, SEO defaults |
| Product page | Read-only product name, link to admin product editor |
| Collection page | Read-only collection name, link to admin |
| Cart/checkout | Shop policies, checkout settings link |
Common sections:
- Page SEO (meta title, description, social image)
- Favicon settings (short name, icon upload)
- Shop identity (name, description)
Phase 4: Image Upload Handling
Image uploads need special handling since we're in a hook, not a LiveView:
Option A: Delegate to the parent LiveView
- Pass
@uploadsfrom the LiveView through assigns - Hook events trigger upload handling in the LiveView
- Cleanest approach, matches existing pattern
Option B: Use a LiveComponent for uploads
- Wrap upload sections in a stateful LiveComponent
- Component handles its own uploads
- More isolated but adds complexity
Recommended: Option A — follow the pattern the page editor already uses.
Phase 5: URL-Based Mode Activation
Handle ?edit=theme query param:
# In PageEditorHook handle_params hook
defp handle_params_hook(params, _uri, socket) do
socket =
case params["edit"] do
"theme" -> enter_theme_edit_mode(socket)
"page" -> enter_page_edit_mode(socket)
_ -> socket
end
{:cont, socket}
end
Phase 6: Admin Routing
Update router to redirect theme link:
# In admin scope, redirect /admin/theme to shop
get "/theme", Plug.Redirect, to: "/?edit=theme"
Or keep the admin theme page as a fallback/redirect:
live "/theme", Admin.Theme.Redirect # Simple LiveView that redirects
State Management
Theme editing state persists across navigation via hook attach:
attach_hook(:theme_params, :handle_params, &handle_params_hook/3)tracks URL- Theme settings stored in socket assigns
- CSS regenerated and assigned to
@generated_csson each change - ThemeHook already loads CSS; we just update the assign
Dirty tracking:
- Theme changes save immediately (current behavior) OR
- Add "unsaved" state like page editor (better UX but more work)
Recommendation: Keep immediate save for now (it's the existing behavior and simpler).
CSS Considerations
The editor panel uses admin CSS classes. Options:
- Extract shared styles into
shop/editor.cssloaded on all shop pages - Namespace admin styles so they work in shop context
- Use inline styles (not recommended)
Recommended: Create assets/css/shop/editor.css with editor panel styles, include it in shop layout when @is_admin.
What Happens to /admin/theme?
Options:
- Remove it entirely — redirects to
/?edit=theme - Keep as fallback — simple page that redirects or links to on-site editor
- Keep for non-visual settings — things like favicon short name, SEO defaults
Recommended: Redirect to /?edit=theme for now. Can always add back a minimal settings page later if needed.
Verification
- Click "Theme" in admin → lands on shop homepage with theme panel open
- Change mood → see colors update instantly on real site
- Upload logo → appears in real header immediately
- Navigate to product page → theme panel stays open
- Switch to "Page" tab on homepage → shows page block editor
- Switch to "Page" tab on product page → tab disabled (no editable blocks)
- Click "Done" → returns to admin dashboard
- Mobile: panel slides up from bottom, fully usable
Phased Rollout
MVP (Phases 1-3)
- Theme editing works on-site
- Basic tab UI (Page | Theme | Settings)
- All current theme settings functional
- No image uploads yet (use existing images)
Full Feature (Phases 4-6)
- Image uploads working
- URL-based mode activation
- Admin redirect in place
- Polish and edge cases
Future Enhancements (Not in Scope)
- Undo/redo for theme changes
- "Preview without saving" mode
- Theme presets preview before applying
- Multi-page preview from single location