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

@@ -6,6 +6,7 @@ defmodule BerrypodWeb.ShopComponents.Cart do
import BerrypodWeb.ShopComponents.Base
alias Berrypod.Products.{Product, ProductImage}
alias BerrypodWeb.R
defp close_cart_drawer_js do
Phoenix.LiveView.JS.push("close_cart_drawer")
@@ -193,7 +194,7 @@ defmodule BerrypodWeb.ShopComponents.Cart do
>
<%= if @mode != :preview do %>
<.link
patch={"/products/#{@item.product_id}"}
patch={R.product(@item.product_id)}
class={["cart-item-image", !@item.image && "cart-item-image--empty"]}
data-size={if @size == :compact, do: "compact"}
style={if @item.image, do: "background-image: url('#{@item.image}');"}
@@ -212,7 +213,7 @@ defmodule BerrypodWeb.ShopComponents.Cart do
<h3 class="cart-item-name" data-size={if @size == :compact, do: "compact"}>
<%= if @mode != :preview do %>
<.link
patch={"/products/#{@item.product_id}"}
patch={R.product(@item.product_id)}
class="cart-item-name-link"
>
{@item.name}
@@ -321,7 +322,7 @@ defmodule BerrypodWeb.ShopComponents.Cart do
</button>
<% else %>
<.link
patch="/collections/all"
patch={R.collection("all")}
class="cart-continue-link"
>
Continue shopping
@@ -533,7 +534,7 @@ defmodule BerrypodWeb.ShopComponents.Cart do
</.shop_button>
<p class="order-summary-notice">Checkout isn't available yet.</p>
<.shop_link_outline
href="/collections/all"
href={R.collection("all")}
class="order-summary-continue"
>
Continue shopping
@@ -544,7 +545,7 @@ defmodule BerrypodWeb.ShopComponents.Cart do
</.shop_button>
<p class="order-summary-notice">Remove unavailable items to checkout.</p>
<.shop_link_outline
href="/collections/all"
href={R.collection("all")}
class="order-summary-continue"
>
Continue shopping
@@ -557,7 +558,7 @@ defmodule BerrypodWeb.ShopComponents.Cart do
</.shop_button>
</form>
<.shop_link_outline
href="/collections/all"
href={R.collection("all")}
class="order-summary-continue"
>
Continue shopping

View File

