Document the architecture and implementation phases for transforming static page templates into a database-driven, customizable page builder with visual editing capabilities. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
400 lines
16 KiB
Markdown
400 lines
16 KiB
Markdown
# Page Builder Feature Plan
|
|
|
|
## Overview
|
|
|
|
Transform the static page templates into a database-driven, customizable page builder. Users will be able to add, remove, reorder, and configure page sections through a visual editor similar to the existing theme editor.
|
|
|
|
## Goals
|
|
|
|
1. Allow customization of page layouts without code changes
|
|
2. Provide real-time preview while editing
|
|
3. Support responsive editing (works on mobile and desktop)
|
|
4. Maintain theme consistency with existing design system
|
|
|
|
## Architecture
|
|
|
|
### Current State
|
|
|
|
```
|
|
PageTemplates/
|
|
├── home.html.heex # Static: hero → category_nav → featured_products → image_text
|
|
├── collection.html.heex
|
|
├── pdp.html.heex
|
|
├── cart.html.heex
|
|
├── about.html.heex
|
|
├── contact.html.heex
|
|
└── error.html.heex
|
|
```
|
|
|
|
### Future State
|
|
|
|
```
|
|
Database (page_layouts table)
|
|
├── page: "home"
|
|
│ └── sections: [
|
|
│ {type: "hero", order: 1, settings: {...}},
|
|
│ {type: "category_nav", order: 2, settings: {...}},
|
|
│ {type: "featured_products", order: 3, settings: {...}},
|
|
│ {type: "image_text", order: 4, settings: {...}}
|
|
│ ]
|
|
├── page: "about"
|
|
│ └── sections: [...]
|
|
└── ...
|
|
|
|
PageRenderer module
|
|
├── render_page/2 # Renders page from layout + data
|
|
└── render_section/1 # Dispatches to component by type
|
|
```
|
|
|
|
## Data Model
|
|
|
|
### PageLayout Schema
|
|
|
|
```elixir
|
|
defmodule SimpleshopTheme.Content.PageLayout do
|
|
use Ecto.Schema
|
|
|
|
schema "page_layouts" do
|
|
field :page_type, :string # "home", "about", "collection", etc.
|
|
field :name, :string # Display name for the layout
|
|
field :is_default, :boolean, default: false
|
|
|
|
has_many :sections, SimpleshopTheme.Content.PageSection
|
|
|
|
timestamps()
|
|
end
|
|
end
|
|
```
|
|
|
|
### PageSection Schema
|
|
|
|
```elixir
|
|
defmodule SimpleshopTheme.Content.PageSection do
|
|
use Ecto.Schema
|
|
|
|
schema "page_sections" do
|
|
field :section_type, :string # "hero", "featured_products", etc.
|
|
field :order, :integer
|
|
field :settings, :map # JSON settings for the section
|
|
field :enabled, :boolean, default: true
|
|
|
|
belongs_to :page_layout, SimpleshopTheme.Content.PageLayout
|
|
|
|
timestamps()
|
|
end
|
|
end
|
|
```
|
|
|
|
### Section Types Registry
|
|
|
|
```elixir
|
|
defmodule SimpleshopTheme.Content.SectionTypes do
|
|
@sections %{
|
|
"hero" => %{
|
|
name: "Hero Banner",
|
|
component: &SimpleshopThemeWeb.ShopComponents.hero_section/1,
|
|
settings_schema: %{
|
|
title: %{type: :string, default: "Welcome"},
|
|
description: %{type: :string, default: ""},
|
|
cta_text: %{type: :string, default: "Shop now"},
|
|
cta_page: %{type: :string, default: "collection"},
|
|
background: %{type: :select, options: [:default, :sunken], default: :default}
|
|
},
|
|
allowed_on: [:home, :about, :contact, :error]
|
|
},
|
|
"featured_products" => %{
|
|
name: "Featured Products",
|
|
component: &SimpleshopThemeWeb.ShopComponents.featured_products_section/1,
|
|
settings_schema: %{
|
|
title: %{type: :string, default: "Featured products"},
|
|
product_count: %{type: :integer, default: 8}
|
|
},
|
|
allowed_on: [:home]
|
|
},
|
|
"category_nav" => %{
|
|
name: "Category Navigation",
|
|
component: &SimpleshopThemeWeb.ShopComponents.category_nav/1,
|
|
settings_schema: %{},
|
|
allowed_on: [:home]
|
|
},
|
|
"image_text" => %{
|
|
name: "Image + Text Block",
|
|
component: &SimpleshopThemeWeb.ShopComponents.image_text_section/1,
|
|
settings_schema: %{
|
|
title: %{type: :string},
|
|
description: %{type: :text},
|
|
image_url: %{type: :image},
|
|
link_text: %{type: :string},
|
|
link_page: %{type: :string}
|
|
},
|
|
allowed_on: [:home, :about]
|
|
},
|
|
"content_body" => %{
|
|
name: "Rich Text Content",
|
|
component: &SimpleshopThemeWeb.ShopComponents.content_body/1,
|
|
settings_schema: %{
|
|
image_url: %{type: :image},
|
|
content: %{type: :rich_text}
|
|
},
|
|
allowed_on: [:about, :contact]
|
|
},
|
|
"reviews_section" => %{
|
|
name: "Customer Reviews",
|
|
component: &SimpleshopThemeWeb.ShopComponents.reviews_section/1,
|
|
settings_schema: %{},
|
|
allowed_on: [:pdp]
|
|
},
|
|
"related_products" => %{
|
|
name: "Related Products",
|
|
component: &SimpleshopThemeWeb.ShopComponents.related_products_section/1,
|
|
settings_schema: %{},
|
|
allowed_on: [:pdp]
|
|
}
|
|
}
|
|
end
|
|
```
|
|
|
|
## Page Renderer
|
|
|
|
```elixir
|
|
defmodule SimpleshopThemeWeb.PageRenderer do
|
|
use Phoenix.Component
|
|
import SimpleshopThemeWeb.ShopComponents
|
|
|
|
alias SimpleshopTheme.Content.SectionTypes
|
|
|
|
@doc """
|
|
Renders a page from its layout and data context.
|
|
"""
|
|
def render_page(assigns) do
|
|
~H"""
|
|
<div class="shop-container min-h-screen" style="...">
|
|
<.skip_link />
|
|
<%= if @theme_settings.announcement_bar do %>
|
|
<.announcement_bar theme_settings={@theme_settings} />
|
|
<% end %>
|
|
|
|
<.shop_header {...} />
|
|
|
|
<main id="main-content">
|
|
<%= for section <- @page_layout.sections do %>
|
|
<.render_section
|
|
section={section}
|
|
data={@data}
|
|
theme_settings={@theme_settings}
|
|
mode={@mode}
|
|
/>
|
|
<% end %>
|
|
</main>
|
|
|
|
<.shop_footer theme_settings={@theme_settings} mode={@mode} />
|
|
<.cart_drawer {...} />
|
|
<.search_modal {...} />
|
|
</div>
|
|
"""
|
|
end
|
|
|
|
defp render_section(assigns) do
|
|
section_config = SectionTypes.get(assigns.section.section_type)
|
|
component = section_config.component
|
|
settings = Map.merge(section_config.default_settings, assigns.section.settings)
|
|
|
|
assigns = assign(assigns, :settings, settings)
|
|
|
|
~H"""
|
|
<%= if @section.enabled do %>
|
|
<%= component.(@settings |> Map.merge(%{mode: @mode, data: @data})) %>
|
|
<% end %>
|
|
"""
|
|
end
|
|
end
|
|
```
|
|
|
|
## Editor UI
|
|
|
|
### Layout Structure
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ Desktop (≥1024px) │
|
|
├───────────────────┬─────────────────────────────────────────┤
|
|
│ │ │
|
|
│ Section List │ Live Preview │
|
|
│ ┌───────────┐ │ │
|
|
│ │ Hero │ │ ┌─────────────────────────────────┐ │
|
|
│ │ ≡ ✎ ✕ │ │ │ │ │
|
|
│ └───────────┘ │ │ [Hero Section Preview] │ │
|
|
│ ┌───────────┐ │ │ │ │
|
|
│ │ Products │ │ ├─────────────────────────────────┤ │
|
|
│ │ ≡ ✎ ✕ │ │ │ │ │
|
|
│ └───────────┘ │ │ [Products Section Preview] │ │
|
|
│ ┌───────────┐ │ │ │ │
|
|
│ │ Image+Text│ │ └─────────────────────────────────┘ │
|
|
│ │ ≡ ✎ ✕ │ │ │
|
|
│ └───────────┘ │ │
|
|
│ │ │
|
|
│ [+ Add Section] │ │
|
|
│ │ │
|
|
└───────────────────┴─────────────────────────────────────────┘
|
|
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ Mobile (<1024px) │
|
|
├─────────────────────────────────────────────────────────────┤
|
|
│ ┌─────────────────────────────────────────────────────┐ │
|
|
│ │ Page: Home [Preview] ▼ │ │
|
|
│ └─────────────────────────────────────────────────────┘ │
|
|
│ │
|
|
│ Sections: │
|
|
│ ┌─────────────────────────────────────────────────────┐ │
|
|
│ │ ≡ Hero ✎ ✕ │ │
|
|
│ └─────────────────────────────────────────────────────┘ │
|
|
│ ┌─────────────────────────────────────────────────────┐ │
|
|
│ │ ≡ Featured Products ✎ ✕ │ │
|
|
│ └─────────────────────────────────────────────────────┘ │
|
|
│ ┌─────────────────────────────────────────────────────┐ │
|
|
│ │ ≡ Image + Text ✎ ✕ │ │
|
|
│ └─────────────────────────────────────────────────────┘ │
|
|
│ │
|
|
│ [+ Add Section] │
|
|
│ │
|
|
│ ───────────────────────────────────────────────────────── │
|
|
│ │
|
|
│ [Preview Panel - expandable/collapsible] │
|
|
│ │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### Key Interactions
|
|
|
|
1. **Drag & Drop Reordering**: Sections can be reordered via drag handles (≡)
|
|
2. **Edit Settings**: Clicking ✎ opens settings panel for that section
|
|
3. **Remove Section**: Clicking ✕ removes section (with confirmation)
|
|
4. **Add Section**: Opens modal/drawer with available section types
|
|
5. **Live Preview**: Updates in real-time as changes are made
|
|
6. **Undo/Redo**: Track changes for undo capability (optional)
|
|
|
|
### Settings Panel
|
|
|
|
When editing a section, show a form based on its `settings_schema`:
|
|
|
|
```
|
|
┌─────────────────────────────────────────┐
|
|
│ Edit: Hero Banner ✕ │
|
|
├─────────────────────────────────────────┤
|
|
│ │
|
|
│ Title │
|
|
│ ┌─────────────────────────────────────┐ │
|
|
│ │ Original designs, printed on demand │ │
|
|
│ └─────────────────────────────────────┘ │
|
|
│ │
|
|
│ Description │
|
|
│ ┌─────────────────────────────────────┐ │
|
|
│ │ From art prints to apparel... │ │
|
|
│ └─────────────────────────────────────┘ │
|
|
│ │
|
|
│ Button Text │
|
|
│ ┌─────────────────────────────────────┐ │
|
|
│ │ Shop the collection │ │
|
|
│ └─────────────────────────────────────┘ │
|
|
│ │
|
|
│ Button Link │
|
|
│ ┌─────────────────────────────────────┐ │
|
|
│ │ collection ▼ │ │
|
|
│ └─────────────────────────────────────┘ │
|
|
│ │
|
|
│ Background Style │
|
|
│ ○ Default ● Sunken │
|
|
│ │
|
|
├─────────────────────────────────────────┤
|
|
│ [Cancel] [Save Changes] │
|
|
└─────────────────────────────────────────┘
|
|
```
|
|
|
|
## Implementation Phases
|
|
|
|
### Phase 1: Data Model & Migration
|
|
- Create `page_layouts` and `page_sections` tables
|
|
- Create Ecto schemas
|
|
- Create seed data from current static templates
|
|
- Add context functions (CRUD)
|
|
|
|
### Phase 2: Page Renderer
|
|
- Create `PageRenderer` module
|
|
- Create `SectionTypes` registry
|
|
- Update shop LiveViews to use renderer
|
|
- Update preview to use renderer
|
|
- Ensure backward compatibility (fallback to static if no layout)
|
|
|
|
### Phase 3: Editor UI - Read Only
|
|
- Create `PageEditorLive` LiveView
|
|
- Display current sections in list
|
|
- Show live preview
|
|
- Page selector dropdown
|
|
|
|
### Phase 4: Editor UI - Basic Editing
|
|
- Add/remove sections
|
|
- Drag & drop reordering (using sortable.js or similar)
|
|
- Save changes to database
|
|
|
|
### Phase 5: Section Settings
|
|
- Settings panel component
|
|
- Dynamic form generation from schema
|
|
- Real-time preview updates
|
|
|
|
### Phase 6: Polish & UX
|
|
- Undo/redo support
|
|
- Keyboard shortcuts
|
|
- Mobile-optimized editing
|
|
- Loading states
|
|
- Error handling
|
|
|
|
## Technical Considerations
|
|
|
|
### LiveView Communication
|
|
|
|
The editor needs to:
|
|
1. Load page layout on mount
|
|
2. Send updates on reorder/add/remove
|
|
3. Update preview in real-time
|
|
|
|
Using `phx-hook` for drag-and-drop and `push_event` for preview updates.
|
|
|
|
### Caching
|
|
|
|
Page layouts should be cached since they change infrequently:
|
|
- ETS cache similar to `CSSCache`
|
|
- Invalidate on save
|
|
|
|
### Migration Path
|
|
|
|
1. Deploy data model
|
|
2. Seed current layouts as "default" layouts
|
|
3. Deploy renderer (uses defaults if no custom layout)
|
|
4. Deploy editor
|
|
5. Users can customize
|
|
|
|
### Permissions
|
|
|
|
- Only authenticated admins can edit layouts
|
|
- Consider draft/published states for layouts
|
|
- Preview unpublished changes before going live
|
|
|
|
## Open Questions
|
|
|
|
1. Should users be able to create multiple layouts per page type (A/B testing)?
|
|
2. Should sections support nesting (e.g., columns within a section)?
|
|
3. How to handle page-specific data (e.g., PDP needs product, collection needs filter state)?
|
|
4. Should there be a "reset to default" option?
|
|
|
|
## Dependencies
|
|
|
|
- `sortable.js` or similar for drag-and-drop
|
|
- Possibly `@dnd-kit/sortable` if using JS framework
|
|
- JSON schema validation for settings
|
|
|
|
## Related Files
|
|
|
|
- `lib/simpleshop_theme_web/components/shop_components.ex` - Existing section components
|
|
- `lib/simpleshop_theme_web/components/page_templates/` - Current static templates (will become defaults)
|
|
- `lib/simpleshop_theme_web/live/theme_live/index.ex` - Theme editor (reference implementation)
|