diff --git a/docs/plans/page-builder.md b/docs/plans/page-builder.md new file mode 100644 index 0000000..0a50737 --- /dev/null +++ b/docs/plans/page-builder.md @@ -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""" +
+ <.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)