All checks were successful
deploy / deploy (push) Successful in 1m27s
When navigating between page types in the unified shop LiveView, assigns from the previous page could persist and cause stale data to appear or template errors. Now explicitly nils them out. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
215 lines
6.0 KiB
Elixir
215 lines
6.0 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 - clear previous page's assigns and call init
|
|
socket =
|
|
socket
|
|
|> assign(:_current_page_action, action)
|
|
|> clear_page_specific_assigns(prev_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
|
|
|
|
# Clear assigns that are specific to each page type to prevent stale data
|
|
# from bleeding into the next page's render
|
|
defp clear_page_specific_assigns(socket, :product) do
|
|
socket
|
|
|> assign(:product, nil)
|
|
|> assign(:all_images, nil)
|
|
|> assign(:variants, nil)
|
|
|> assign(:option_types, nil)
|
|
|> assign(:selected_variant, nil)
|
|
|> assign(:selected_options, nil)
|
|
|> assign(:available_options, nil)
|
|
|> assign(:display_price, nil)
|
|
|> assign(:gallery_images, nil)
|
|
|> assign(:option_urls, nil)
|
|
|> assign(:related_products, nil)
|
|
|> assign(:json_ld, nil)
|
|
end
|
|
|
|
defp clear_page_specific_assigns(socket, :collection) do
|
|
socket
|
|
|> assign(:products, nil)
|
|
|> assign(:pagination, nil)
|
|
|> assign(:current_sort, nil)
|
|
|> assign(:collection_title, nil)
|
|
|> assign(:collection_slug, nil)
|
|
end
|
|
|
|
defp clear_page_specific_assigns(socket, :contact) do
|
|
socket
|
|
|> assign(:contact_form, nil)
|
|
|> assign(:lookup_form, nil)
|
|
|> assign(:looked_up_orders, nil)
|
|
end
|
|
|
|
defp clear_page_specific_assigns(socket, :search) do
|
|
assign(socket, :search_results, nil)
|
|
end
|
|
|
|
defp clear_page_specific_assigns(socket, :orders) do
|
|
assign(socket, :orders, nil)
|
|
end
|
|
|
|
defp clear_page_specific_assigns(socket, :order_detail) do
|
|
socket
|
|
|> assign(:order, nil)
|
|
|> assign(:email_form, nil)
|
|
end
|
|
|
|
defp clear_page_specific_assigns(socket, :checkout_success) do
|
|
socket
|
|
|> assign(:order, nil)
|
|
|> assign(:js_purchase_tracked, nil)
|
|
|> assign(:css_purchase_tracked, nil)
|
|
end
|
|
|
|
defp clear_page_specific_assigns(socket, _), do: socket
|
|
end
|