add url slug resolution to shop routing
Shop.Page resolves custom URLs to correct page types: - resolve_custom_slug/2 detects system pages with custom URLs - Dynamic prefix resolution for /:prefix/:id routes - Updates live_action when slug resolves to different type Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
e9649218fb
commit
c115f08cb8
@ -9,10 +9,11 @@ defmodule BerrypodWeb.Shop.Page do
|
|||||||
use BerrypodWeb, :live_view
|
use BerrypodWeb, :live_view
|
||||||
|
|
||||||
alias BerrypodWeb.Shop.Pages
|
alias BerrypodWeb.Shop.Pages
|
||||||
|
alias BerrypodWeb.R
|
||||||
alias Berrypod.{Media, Settings}
|
alias Berrypod.{Media, Settings}
|
||||||
alias Berrypod.Workers.FaviconGeneratorWorker
|
alias Berrypod.Workers.FaviconGeneratorWorker
|
||||||
|
|
||||||
# Map live_action atoms to page handler modules
|
# Map page type atoms to handler modules
|
||||||
@page_modules %{
|
@page_modules %{
|
||||||
home: Pages.Home,
|
home: Pages.Home,
|
||||||
product: Pages.Product,
|
product: Pages.Product,
|
||||||
@ -187,10 +188,22 @@ defmodule BerrypodWeb.Shop.Page do
|
|||||||
action = socket.assigns.live_action
|
action = socket.assigns.live_action
|
||||||
prev_action = socket.assigns[:_current_page_action]
|
prev_action = socket.assigns[:_current_page_action]
|
||||||
prev_path = socket.assigns[:_current_path]
|
prev_path = socket.assigns[:_current_path]
|
||||||
module = @page_modules[action]
|
|
||||||
parsed_uri = URI.parse(uri)
|
parsed_uri = URI.parse(uri)
|
||||||
current_path = parsed_uri.path
|
current_path = parsed_uri.path
|
||||||
|
|
||||||
|
# For custom_page action, check if slug is a custom URL for a system page
|
||||||
|
{action, params} = resolve_custom_slug(action, params)
|
||||||
|
|
||||||
|
# Update live_action if we resolved to a different page type
|
||||||
|
socket =
|
||||||
|
if action != socket.assigns.live_action do
|
||||||
|
assign(socket, :live_action, action)
|
||||||
|
else
|
||||||
|
socket
|
||||||
|
end
|
||||||
|
|
||||||
|
module = @page_modules[action]
|
||||||
|
|
||||||
# Clean up previous page if needed (e.g., unsubscribe from PubSub)
|
# Clean up previous page if needed (e.g., unsubscribe from PubSub)
|
||||||
socket = maybe_cleanup_previous_page(socket, prev_action)
|
socket = maybe_cleanup_previous_page(socket, prev_action)
|
||||||
|
|
||||||
@ -375,4 +388,51 @@ defmodule BerrypodWeb.Shop.Page do
|
|||||||
end
|
end
|
||||||
|
|
||||||
defp clear_page_specific_assigns(socket, _), do: socket
|
defp clear_page_specific_assigns(socket, _), do: socket
|
||||||
|
|
||||||
|
# Resolve custom URL slugs to system pages
|
||||||
|
# E.g., if cart page has url_slug: "basket", visiting /basket should load the cart page
|
||||||
|
defp resolve_custom_slug(:custom_page, %{"slug" => slug} = params) do
|
||||||
|
case R.page_type_from_slug(slug) do
|
||||||
|
{:page, page_type} ->
|
||||||
|
# Custom URL for a system page - dispatch to that page type
|
||||||
|
# Preserve other params (like query params) but remove slug
|
||||||
|
{page_type, Map.delete(params, "slug")}
|
||||||
|
|
||||||
|
{:custom, _page} ->
|
||||||
|
# Regular custom CMS page - let CustomPage handle it
|
||||||
|
{:custom_page, params}
|
||||||
|
|
||||||
|
nil ->
|
||||||
|
# Unknown slug - let CustomPage handle the 404
|
||||||
|
{:custom_page, params}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Handle dynamic prefix routes for products, collections, and orders.
|
||||||
|
# Works with both default prefixes (/products/123) and custom prefixes (/p/123).
|
||||||
|
# R.prefix_type_from_segment checks both the ETS cache (custom) and defaults.
|
||||||
|
defp resolve_custom_slug(:dynamic_prefix, %{"prefix" => prefix} = params) do
|
||||||
|
# The second segment param name is "id_or_slug" from the route definition
|
||||||
|
id_or_slug = params["id_or_slug"]
|
||||||
|
# Preserve query params by keeping all params except route-specific ones
|
||||||
|
other_params = Map.drop(params, ["prefix", "id_or_slug"])
|
||||||
|
|
||||||
|
case R.prefix_type_from_segment(prefix) do
|
||||||
|
:collections ->
|
||||||
|
{:collection, Map.put(other_params, "slug", id_or_slug)}
|
||||||
|
|
||||||
|
:products ->
|
||||||
|
{:product, Map.put(other_params, "id", id_or_slug)}
|
||||||
|
|
||||||
|
:orders ->
|
||||||
|
{:order_detail, Map.put(other_params, "order_number", id_or_slug)}
|
||||||
|
|
||||||
|
nil ->
|
||||||
|
# Not a known prefix - check if combined path is a system page (e.g. checkout/success)
|
||||||
|
combined_slug = prefix <> "/" <> id_or_slug
|
||||||
|
resolve_custom_slug(:custom_page, Map.put(other_params, "slug", combined_slug))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp resolve_custom_slug(action, params), do: {action, params}
|
||||||
end
|
end
|
||||||
|
|||||||
@ -12,7 +12,7 @@ defmodule BerrypodWeb.Shop.Pages.CustomPage do
|
|||||||
{:noreply, socket}
|
{:noreply, socket}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_params(%{"slug" => slug}, _uri, socket) do
|
def handle_params(%{"slug" => slug} = params, uri, socket) do
|
||||||
page = Pages.get_page(slug)
|
page = Pages.get_page(slug)
|
||||||
|
|
||||||
cond do
|
cond do
|
||||||
@ -27,6 +27,11 @@ defmodule BerrypodWeb.Shop.Pages.CustomPage do
|
|||||||
raise BerrypodWeb.NotFoundError
|
raise BerrypodWeb.NotFoundError
|
||||||
|
|
||||||
true ->
|
true ->
|
||||||
|
handle_page(page_to_map(page), params, uri, socket)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp handle_page(page, _params, _uri, socket) do
|
||||||
extra = Pages.load_block_data(page.blocks, socket.assigns)
|
extra = Pages.load_block_data(page.blocks, socket.assigns)
|
||||||
base = BerrypodWeb.Endpoint.url()
|
base = BerrypodWeb.Endpoint.url()
|
||||||
|
|
||||||
@ -39,21 +44,50 @@ defmodule BerrypodWeb.Shop.Pages.CustomPage do
|
|||||||
|
|
||||||
{:noreply, socket}
|
{:noreply, socket}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Convert Page struct to map for consistent handling
|
||||||
|
defp page_to_map(%Berrypod.Pages.Page{} = page) do
|
||||||
|
%{
|
||||||
|
id: page.id,
|
||||||
|
slug: page.slug,
|
||||||
|
title: page.title,
|
||||||
|
blocks: page.blocks,
|
||||||
|
type: page.type,
|
||||||
|
published: page.published,
|
||||||
|
meta_description: page.meta_description,
|
||||||
|
url_slug: page.url_slug,
|
||||||
|
show_in_nav: page.show_in_nav,
|
||||||
|
nav_label: page.nav_label,
|
||||||
|
nav_position: page.nav_position
|
||||||
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp page_to_map(page) when is_map(page), do: page
|
||||||
|
|
||||||
def handle_event(_event, _params, _socket), do: :cont
|
def handle_event(_event, _params, _socket), do: :cont
|
||||||
|
|
||||||
defp record_broken_url(path) do
|
defp record_broken_url(path) do
|
||||||
|
# Skip static asset paths - these are expected 404s
|
||||||
|
unless static_path?(path) do
|
||||||
prior_hits = Berrypod.Analytics.count_pageviews_for_path(path)
|
prior_hits = Berrypod.Analytics.count_pageviews_for_path(path)
|
||||||
Berrypod.Redirects.record_broken_url(path, prior_hits)
|
Berrypod.Redirects.record_broken_url(path, prior_hits)
|
||||||
|
|
||||||
if prior_hits > 0 do
|
if prior_hits > 0 do
|
||||||
Berrypod.Redirects.attempt_auto_resolve(path)
|
Berrypod.Redirects.attempt_auto_resolve(path)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
rescue
|
rescue
|
||||||
_ -> :ok
|
_ -> :ok
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp static_path?(path) do
|
||||||
|
String.starts_with?(path, "/assets/") or
|
||||||
|
String.starts_with?(path, "/images/") or
|
||||||
|
String.starts_with?(path, "/image_cache/") or
|
||||||
|
String.starts_with?(path, "/mockups/") or
|
||||||
|
String.starts_with?(path, "/favicon")
|
||||||
|
end
|
||||||
|
|
||||||
defp maybe_assign_meta(socket, page, base) do
|
defp maybe_assign_meta(socket, page, base) do
|
||||||
socket
|
socket
|
||||||
|> assign(:og_url, base <> "/#{page.slug}")
|
|> assign(:og_url, base <> "/#{page.slug}")
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user