integrate R module and add url editor ui

Replaces hardcoded paths with R module throughout:
- Shop components: layout nav, cart, product links
- Controllers: cart, checkout, contact, seo, order lookup
- Shop pages: collection, product, search, checkout success, etc.
- Site context: nav item url resolution

Admin URL management:
- Settings page: prefix editor with validation feedback
- Page renderer: url_editor component for page URLs
- CSS for url editor styling

Test updates for cache isolation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
jamey
2026-04-01 00:36:17 +01:00
parent c115f08cb8
commit a41771efc8
28 changed files with 938 additions and 160 deletions

View File

@@ -270,10 +270,11 @@ defmodule BerrypodWeb.PageEditorHook do
end
# Initialize settings form from current page if not already set
# Only custom pages have editable settings (meta_description, published, etc.)
# Init settings form for pages with editable settings
# Custom pages: all settings editable
# System pages: only url_slug editable (except home)
socket =
if is_nil(socket.assigns[:settings_form]) && socket.assigns[:page] &&
socket.assigns.page[:type] == "custom" do
if is_nil(socket.assigns[:settings_form]) && socket.assigns[:page] do
init_settings_form(socket)
else
socket
@@ -930,14 +931,14 @@ defmodule BerrypodWeb.PageEditorHook do
# Catch-all for unknown theme actions
defp handle_theme_action(_action, _params, socket), do: {:halt, socket}
# ── Settings editing actions (custom page settings) ────────────────
# ── Settings editing actions (page settings) ────────────────
# Validate page settings form (live as-you-type)
defp handle_settings_action("validate_page", %{"page" => params}, socket) do
page = socket.assigns.page
# Only allow editing custom pages
if page && page.type == "custom" do
# Allow editing for custom pages (all fields) or system pages (url_slug only)
if page && page.type in ["custom", "system"] do
socket =
socket
|> assign(:settings_form, params)
@@ -954,43 +955,127 @@ defmodule BerrypodWeb.PageEditorHook do
defp handle_settings_action("save_page", %{"page" => params}, socket) do
page = socket.assigns.page
# Only allow editing custom pages
if page && page.type == "custom" do
# Normalize checkbox fields (unchecked checkboxes aren't sent)
params =
params
|> Map.put_new("published", "false")
|> Map.put_new("show_in_nav", "false")
cond do
# Custom pages: save all settings including url_slug
page && page.type == "custom" ->
save_custom_page_settings(socket, page, params)
old_slug = page.slug
# System pages with url_prefix (collections, products, orders)
page && page.type == "system" && params["url_prefix"] ->
save_system_page_url_prefix(socket, page, params["url_prefix"])
# Fetch the Page struct from DB (assigns.page may be a map from cache)
page_struct = Pages.get_page_struct(page.slug)
# System pages: only save url_slug (other fields are read-only)
page && page.type == "system" && params["url_slug"] ->
save_system_page_url_slug(socket, page, params["url_slug"])
case Pages.update_custom_page(page_struct, params) do
{:ok, updated_page} ->
# Reinitialize form from saved page
true ->
{:halt, socket}
end
end
defp handle_settings_action(_action, _params, socket), do: {:halt, socket}
defp save_custom_page_settings(socket, page, params) do
# Normalize checkbox fields (unchecked checkboxes aren't sent)
params =
params
|> Map.put_new("published", "false")
|> Map.put_new("show_in_nav", "false")
old_slug = page.slug
# Fetch the Page struct from DB (assigns.page may be a map from cache)
page_struct = Pages.get_page_struct(page.slug)
# Handle url_slug change separately to ensure redirects are created
url_slug = params["url_slug"]
old_url_slug = page[:url_slug]
# First update the main page settings
case Pages.update_custom_page(page_struct, params) do
{:ok, updated_page} ->
# If url_slug changed, handle redirect creation
socket =
if url_slug != old_url_slug do
Pages.update_page_url_slug(updated_page, url_slug)
# Reload page to get updated url_slug
updated_page = Pages.get_page(updated_page.slug)
assign(socket, :page, updated_page)
else
assign(socket, :page, updated_page)
end
socket =
socket
|> assign(:settings_form, nil)
|> assign(:settings_dirty, false)
|> assign(:settings_save_status, :saved)
# Reinit form with new page values
socket = init_settings_form(socket)
# If slug changed, redirect to new URL
socket =
if updated_page.slug != old_slug do
push_navigate(socket, to: "/#{updated_page.slug}")
else
socket
end
{:halt, socket}
{:error, _changeset} ->
socket = assign(socket, :settings_save_status, :error)
{:halt, socket}
end
end
defp save_system_page_url_slug(socket, page, url_slug) do
case Pages.update_page_url_slug(page.slug, url_slug) do
{:ok, _updated} ->
# Reload page from cache to get updated data
updated_page = Pages.get_page(page.slug)
socket =
socket
|> assign(:page, updated_page)
|> assign(:settings_form, nil)
|> assign(:settings_dirty, false)
|> assign(:settings_save_status, :saved)
socket = init_settings_form(socket)
{:halt, socket}
{:error, _} ->
socket = assign(socket, :settings_save_status, :error)
{:halt, socket}
end
end
# Save URL prefix for prefixed pages (collection, pdp, orders)
defp save_system_page_url_prefix(socket, page, new_prefix) do
# Determine the prefix type based on the page slug
prefix_type =
case page.slug do
"collection" -> :collections
"pdp" -> :products
"orders" -> :orders
"order_detail" -> :orders
_ -> nil
end
if prefix_type do
case Settings.update_url_prefix(prefix_type, new_prefix) do
{:ok, _} ->
socket =
socket
|> assign(:page, updated_page)
|> assign(:settings_form, nil)
|> assign(:settings_dirty, false)
|> assign(:settings_save_status, :saved)
# Reinit form with new page values
socket = init_settings_form(socket)
# If slug changed, redirect to new URL
socket =
if updated_page.slug != old_slug do
push_navigate(socket, to: "/#{updated_page.slug}")
else
socket
end
{:halt, socket}
{:error, _changeset} ->
{:error, _reason} ->
socket = assign(socket, :settings_save_status, :error)
{:halt, socket}
end
@@ -999,9 +1084,6 @@ defmodule BerrypodWeb.PageEditorHook do
end
end
# Catch-all for unknown settings actions
defp handle_settings_action(_action, _params, socket), do: {:halt, socket}
# --- Site tab event handlers ---
defp handle_site_action("update", %{"site" => site_params}, socket) do
@@ -1266,13 +1348,33 @@ defmodule BerrypodWeb.PageEditorHook do
defp has_settings_changed?(page, params) do
page.title != (params["title"] || "") or
page.slug != (params["slug"] || "") or
(page[:url_slug] || "") != (params["url_slug"] || "") or
(page.meta_description || "") != (params["meta_description"] || "") or
to_string(page.published) != (params["published"] || "false") or
to_string(page.show_in_nav) != (params["show_in_nav"] || "false") or
(page.nav_label || "") != (params["nav_label"] || "") or
to_string(page.nav_position || 0) != (params["nav_position"] || "0")
to_string(page.nav_position || 0) != (params["nav_position"] || "0") or
has_prefix_changed?(page, params)
end
# Check if URL prefix changed for prefixed pages
defp has_prefix_changed?(page, params) do
case params["url_prefix"] do
nil ->
false
new_prefix ->
prefix_type = prefix_type_for_page(page.slug)
prefix_type != nil and BerrypodWeb.R.prefix(prefix_type) != new_prefix
end
end
defp prefix_type_for_page("collection"), do: :collections
defp prefix_type_for_page("pdp"), do: :products
defp prefix_type_for_page("orders"), do: :orders
defp prefix_type_for_page("order_detail"), do: :orders
defp prefix_type_for_page(_), do: nil
# Initialize settings form from page values
defp init_settings_form(socket) do
page = socket.assigns.page
@@ -1280,6 +1382,7 @@ defmodule BerrypodWeb.PageEditorHook do
form = %{
"title" => page.title || "",
"slug" => page.slug || "",
"url_slug" => page[:url_slug] || "",
"meta_description" => page.meta_description || "",
"published" => to_string(page.published),
"show_in_nav" => to_string(page.show_in_nav),
@@ -1506,6 +1609,15 @@ defmodule BerrypodWeb.PageEditorHook do
defp save_all_tabs(socket) do
socket
|> maybe_save_page()
|> maybe_save_settings()
|> maybe_finish_save()
end
# If settings save triggered a navigation, don't overwrite the socket
defp maybe_finish_save(%{redirected: {:live, _, _}} = socket), do: socket
defp maybe_finish_save(socket) do
socket
|> maybe_save_theme()
|> maybe_save_site()
|> assign(:editor_save_status, :saved)
@@ -1537,6 +1649,124 @@ defmodule BerrypodWeb.PageEditorHook do
end
end
defp maybe_save_settings(socket) do
if socket.assigns[:settings_dirty] do
page = socket.assigns.page
params = socket.assigns[:settings_form] || %{}
cond do
# Custom pages: save all settings including url_slug
page && page.type == "custom" ->
do_save_custom_page_settings(socket, page, params)
# System pages with url_prefix (collections, products, orders)
page && page.type == "system" && params["url_prefix"] ->
do_save_system_page_url_prefix(socket, page, params["url_prefix"])
# System pages: only save url_slug
page && page.type == "system" && params["url_slug"] ->
do_save_system_page_url_slug(socket, page, params["url_slug"])
true ->
socket
end
else
socket
end
end
defp do_save_custom_page_settings(socket, page, params) do
params =
params
|> Map.put_new("published", "false")
|> Map.put_new("show_in_nav", "false")
page_struct = Pages.get_page_struct(page.slug)
url_slug = params["url_slug"]
old_url_slug = page[:url_slug]
case Pages.update_custom_page(page_struct, params) do
{:ok, updated_page} ->
if url_slug != old_url_slug do
Pages.update_page_url_slug(updated_page, url_slug)
end
updated_page = Pages.get_page(updated_page.slug)
socket
|> assign(:page, updated_page)
|> assign(:settings_form, nil)
|> assign(:settings_dirty, false)
|> init_settings_form()
{:error, _changeset} ->
socket
end
end
defp do_save_system_page_url_slug(socket, page, url_slug) do
old_url = Pages.Page.effective_url(page)
case Pages.update_page_url_slug(page.slug, url_slug) do
{:ok, _updated} ->
updated_page = Pages.get_page(page.slug)
new_url = Pages.Page.effective_url(updated_page)
socket =
socket
|> assign(:page, updated_page)
|> assign(:settings_form, nil)
|> assign(:settings_dirty, false)
|> init_settings_form()
# If URL changed, navigate to the new URL
if old_url != new_url do
push_navigate(socket, to: "/#{new_url}?edit=page")
else
socket
end
{:error, _reason} ->
socket
end
end
defp do_save_system_page_url_prefix(socket, page, new_prefix) do
prefix_type = prefix_type_for_page(page.slug)
if prefix_type do
old_prefix = BerrypodWeb.R.prefix(prefix_type)
case Settings.update_url_prefix(prefix_type, new_prefix) do
{:ok, _} ->
socket =
socket
|> assign(:settings_form, nil)
|> assign(:settings_dirty, false)
# If prefix changed, navigate to the new URL
if old_prefix != new_prefix do
# For collection page, navigate to /new-prefix/all
new_path =
case page.slug do
"collection" -> "/#{new_prefix}/all?edit=page"
"pdp" -> "/#{new_prefix}?edit=page"
_ -> "/#{new_prefix}?edit=page"
end
push_navigate(socket, to: new_path)
else
socket
end
{:error, _reason} ->
socket
end
else
socket
end
end
defp maybe_save_theme(socket) do
if socket.assigns[:theme_dirty] do
case Settings.update_theme_settings(Map.from_struct(socket.assigns.theme_editor_settings)) do