defmodule BerrypodWeb.Admin.Pages.Editor do use BerrypodWeb, :live_view alias Berrypod.{Media, Pages} alias Berrypod.Pages.{BlockEditor, BlockTypes, Page} alias Berrypod.Products.ProductImage alias Berrypod.Theme.{Fonts, PreviewData} import BerrypodWeb.BlockEditorComponents @impl true def mount(%{"slug" => slug}, _session, socket) do page = Pages.get_page(slug) allowed_blocks = BlockTypes.allowed_for(slug) preview_data = %{ products: PreviewData.products(), categories: PreviewData.categories(), cart_items: PreviewData.cart_items() } {:ok, socket |> assign(:page_title, page.title) |> assign(:slug, slug) |> assign(:page_data, page) |> assign(:blocks, page.blocks) |> assign(:allowed_blocks, allowed_blocks) |> assign(:dirty, false) |> assign(:show_picker, false) |> assign(:picker_filter, "") |> assign(:expanded, MapSet.new()) |> assign(:live_region_message, nil) |> assign(:show_preview, false) |> assign(:preview_data, preview_data) |> assign(:logo_image, Media.get_logo()) |> assign(:header_image, Media.get_header()) |> assign(:image_picker_block_id, nil) |> assign(:image_picker_field_key, nil) |> assign(:image_picker_images, []) |> assign(:image_picker_search, "") |> assign(:is_custom_page, !Page.system_slug?(slug))} end # ── Block manipulation events ──────────────────────────────────── @impl true def handle_event("move_up", %{"id" => block_id}, socket) do case BlockEditor.move_up(socket.assigns.blocks, block_id) do {:ok, new_blocks, message} -> {:noreply, apply_mutation(socket, new_blocks, message)} :noop -> {:noreply, socket} end end def handle_event("move_down", %{"id" => block_id}, socket) do case BlockEditor.move_down(socket.assigns.blocks, block_id) do {:ok, new_blocks, message} -> {:noreply, apply_mutation(socket, new_blocks, message)} :noop -> {:noreply, socket} end end def handle_event("remove_block", %{"id" => block_id}, socket) do {:ok, new_blocks, message} = BlockEditor.remove_block(socket.assigns.blocks, block_id) {:noreply, apply_mutation(socket, new_blocks, message)} end def handle_event("duplicate_block", %{"id" => block_id}, socket) do case BlockEditor.duplicate_block(socket.assigns.blocks, block_id) do {:ok, new_blocks, message} -> {:noreply, apply_mutation(socket, new_blocks, message)} :noop -> {:noreply, socket} end end def handle_event("add_block", %{"type" => type}, socket) do case BlockEditor.add_block(socket.assigns.blocks, type) do {:ok, new_blocks, message} -> {:noreply, socket |> assign(:blocks, new_blocks) |> assign(:dirty, true) |> assign(:show_picker, false) |> assign(:live_region_message, message)} :noop -> {:noreply, socket} end end def handle_event("update_block_settings", params, socket) do block_id = params["block_id"] new_settings = params["block_settings"] || %{} case BlockEditor.update_settings(socket.assigns.blocks, block_id, new_settings) do {:ok, new_blocks} -> {:noreply, socket |> assign(:blocks, new_blocks) |> assign(:dirty, true)} :noop -> {:noreply, socket} end end # ── Repeater events ────────────────────────────────────────────── def handle_event("repeater_add", %{"block-id" => block_id, "field" => field_key}, socket) do case BlockEditor.repeater_add(socket.assigns.blocks, block_id, field_key) do {:ok, new_blocks, message} -> {:noreply, apply_mutation(socket, new_blocks, message)} :noop -> {:noreply, socket} end end def handle_event( "repeater_remove", %{"block-id" => block_id, "field" => field_key, "index" => index_str}, socket ) do index = String.to_integer(index_str) case BlockEditor.repeater_remove(socket.assigns.blocks, block_id, field_key, index) do {:ok, new_blocks, message} -> {:noreply, apply_mutation(socket, new_blocks, message)} :noop -> {:noreply, socket} end end def handle_event( "repeater_move", %{"block-id" => block_id, "field" => field_key, "index" => index_str, "dir" => dir}, socket ) do index = String.to_integer(index_str) case BlockEditor.repeater_move(socket.assigns.blocks, block_id, field_key, index, dir) do {:ok, new_blocks, message} -> {:noreply, apply_mutation(socket, new_blocks, message)} :noop -> {:noreply, socket} end end # ── Image picker events ────────────────────────────────────────── def handle_event( "show_image_picker", %{"block-id" => block_id, "field" => field_key}, socket ) do images = Media.list_images() {:noreply, socket |> assign(:image_picker_block_id, block_id) |> assign(:image_picker_field_key, field_key) |> assign(:image_picker_images, images) |> assign(:image_picker_search, "")} end def handle_event("hide_image_picker", _params, socket) do {:noreply, socket |> assign(:image_picker_block_id, nil) |> assign(:image_picker_field_key, nil)} end def handle_event("image_picker_search", %{"value" => value}, socket) do {:noreply, assign(socket, :image_picker_search, value)} end def handle_event("pick_image", %{"image-id" => image_id}, socket) do block_id = socket.assigns.image_picker_block_id field_key = socket.assigns.image_picker_field_key case BlockEditor.update_settings(socket.assigns.blocks, block_id, %{field_key => image_id}) do {:ok, new_blocks} -> {:noreply, socket |> assign(:blocks, new_blocks) |> assign(:dirty, true) |> assign(:image_picker_block_id, nil) |> assign(:image_picker_field_key, nil)} :noop -> {:noreply, socket} end end def handle_event( "clear_image", %{"block-id" => block_id, "field" => field_key}, socket ) do case BlockEditor.update_settings(socket.assigns.blocks, block_id, %{field_key => ""}) do {:ok, new_blocks} -> {:noreply, socket |> assign(:blocks, new_blocks) |> assign(:dirty, true)} :noop -> {:noreply, socket} end end # ── UI events ──────────────────────────────────────────────────── def handle_event("toggle_expand", %{"id" => block_id}, socket) do expanded = socket.assigns.expanded block = Enum.find(socket.assigns.blocks, &(&1["id"] == block_id)) block_name = BlockEditor.block_display_name(block) {new_expanded, action} = if MapSet.member?(expanded, block_id) do {MapSet.delete(expanded, block_id), "collapsed"} else {MapSet.put(expanded, block_id), "expanded"} end {:noreply, socket |> assign(:expanded, new_expanded) |> assign(:live_region_message, "#{block_name} settings #{action}")} end def handle_event("show_picker", _params, socket) do {:noreply, socket |> assign(:show_picker, true) |> assign(:picker_filter, "")} end def handle_event("hide_picker", _params, socket) do {:noreply, assign(socket, :show_picker, false)} end def handle_event("filter_picker", %{"value" => value}, socket) do {:noreply, assign(socket, :picker_filter, value)} end def handle_event("toggle_preview", _params, socket) do {:noreply, assign(socket, :show_preview, !socket.assigns.show_preview)} end # ── Page actions ───────────────────────────────────────────────── def handle_event("save", _params, socket) do %{slug: slug, blocks: blocks, page_data: page_data} = socket.assigns case Pages.save_page(slug, %{title: page_data.title, blocks: blocks}) do {:ok, _page} -> {:noreply, socket |> assign(:dirty, false) |> put_flash(:info, "Page saved")} {:error, _changeset} -> {:noreply, put_flash(socket, :error, "Failed to save page")} end end def handle_event("reset_defaults", _params, socket) do slug = socket.assigns.slug :ok = Pages.reset_page(slug) page = Pages.get_page(slug) {:noreply, socket |> assign(:blocks, page.blocks) |> assign(:dirty, false) |> put_flash(:info, "Page reset to defaults")} end # ── Render ─────────────────────────────────────────────────────── @impl true def render(assigns) do ~H"""
Unsaved changes
No blocks on this page yet.