consolidate shop pages into unified LiveView for editor state persistence
All checks were successful
deploy / deploy (push) Successful in 1m27s
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>
This commit is contained in:
154
lib/berrypod_web/live/shop/page.ex
Normal file
154
lib/berrypod_web/live/shop/page.ex
Normal file
@@ -0,0 +1,154 @@
|
||||
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
|
||||
Reference in New Issue
Block a user