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>
16 KiB
16 KiB
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
- Allow customization of page layouts without code changes
- Provide real-time preview while editing
- Support responsive editing (works on mobile and desktop)
- 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
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
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
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
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
- Drag & Drop Reordering: Sections can be reordered via drag handles (≡)
- Edit Settings: Clicking ✎ opens settings panel for that section
- Remove Section: Clicking ✕ removes section (with confirmation)
- Add Section: Opens modal/drawer with available section types
- Live Preview: Updates in real-time as changes are made
- 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_layoutsandpage_sectionstables - Create Ecto schemas
- Create seed data from current static templates
- Add context functions (CRUD)
Phase 2: Page Renderer
- Create
PageRenderermodule - Create
SectionTypesregistry - 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
PageEditorLiveLiveView - 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:
- Load page layout on mount
- Send updates on reorder/add/remove
- 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
- Deploy data model
- Seed current layouts as "default" layouts
- Deploy renderer (uses defaults if no custom layout)
- Deploy editor
- Users can customize
Permissions
- Only authenticated admins can edit layouts
- Consider draft/published states for layouts
- Preview unpublished changes before going live
Open Questions
- Should users be able to create multiple layouts per page type (A/B testing)?
- Should sections support nesting (e.g., columns within a section)?
- How to handle page-specific data (e.g., PDP needs product, collection needs filter state)?
- Should there be a "reset to default" option?
Dependencies
sortable.jsor similar for drag-and-drop- Possibly
@dnd-kit/sortableif using JS framework - JSON schema validation for settings
Related Files
lib/simpleshop_theme_web/components/shop_components.ex- Existing section componentslib/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)