docs: add page builder feature plan

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>
This commit is contained in:
Jamey Greenwood 2026-01-17 22:19:09 +00:00
parent c25953780a
commit 94f98b8be0

399
docs/plans/page-builder.md Normal file
View File

@ -0,0 +1,399 @@
# 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)