# 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"""
<.skip_link /> <%= if @theme_settings.announcement_bar do %> <.announcement_bar theme_settings={@theme_settings} /> <% end %> <.shop_header {...} />
<%= for section <- @page_layout.sections do %> <.render_section section={section} data={@data} theme_settings={@theme_settings} mode={@mode} /> <% end %>
<.shop_footer theme_settings={@theme_settings} mode={@mode} /> <.cart_drawer {...} /> <.search_modal {...} />
""" 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)