@@ -4,6 +4,8 @@ defmodule BerrypodWeb.ShopComponents.Layout do
import BerrypodWeb.ShopComponents.Cart
import BerrypodWeb.ShopComponents.Content
alias BerrypodWeb.R
@doc """
Renders the announcement bar.
@@ -153,10 +155,18 @@ defmodule BerrypodWeb.ShopComponents.Layout do
# Extract a slug from a URL for nav item matching
defp extract_slug_from_url(url) when is_binary(url) do
cond do
url == "/" -> "home"
String.starts_with?(url, "/collections") -> "collection"
String.starts_with?(url, "/products") -> "pdp"
true -> url |> String.trim_leading("/") |> String.split("/") |> List.first() || ""
url == "/" ->
"home"
true ->
# Extract first segment and check if it's a known prefix
first_segment = url |> String.trim_leading("/") |> String.split("/") |> List.first() || ""
case R.prefix_type_from_segment(first_segment) do
:collections -> "collection"
:products -> "pdp"
_ -> first_segment
end
end
end
@@ -651,7 +661,7 @@ defmodule BerrypodWeb.ShopComponents.Layout do
aria-selected="false"
>
<.link
patch={"/products/#{item.product.slug || item.product.id}"}
patch={R.product(item.product.slug || item.product.id)}
class="search-result"
phx-click={Phoenix.LiveView.JS.dispatch("close-search", to: "#search-modal")}
>
@@ -782,7 +792,7 @@ defmodule BerrypodWeb.ShopComponents.Layout do
</a>
<% else %>
<.link
patch={"/collections/#{category.slug}"}
patch={R.collection(category.slug)}
class="mobile-nav-link"
phx-click={
Phoenix.LiveView.JS.dispatch("close-mobile-nav", to: "#mobile-nav-drawer")
@@ -868,7 +878,7 @@ defmodule BerrypodWeb.ShopComponents.Layout do
<% else %>
<li>
<.link
patch="/collections/all"
patch={R.collection("all")}
class="footer-link"
>
All products
@@ -877,7 +887,7 @@ defmodule BerrypodWeb.ShopComponents.Layout do
<%= for category <- @categories do %>
<li>
<.link
patch={"/collections/#{category.slug}"}
patch={R.collection(category.slug)}
class="footer-link"
>
{category.name}
@@ -1013,7 +1023,7 @@ defmodule BerrypodWeb.ShopComponents.Layout do
<.admin_cog_svg />
</.link>
<a
href="/search"
href={R.search()}
phx-click={Phoenix.LiveView.JS.dispatch("open-search", to: "#search-modal")}
class="header-icon-btn"
aria-label="Search"
@@ -1031,7 +1041,7 @@ defmodule BerrypodWeb.ShopComponents.Layout do
</svg>
</a>
<a
href="/cart"
href={R.cart()}
phx-click={open_cart_drawer_js()}
class="header-icon-btn"
aria-label="Cart"
@@ -1241,6 +1251,7 @@ defmodule BerrypodWeb.ShopComponents.Layout do
attr :editor_dirty, :boolean, default: false
attr :theme_dirty, :boolean, default: false
attr :site_dirty, :boolean, default: false
attr :settings_dirty, :boolean, default: false
attr :editor_sheet_state, :atom, default: :collapsed
attr :editor_save_status, :atom, default: :idle
attr :editor_active_tab, :atom, default: :page
@@ -1263,7 +1274,8 @@ defmodule BerrypodWeb.ShopComponents.Layout do
any_editing = assigns.editing || assigns.theme_editing
# Any tab has unsaved changes
any_dirty = assigns.editor_dirty || assigns.theme_dirty || assigns.site_dirty
any_dirty =
assigns.editor_dirty || assigns.theme_dirty || assigns.site_dirty || assigns.settings_dirty
assigns =
assigns

View File

@@ -5,6 +5,7 @@ defmodule BerrypodWeb.ShopComponents.Product do
import BerrypodWeb.ShopComponents.Content, only: [responsive_image: 1]
alias Berrypod.Products.{Product, ProductImage}
alias BerrypodWeb.R
@doc """
Renders a product card with configurable variants.
@@ -98,7 +99,7 @@ defmodule BerrypodWeb.ShopComponents.Product do
product_url =
if assigns.clickable && assigns.mode != :preview do
"/products/#{Map.get(assigns.product, :slug) || Map.get(assigns.product, :id)}"
R.product(Map.get(assigns.product, :slug) || Map.get(assigns.product, :id))
end
assigns =
@@ -157,7 +158,7 @@ defmodule BerrypodWeb.ShopComponents.Product do
</p>
<% else %>
<.link
patch={"/collections/#{Slug.slugify(@product.category)}"}
patch={R.collection(Slug.slugify(@product.category))}
class="product-card-category"
>
{@product.category}
@@ -177,7 +178,7 @@ defmodule BerrypodWeb.ShopComponents.Product do
</a>
<% else %>
<.link
patch={"/products/#{Map.get(@product, :slug) || Map.get(@product, :id)}"}
patch={R.product(Map.get(@product, :slug) || Map.get(@product, :id))}
class="stretched-link"
>
{@product.title}
@@ -629,7 +630,7 @@ defmodule BerrypodWeb.ShopComponents.Product do
</a>
<% else %>
<.link
patch={"/collections/#{category.slug}"}
patch={R.collection(category.slug)}
class="category-card"
>
<div