All checks were successful
deploy / deploy (push) Successful in 1m27s
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>
155 lines
4.3 KiB
Elixir
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
|