berrypod/lib/berrypod_web/live/shop/page.ex
jamey bb5d220079
All checks were successful
deploy / deploy (push) Successful in 1m27s
consolidate shop pages into unified LiveView for editor state persistence
Replace individual shop LiveViews with a single Shop.Page that dispatches
to page modules based on live_action. This enables patch navigation between
pages, preserving socket state (including editor state) across transitions.

Changes:
- Add Shop.Page unified LiveView with handle_params dispatch
- Extract page logic into Shop.Pages.* modules (Home, Product, Collection, etc.)
- Update router to use Shop.Page with live_action for all shop routes
- Change navigate= to patch= in shop component links
- Add maybe_sync_editing_blocks to reload editor state when page changes
- Track editor_page_slug to detect cross-page navigation while editing
- Fix picture element height when hover image disabled
- Extract ThemeEditor components for shared use

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-09 14:47:50 +00:00

155 lines
4.3 KiB
Elixir

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"""
<BerrypodWeb.PageRenderer.render_page {assigns} />
"""
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