- <.block_card
- :for={{block, idx} <- Enum.with_index(@blocks)}
- block={block}
- idx={idx}
- total={length(@blocks)}
- expanded={@expanded}
- />
+
+ <%!-- Editor pane --%>
+
+ <%!-- Block list --%>
+
+ <.block_card
+ :for={{block, idx} <- Enum.with_index(@blocks)}
+ block={block}
+ idx={idx}
+ total={length(@blocks)}
+ expanded={@expanded}
+ />
-
-
No blocks on this page yet.
+
+
No blocks on this page yet.
+
+
+
+ <%!-- Add block button --%>
+
+
+
-
- <%!-- Add block button --%>
-
-
+ <%!-- Preview pane --%>
+
+ <.preview_pane
+ slug={@slug}
+ blocks={@blocks}
+ page_data={@page_data}
+ preview_data={@preview_data}
+ theme_settings={@theme_settings}
+ generated_css={@generated_css}
+ logo_image={@logo_image}
+ header_image={@header_image}
+ />
+
<%!-- Block picker modal --%>
@@ -385,6 +436,126 @@ defmodule BerrypodWeb.Admin.Pages.Editor do
"""
end
+ # ── Preview ───────────────────────────────────────────────────────
+
+ defp preview_pane(assigns) do
+ # Build a temporary page struct from working state
+ page = %{slug: assigns.slug, title: assigns.page_data.title, blocks: assigns.blocks}
+
+ preview =
+ assigns
+ |> assign(:page, page)
+ |> assign(:mode, :preview)
+ |> assign(:products, assigns.preview_data.products)
+ |> assign(:categories, assigns.preview_data.categories)
+ |> assign(:cart_items, PreviewData.cart_drawer_items())
+ |> assign(:cart_count, 2)
+ |> assign(:cart_subtotal, "£72.00")
+ |> assign(:cart_drawer_open, false)
+ |> preview_page_context(assigns.slug)
+
+ extra = Pages.load_block_data(page.blocks, preview)
+ assigns = assign(assigns, :preview, assign(preview, extra))
+
+ ~H"""
+
+
+
+
+ """
+ end
+
+ defp preview_page_context(assigns, "pdp") do
+ product = List.first(assigns.preview_data.products)
+ option_types = Map.get(product, :option_types) || []
+ variants = Map.get(product, :variants) || []
+
+ {selected_options, selected_variant} =
+ case variants do
+ [first | _] -> {first.options, first}
+ [] -> {%{}, nil}
+ end
+
+ available_options =
+ Enum.reduce(option_types, %{}, fn opt, acc ->
+ values = Enum.map(opt.values, & &1.title)
+ Map.put(acc, opt.name, values)
+ end)
+
+ display_price =
+ if selected_variant, do: selected_variant.price, else: product.cheapest_price
+
+ assigns
+ |> assign(:product, product)
+ |> assign(:gallery_images, build_gallery_images(product))
+ |> assign(:option_types, option_types)
+ |> assign(:selected_options, selected_options)
+ |> assign(:available_options, available_options)
+ |> assign(:display_price, display_price)
+ |> assign(:quantity, 1)
+ |> assign(:option_urls, %{})
+ end
+
+ defp preview_page_context(assigns, "cart") do
+ cart_items = assigns.preview_data.cart_items
+
+ subtotal =
+ Enum.reduce(cart_items, 0, fn item, acc ->
+ acc + item.product.cheapest_price * item.quantity
+ end)
+
+ assigns
+ |> assign(:cart_page_items, cart_items)
+ |> assign(:cart_page_subtotal, subtotal)
+ end
+
+ defp preview_page_context(assigns, "about"),
+ do: assign(assigns, :content_blocks, PreviewData.about_content())
+
+ defp preview_page_context(assigns, "delivery"),
+ do: assign(assigns, :content_blocks, PreviewData.delivery_content())
+
+ defp preview_page_context(assigns, "privacy"),
+ do: assign(assigns, :content_blocks, PreviewData.privacy_content())
+
+ defp preview_page_context(assigns, "terms"),
+ do: assign(assigns, :content_blocks, PreviewData.terms_content())
+
+ defp preview_page_context(assigns, "error") do
+ assign(assigns, %{
+ error_code: "404",
+ error_title: "Page Not Found",
+ error_description:
+ "Sorry, we couldn't find the page you're looking for. Perhaps you've mistyped the URL or the page has been moved."
+ })
+ end
+
+ defp preview_page_context(assigns, _slug), do: assigns
+
+ defp build_gallery_images(product) do
+ (Map.get(product, :images) || [])
+ |> Enum.sort_by(& &1.position)
+ |> Enum.map(fn img -> ProductImage.url(img, 1200) end)
+ |> Enum.reject(&is_nil/1)
+ end
+
+ # ── Block card ─────────────────────────────────────────────────────
+
defp block_card(assigns) do
block_type = BlockTypes.get(assigns.block["type"])
has_settings = has_settings?(assigns.block)
diff --git a/test/berrypod_web/live/admin/pages_test.exs b/test/berrypod_web/live/admin/pages_test.exs
index 7ba3086..3389259 100644
--- a/test/berrypod_web/live/admin/pages_test.exs
+++ b/test/berrypod_web/live/admin/pages_test.exs
@@ -624,6 +624,83 @@ defmodule BerrypodWeb.Admin.PagesTest do
end
end
+ describe "live preview" do
+ setup %{conn: conn, user: user} do
+ %{conn: log_in_user(conn, user)}
+ end
+
+ test "preview pane renders page content", %{conn: conn} do
+ {:ok, _view, html} = live(conn, ~p"/admin/pages/home")
+
+ # Preview pane should be in the DOM with the themed class
+ assert html =~ "page-editor-preview themed"
+ # Should render the page via PageRenderer (hero block is on home)
+ assert html =~ "page-editor-preview-pane"
+ end
+
+ test "toggle preview switches between edit and preview on mobile", %{conn: conn} do
+ {:ok, view, _html} = live(conn, ~p"/admin/pages/home")
+
+ # Initially: editor visible, preview hidden on mobile
+ assert has_element?(view, ".page-editor-pane:not(.page-editor-pane-hidden-mobile)")
+ assert has_element?(view, ".page-editor-preview-hidden-mobile")
+
+ # Toggle to preview
+ render_click(view, "toggle_preview")
+
+ assert has_element?(view, ".page-editor-pane-hidden-mobile")
+
+ assert has_element?(
+ view,
+ ".page-editor-preview-pane:not(.page-editor-preview-hidden-mobile)"
+ )
+
+ # Toggle back to edit
+ render_click(view, "toggle_preview")
+
+ assert has_element?(view, ".page-editor-pane:not(.page-editor-pane-hidden-mobile)")
+ assert has_element?(view, ".page-editor-preview-hidden-mobile")
+ end
+
+ test "preview updates when block settings change", %{conn: conn} do
+ {:ok, view, _html} = live(conn, ~p"/admin/pages/home")
+
+ page = Pages.get_page("home")
+ hero = Enum.at(page.blocks, 0)
+
+ render_click(view, "toggle_expand", %{"id" => hero["id"]})
+
+ render_change(view, "update_block_settings", %{
+ "block_id" => hero["id"],
+ "block_settings" => %{"title" => "Preview test title"}
+ })
+
+ # The preview should show the updated title
+ html = render(view)
+ assert html =~ "Preview test title"
+ end
+
+ test "preview updates after block reorder", %{conn: conn} do
+ {:ok, view, _html} = live(conn, ~p"/admin/pages/home")
+
+ page = Pages.get_page("home")
+ first_block = Enum.at(page.blocks, 0)
+
+ render_click(view, "move_down", %{"id" => first_block["id"]})
+
+ # Should still render without errors
+ html = render(view)
+ assert html =~ "page-editor-preview themed"
+ end
+
+ test "preview toggle button shows in header", %{conn: conn} do
+ {:ok, view, _html} = live(conn, ~p"/admin/pages/home")
+
+ # Toggle button should be present
+ assert has_element?(view, ".page-editor-toggle-preview")
+ end
+ end
+
defp count_repeater_items(html) do
Regex.scan(~r/class="repeater-item"/, html) |> length()
end