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:
jamey
2026-04-01 00:36:05 +01:00
parent e9649218fb
commit c115f08cb8
2 changed files with 112 additions and 18 deletions

View File

@@ -9,10 +9,11 @@ defmodule BerrypodWeb.Shop.Page do
use BerrypodWeb, :live_view
alias BerrypodWeb.Shop.Pages
alias BerrypodWeb.R
alias Berrypod.{Media, Settings}
alias Berrypod.Workers.FaviconGeneratorWorker
# Map live_action atoms to page handler modules
# Map page type atoms to handler modules
@page_modules %{
home: Pages.Home,
product: Pages.Product,
@@ -187,10 +188,22 @@ defmodule BerrypodWeb.Shop.Page do
action = socket.assigns.live_action
prev_action = socket.assigns[:_current_page_action]
prev_path = socket.assigns[:_current_path]
module = @page_modules[action]
parsed_uri = URI.parse(uri)
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)
socket = maybe_cleanup_previous_page(socket, prev_action)
@@ -375,4 +388,51 @@ defmodule BerrypodWeb.Shop.Page do
end
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