defmodule BerrypodWeb.Shop.Page do @moduledoc """ Unified shop LiveView that handles all shop pages. Using a single LiveView enables `patch` navigation between pages, preserving socket state (including editor state) across transitions. """ use BerrypodWeb, :live_view alias BerrypodWeb.Shop.Pages # Map live_action atoms to page handler modules @page_modules %{ home: Pages.Home, product: Pages.Product, collection: Pages.Collection, cart: Pages.Cart, contact: Pages.Contact, search: Pages.Search, orders: Pages.Orders, order_detail: Pages.OrderDetail, checkout_success: Pages.CheckoutSuccess, custom_page: Pages.CustomPage, # Content pages all use the same module about: Pages.Content, delivery: Pages.Content, privacy: Pages.Content, terms: Pages.Content } # Pages that need session data passed to init @session_pages [:orders, :order_detail] @impl true def mount(_params, session, socket) do # Store session for pages that need it (orders, order_detail) {:ok, assign(socket, :_session, session)} end @impl true def handle_params(params, uri, socket) do action = socket.assigns.live_action prev_action = socket.assigns[:_current_page_action] module = @page_modules[action] # Clean up previous page if needed (e.g., unsubscribe from PubSub) socket = maybe_cleanup_previous_page(socket, prev_action) socket = if action != prev_action do # Page type changed - call init socket = assign(socket, :_current_page_action, action) result = if action in @session_pages do module.init(socket, params, uri, socket.assigns._session) else module.init(socket, params, uri) end case result do {:noreply, socket} -> socket {:redirect, socket} -> socket end else socket end # After page init, sync editor state if editing and page changed socket = maybe_sync_editing_blocks(socket) # Always call handle_params for URL changes case module.handle_params(params, uri, socket) do {:noreply, socket} -> {:noreply, socket} end end # If editing and we navigated to a different page, reload editing_blocks defp maybe_sync_editing_blocks(socket) do page = socket.assigns[:page] editing = socket.assigns[:editing] editor_page_slug = socket.assigns[:editor_page_slug] if editing && page && page.slug != editor_page_slug do # Page changed while editing - reload editing state for the new page allowed = Berrypod.Pages.BlockTypes.allowed_for(page.slug) at_defaults = Berrypod.Pages.Defaults.matches_defaults?(page.slug, page.blocks) socket |> assign(:editing_blocks, page.blocks) |> assign(:editor_page_slug, page.slug) |> assign(:editor_dirty, false) |> assign(:editor_at_defaults, at_defaults) |> assign(:editor_history, []) |> assign(:editor_future, []) |> assign(:editor_expanded, MapSet.new()) |> assign(:editor_allowed_blocks, allowed) else socket end end @impl true def handle_event(event, params, socket) do module = @page_modules[socket.assigns.live_action] case module.handle_event(event, params, socket) do :cont -> # Event not handled by page module, let hooks handle it {:noreply, socket} {:noreply, socket} -> {:noreply, socket} end end @impl true def handle_info(msg, socket) do module = @page_modules[socket.assigns.live_action] # Check if the module defines handle_info if function_exported?(module, :handle_info, 2) do case module.handle_info(msg, socket) do :cont -> {:noreply, socket} {:noreply, socket} -> {:noreply, socket} end else {:noreply, socket} end end @impl true def render(assigns) do # Cart page needs extra assigns computed at render time assigns = if assigns.live_action == :cart do Pages.Cart.compute_assigns(assigns) else assigns end ~H""" """ end # Clean up previous page state when transitioning defp maybe_cleanup_previous_page(socket, :checkout_success) do Pages.CheckoutSuccess.cleanup(socket) end defp maybe_cleanup_previous_page(socket, _), do: socket end