berrypod/lib/berrypod_web/page_renderer.ex
jamey 24ad3b8b60
Some checks failed
deploy / deploy (push) Has been cancelled
wire order pages and theme preview to page renderer, remove old templates
All 14 pages now render through PageRenderer. Theme editor preview
unified from 10 preview_page clauses to one function + page-context
helpers. PageTemplates module and 10 .heex template files deleted.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 19:32:50 +00:00

868 lines
31 KiB
Elixir

defmodule BerrypodWeb.PageRenderer do
@moduledoc """
Generic page renderer for the page builder.
One `render_page/1` renders ALL pages — no page-specific render functions.
Each page is a flat list of blocks rendered in order. Block components
dispatch to existing shop components.
"""
use Phoenix.Component
use BerrypodWeb.ShopComponents
use Phoenix.VerifiedRoutes,
endpoint: BerrypodWeb.Endpoint,
router: BerrypodWeb.Router,
statics: BerrypodWeb.static_paths()
alias Berrypod.Cart
# ── Public API ──────────────────────────────────────────────────
@doc """
Renders a full page from its block definition.
Expects `@page` (with `:slug` and `:blocks`) plus all the standard
layout assigns (theme_settings, cart_items, etc.).
"""
def render_page(assigns) do
assigns = assign(assigns, :block_assigns, block_assigns(assigns))
~H"""
<.shop_layout
{layout_assigns(assigns)}
active_page={@page.slug}
error_page={@page.slug == "error"}
>
<main id="main-content" class={page_main_class(@page.slug)}>
<div :for={block <- @page.blocks} data-block-type={block["type"]}>
{render_block(Map.merge(@block_assigns, %{block: block, page_slug: @page.slug}))}
</div>
</main>
</.shop_layout>
"""
end
# ── Block dispatch ──────────────────────────────────────────────
defp render_block(%{block: %{"type" => "hero"}} = assigns) do
settings = assigns.block["settings"] || %{}
variant = hero_variant(settings["variant"])
# Page-context assigns (e.g. error_title, error_code) override block settings
assigns =
assigns
|> assign(:title, assigns[:error_title] || settings["title"] || "")
|> assign(:description, assigns[:error_description] || settings["description"] || "")
|> assign(:variant, variant)
|> assign(:background, hero_background(settings["variant"]))
|> assign(:pre_title, assigns[:error_code] || settings["pre_title"])
|> assign(:cta_text, settings["cta_text"])
|> assign(:cta_href, settings["cta_href"])
|> assign(:secondary_cta_text, settings["secondary_cta_text"])
|> assign(:secondary_cta_href, settings["secondary_cta_href"])
~H"""
<.hero_section
title={@title}
description={@description}
variant={@variant}
background={@background}
pre_title={@pre_title}
cta_text={@cta_text}
cta_href={@cta_href}
secondary_cta_text={@secondary_cta_text}
secondary_cta_href={@secondary_cta_href}
mode={@mode}
/>
"""
end
defp render_block(%{block: %{"type" => "category_nav"}} = assigns) do
~H"""
<.category_nav categories={assigns[:categories] || []} mode={@mode} />
"""
end
defp render_block(%{block: %{"type" => "featured_products"}} = assigns) do
settings = assigns.block["settings"] || %{}
assigns =
assigns
|> assign(:section_title, settings["title"] || "Featured products")
|> assign(:layout, settings["layout"] || "section")
|> assign(:card_variant, card_variant(settings["card_variant"]))
|> assign(:columns, grid_columns(settings["columns"]))
~H"""
<%= if @layout == "grid" do %>
<.product_grid columns={@columns} theme_settings={@theme_settings}>
<%= for product <- assigns[:products] || [] do %>
<.product_card
product={product}
theme_settings={@theme_settings}
mode={@mode}
variant={@card_variant}
/>
<% end %>
</.product_grid>
<% else %>
<.featured_products_section
title={@section_title}
products={assigns[:products] || []}
theme_settings={@theme_settings}
mode={@mode}
/>
<% end %>
"""
end
defp render_block(%{block: %{"type" => "image_text"}} = assigns) do
settings = assigns.block["settings"] || %{}
assigns =
assigns
|> assign(:section_title, settings["title"] || "")
|> assign(:section_description, settings["description"] || "")
|> assign(:image_url, settings["image_url"] || "")
|> assign(:link_text, settings["link_text"])
|> assign(:link_href, settings["link_href"])
~H"""
<.image_text_section
title={@section_title}
description={@section_description}
image_url={@image_url}
link_text={@link_text}
link_href={@link_href}
mode={@mode}
/>
"""
end
defp render_block(%{block: %{"type" => "newsletter_card"}} = assigns) do
~H"<.newsletter_card />"
end
defp render_block(%{block: %{"type" => "social_links_card"}} = assigns) do
~H"<.social_links_card />"
end
defp render_block(%{block: %{"type" => "info_card"}} = assigns) do
settings = assigns.block["settings"] || %{}
items =
(settings["items"] || [])
|> Enum.map(fn item ->
%{label: item["label"] || item[:label], value: item["value"] || item[:value]}
end)
assigns =
assigns
|> assign(:card_title, settings["title"] || "")
|> assign(:items, items)
~H"""
<.info_card title={@card_title} items={@items} />
"""
end
defp render_block(%{block: %{"type" => "trust_badges"}} = assigns) do
~H"""
<.trust_badges :if={@theme_settings.pdp_trust_badges} />
"""
end
defp render_block(%{block: %{"type" => "reviews_section"}} = assigns) do
~H"""
<.reviews_section
:if={@theme_settings.pdp_reviews}
reviews={assigns[:reviews] || []}
average_rating={assigns[:average_rating] || 5}
total_count={assigns[:total_count]}
/>
"""
end
# ── PDP blocks ──────────────────────────────────────────────────
defp render_block(%{block: %{"type" => "breadcrumb"}} = assigns) do
~H"""
<.breadcrumb
:if={assigns[:product]}
items={breadcrumb_items(assigns[:product])}
mode={@mode}
/>
"""
end
defp render_block(%{block: %{"type" => "product_hero"}} = assigns) do
~H"""
<%= if assigns[:product] do %>
<div class="pdp-grid">
<.product_gallery images={assigns[:gallery_images] || []} product_name={@product.title} />
<div>
<.product_info product={@product} display_price={assigns[:display_price]} />
<form action="/cart/add" method="post" phx-submit="add_to_cart">
<input type="hidden" name="_csrf_token" value={Phoenix.Controller.get_csrf_token()} />
<input
type="hidden"
name="variant_id"
value={assigns[:selected_variant] && assigns[:selected_variant].id}
/>
<%= for option_type <- assigns[:option_types] || [] do %>
<.variant_selector
option_type={option_type}
selected={(assigns[:selected_options] || %{})[option_type.name]}
available={(assigns[:available_options] || %{})[option_type.name] || []}
mode={@mode}
option_urls={(assigns[:option_urls] || %{})[option_type.name] || %{}}
/>
<% end %>
<div
:if={(assigns[:option_types] || []) == []}
class="pdp-variant-fallback"
>
One size
</div>
<.quantity_selector quantity={assigns[:quantity] || 1} in_stock={@product.in_stock} />
<.add_to_cart_button mode={@mode} />
</form>
</div>
</div>
<% end %>
"""
end
defp render_block(%{block: %{"type" => "product_details"}} = assigns) do
~H"""
<.product_details :if={assigns[:product]} product={@product} />
"""
end
defp render_block(%{block: %{"type" => "related_products"}} = assigns) do
~H"""
<.related_products_section
:if={@theme_settings.pdp_related_products && assigns[:related_products] != []}
products={assigns[:related_products] || []}
theme_settings={@theme_settings}
mode={@mode}
/>
"""
end
# ── Collection blocks ───────────────────────────────────────────
defp render_block(%{block: %{"type" => "collection_header"}} = assigns) do
~H"""
<.collection_header
title={assigns[:collection_title] || "All Products"}
product_count={length(assigns[:products] || [])}
/>
"""
end
defp render_block(%{block: %{"type" => "filter_bar"}} = assigns) do
current_slug =
case assigns[:current_category] do
:sale -> "sale"
nil -> nil
cat -> cat.slug
end
assigns =
assigns
|> assign(:current_slug, current_slug)
|> assign(:current_sort, assigns[:current_sort] || "featured")
|> assign(:sort_options, assigns[:sort_options] || [])
~H"""
<div class="page-container">
<div class="filter-bar">
<nav
aria-label="Collection filters"
id="collection-filters"
phx-hook="CollectionFilters"
class="collection-filters"
>
<ul class="collection-filter-pills">
<li>
<.link
navigate={collection_path("all", @current_sort)}
aria-current={@current_slug == nil && "page"}
class={["collection-filter-pill", @current_slug == nil && "active"]}
>
All
</.link>
</li>
<li>
<.link
navigate={collection_path("sale", @current_sort)}
aria-current={@current_slug == "sale" && "page"}
class={["collection-filter-pill", @current_slug == "sale" && "active"]}
>
Sale
</.link>
</li>
<%= for category <- assigns[:categories] || [] do %>
<li>
<.link
navigate={collection_path(category.slug, @current_sort)}
aria-current={@current_slug == category.slug && "page"}
class={["collection-filter-pill", @current_slug == category.slug && "active"]}
>
{category.name}
</.link>
</li>
<% end %>
</ul>
</nav>
<form
action={~p"/collections/#{@current_slug || "all"}"}
method="get"
phx-change="sort_changed"
>
<.shop_select
name="sort"
options={@sort_options}
selected={@current_sort}
aria-label="Sort products"
/>
<noscript>
<button type="submit" class="themed-button collection-sort-submit">Sort</button>
</noscript>
</form>
</div>
</div>
"""
end
defp render_block(%{block: %{"type" => "product_grid"}} = assigns) do
show_category = assigns[:current_category] in [nil, :sale]
assigns = assign(assigns, :show_category, show_category)
~H"""
<div class="page-container">
<.product_grid theme_settings={@theme_settings}>
<%= for product <- assigns[:products] || [] do %>
<.product_card
product={product}
theme_settings={@theme_settings}
mode={@mode}
variant={:default}
show_category={@show_category}
/>
<% end %>
</.product_grid>
<%= if (assigns[:products] || []) == [] do %>
<div class="collection-empty">
<p>No products found in this collection.</p>
<.link navigate={~p"/collections/all"} class="collection-empty-link">
View all products
</.link>
</div>
<% end %>
</div>
"""
end
# ── Cart blocks ─────────────────────────────────────────────────
defp render_block(%{block: %{"type" => "cart_items"}} = assigns) do
~H"""
<.page_title text="Your basket" />
<%= if @cart_items == [] do %>
<.cart_empty_state mode={@mode} />
<% else %>
<ul role="list" aria-label="Cart items" class="cart-page-list">
<%= for item <- @cart_items do %>
<li>
<.shop_card class="cart-page-card">
<.cart_item_row item={item} size={:default} show_quantity_controls mode={@mode} />
</.shop_card>
</li>
<% end %>
</ul>
<% end %>
"""
end
defp render_block(%{block: %{"type" => "order_summary"}} = assigns) do
~H"""
<%= if @cart_items != [] do %>
<.order_summary
subtotal={assigns[:cart_page_subtotal] || 0}
shipping_estimate={assigns[:shipping_estimate]}
country_code={assigns[:country_code] || "GB"}
available_countries={assigns[:available_countries] || []}
mode={@mode}
/>
<% end %>
"""
end
# ── Contact blocks ──────────────────────────────────────────────
defp render_block(%{block: %{"type" => "contact_form"}} = assigns) do
settings = assigns.block["settings"] || %{}
assigns = assign(assigns, :email, settings["email"])
~H"""
<.contact_form email={@email} />
"""
end
defp render_block(%{block: %{"type" => "order_tracking_card"}} = assigns) do
~H"""
<.order_tracking_card tracking_state={assigns[:tracking_state] || :idle} />
"""
end
# ── Content blocks ──────────────────────────────────────────────
defp render_block(%{block: %{"type" => "content_body"}} = assigns) do
settings = assigns.block["settings"] || %{}
assigns =
assigns
|> assign(:image_src, settings["image_src"])
|> assign(:image_alt, settings["image_alt"] || "")
~H"""
<div class="content-body">
<%= if @image_src && @image_src != "" do %>
<div class="content-image">
<.responsive_image
src={@image_src}
source_width={1200}
alt={@image_alt}
sizes="(max-width: 800px) 100vw, 800px"
class="content-hero-image"
/>
</div>
<% end %>
<.rich_text blocks={assigns[:content_blocks] || []} />
</div>
"""
end
# ── Checkout success ────────────────────────────────────────────
defp render_block(%{block: %{"type" => "checkout_result"}} = assigns) do
~H"""
<%= if assigns[:order] && assigns[:order].payment_status == "paid" do %>
<div class="checkout-header">
<div class="checkout-icon">
<svg
width="32"
height="32"
fill="none"
viewBox="0 0 24 24"
stroke-width="2.5"
stroke="currentColor"
>
<path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5" />
</svg>
</div>
<h1 class="checkout-heading">Thank you for your order</h1>
<p class="checkout-meta">Order <strong>{assigns[:order].order_number}</strong></p>
<%= if assigns[:order].customer_email do %>
<p class="checkout-meta">
A confirmation will be sent to <strong>{assigns[:order].customer_email}</strong>
</p>
<% end %>
</div>
<.shop_card class="checkout-card">
<h2 class="checkout-heading">Order details</h2>
<ul class="checkout-items">
<%= for item <- assigns[:order].items do %>
<li class="checkout-item">
<div>
<p class="checkout-item-name">{item.product_name}</p>
<%= if item.variant_title do %>
<p class="checkout-item-detail">{item.variant_title}</p>
<% end %>
<p class="checkout-item-detail">Qty: {item.quantity}</p>
</div>
<span class="checkout-item-price">
{Cart.format_price(item.unit_price * item.quantity)}
</span>
</li>
<% end %>
</ul>
<div class="checkout-total-border">
<div class="checkout-total">
<span class="checkout-total-label">Total</span>
<span class="checkout-total-amount">{Cart.format_price(assigns[:order].total)}</span>
</div>
</div>
</.shop_card>
<%= if assigns[:order].shipping_address != %{} do %>
<.shop_card class="checkout-card">
<h2 class="checkout-heading">Shipping to</h2>
<div class="checkout-shipping-address">
<p>{assigns[:order].shipping_address["name"]}</p>
<p>{assigns[:order].shipping_address["line1"]}</p>
<%= if assigns[:order].shipping_address["line2"] do %>
<p>{assigns[:order].shipping_address["line2"]}</p>
<% end %>
<p>
{assigns[:order].shipping_address["city"]}, {assigns[:order].shipping_address[
"postal_code"
]}
</p>
<p>{assigns[:order].shipping_address["country"]}</p>
</div>
</.shop_card>
<% end %>
<div class="checkout-actions">
<.shop_link_button href="/collections/all" class="checkout-cta">
Continue shopping
</.shop_link_button>
</div>
<% else %>
<div class="checkout-header">
<div class="checkout-pending-icon">
<span class="checkout-pending-spinner">
<svg
width="32"
height="32"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M12 6v6h4.5m4.5 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"
/>
</svg>
</span>
</div>
<h1 class="checkout-heading">Processing your payment</h1>
<p class="checkout-pending-text">
Please wait while we confirm your payment. This usually takes a few seconds.
</p>
<p class="checkout-pending-hint">
If this page doesn't update, please <.link navigate="/contact" class="checkout-contact-link">contact us</.link>.
</p>
</div>
<% end %>
"""
end
# ── Orders ──────────────────────────────────────────────────────
defp render_block(%{block: %{"type" => "order_card"}} = assigns) do
~H"""
<div class="orders-header">
<h1 class="orders-page-title">Your orders</h1>
<%= if assigns[:lookup_email] do %>
<p class="orders-email-label">
Orders for <strong>{assigns[:lookup_email]}</strong>
</p>
<% end %>
</div>
<%= cond do %>
<% is_nil(assigns[:orders]) -> %>
<div class="orders-empty">
<p>This link has expired or is invalid.</p>
<p class="orders-empty-hint">
Head back to the <.link navigate="/contact">contact page</.link> to request a new one.
</p>
</div>
<% assigns[:orders] == [] -> %>
<div class="orders-empty">
<p>No orders found for that email address.</p>
<p class="orders-empty-hint">
If something doesn't look right, <.link navigate="/contact">get in touch</.link>.
</p>
</div>
<% true -> %>
<div class="orders-list">
<%= for order <- assigns[:orders] do %>
<.link navigate={"/orders/#{order.order_number}"} class="order-summary-card">
<div class="order-summary-top">
<div>
<p class="order-summary-number">{order.order_number}</p>
<p class="order-summary-date">
{Calendar.strftime(order.inserted_at, "%-d %B %Y")}
</p>
</div>
<span class={"order-status-badge order-status-badge--#{order.fulfilment_status}"}>
{format_order_status(order.fulfilment_status)}
</span>
</div>
<ul class="order-summary-items">
<%= for item <- Enum.take(order.items, 2) do %>
<li class="order-summary-item">
{item.quantity}&times; {item.product_name}
<%= if item.variant_title && item.variant_title != "" do %>
<span class="order-summary-variant">&middot; {item.variant_title}</span>
<% end %>
</li>
<% end %>
<%= if length(order.items) > 2 do %>
<li class="order-summary-more">+{length(order.items) - 2} more</li>
<% end %>
</ul>
<div class="order-summary-footer">
<span class="order-summary-total">{Cart.format_price(order.total)}</span>
<span class="order-summary-arrow">&rarr;</span>
</div>
</.link>
<% end %>
</div>
<% end %>
"""
end
# ── Order detail ────────────────────────────────────────────────
defp render_block(%{block: %{"type" => "order_detail_card"}} = assigns) do
~H"""
<%= if assigns[:order] do %>
<div class="order-detail-header">
<.link navigate="/orders" class="order-detail-back">&larr; Back to orders</.link>
<h1 class="checkout-heading" style="margin-top: 1.5rem;">{assigns[:order].order_number}</h1>
<p class="checkout-meta">{Calendar.strftime(assigns[:order].inserted_at, "%-d %B %Y")}</p>
<span class={"order-status-badge order-status-badge--#{assigns[:order].fulfilment_status} order-status-badge--lg"}>
{format_order_status(assigns[:order].fulfilment_status)}
</span>
</div>
<%= if assigns[:order].tracking_number || assigns[:order].tracking_url do %>
<.shop_card class="order-detail-tracking-card">
<div class="order-detail-tracking">
<svg
width="20"
height="20"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M8.25 18.75a1.5 1.5 0 0 1-3 0m3 0a1.5 1.5 0 0 0-3 0m3 0h6m-9 0H3.375a1.125 1.125 0 0 1-1.125-1.125V14.25m17.25 4.5a1.5 1.5 0 0 1-3 0m3 0a1.5 1.5 0 0 0-3 0m3 0h1.125c.621 0 1.129-.504 1.09-1.124a17.902 17.902 0 0 0-3.213-9.193 2.056 2.056 0 0 0-1.58-.86H14.25M16.5 18.75h-2.25m0-11.177v-.958c0-.568-.422-1.048-.987-1.106a48.554 48.554 0 0 0-10.026 0 1.106 1.106 0 0 0-.987 1.106v7.635m12-6.677v6.677m0 4.5v-4.5m0 0h-12"
/>
</svg>
<div>
<p class="order-detail-tracking-label">Shipment tracking</p>
<%= if assigns[:order].tracking_number do %>
<p class="order-detail-tracking-number">{assigns[:order].tracking_number}</p>
<% end %>
</div>
<%= if assigns[:order].tracking_url do %>
<a
href={assigns[:order].tracking_url}
target="_blank"
rel="noopener noreferrer"
class="order-detail-tracking-btn themed-button"
>
Track parcel
</a>
<% end %>
</div>
</.shop_card>
<% end %>
<.shop_card class="checkout-card">
<h2 class="checkout-heading">Items ordered</h2>
<ul class="checkout-items">
<%= for item <- assigns[:order].items do %>
<% info = (assigns[:thumbnails] || %{})[item.variant_id] %>
<li class="checkout-item">
<%= if info && info.thumb do %>
<img src={info.thumb} alt={item.product_name} class="checkout-item-thumb" />
<% end %>
<div>
<%= if info && info.slug do %>
<.link
navigate={"/products/#{info.slug}"}
class="checkout-item-name checkout-item-link"
>
{item.product_name}
</.link>
<% else %>
<p class="checkout-item-name">{item.product_name}</p>
<% end %>
<%= if item.variant_title && item.variant_title != "" do %>
<p class="checkout-item-detail">{item.variant_title}</p>
<% end %>
<p class="checkout-item-detail">Qty: {item.quantity}</p>
</div>
<span class="checkout-item-price">
{Cart.format_price(item.unit_price * item.quantity)}
</span>
</li>
<% end %>
</ul>
<div class="checkout-total-border">
<div class="checkout-total">
<span class="checkout-total-label">Total</span>
<span class="checkout-total-amount">{Cart.format_price(assigns[:order].total)}</span>
</div>
</div>
</.shop_card>
<%= if assigns[:order].shipping_address != %{} do %>
<.shop_card class="checkout-card">
<h2 class="checkout-heading">Shipping to</h2>
<div class="checkout-shipping-address">
<p>{assigns[:order].shipping_address["name"]}</p>
<p>{assigns[:order].shipping_address["line1"]}</p>
<%= if assigns[:order].shipping_address["line2"] do %>
<p>{assigns[:order].shipping_address["line2"]}</p>
<% end %>
<p>
{assigns[:order].shipping_address["city"]}, {assigns[:order].shipping_address[
"postal_code"
]}
</p>
<p>{assigns[:order].shipping_address["country"]}</p>
</div>
</.shop_card>
<% end %>
<div class="checkout-actions">
<.shop_link_button href="/collections/all">Continue shopping</.shop_link_button>
</div>
<% end %>
"""
end
# ── Search ──────────────────────────────────────────────────────
defp render_block(%{block: %{"type" => "search_results"}} = assigns) do
~H"""
<.page_title text="Search" />
<form action="/search" method="get" phx-submit="search_submit" class="search-page-form">
<input
type="search"
name="q"
value={assigns[:search_page_query] || ""}
placeholder="Search products..."
class="themed-input"
/>
<button type="submit" class="themed-button">Search</button>
</form>
<%= if (assigns[:search_page_results] || []) != [] do %>
<p class="search-page-count">
{length(assigns[:search_page_results])} {if length(assigns[:search_page_results]) == 1,
do: "result",
else: "results"} for "{assigns[:search_page_query]}"
</p>
<.product_grid theme_settings={@theme_settings}>
<%= for product <- assigns[:search_page_results] do %>
<.product_card
product={product}
theme_settings={@theme_settings}
mode={@mode}
variant={:default}
/>
<% end %>
</.product_grid>
<% else %>
<%= if (assigns[:search_page_query] || "") != "" do %>
<div class="collection-empty">
<p>No products found for &ldquo;{assigns[:search_page_query]}&rdquo;</p>
<.link navigate="/collections/all" class="collection-empty-link">Browse all products</.link>
</div>
<% end %>
<% end %>
"""
end
# ── Fallback ────────────────────────────────────────────────────
defp render_block(assigns) do
~H"""
<%!-- Unknown block type: {assigns.block["type"]} --%>
"""
end
# ── Helpers ─────────────────────────────────────────────────────
@doc "Returns a CSS class for the page's `<main>` element."
def page_main_class("contact"), do: "page-container contact-main"
def page_main_class("cart"), do: "page-container"
def page_main_class("checkout_success"), do: "page-container checkout-main"
def page_main_class("orders"), do: "page-container orders-main"
def page_main_class("order_detail"), do: "page-container order-detail-main"
def page_main_class("error"), do: "error-main"
def page_main_class("collection"), do: nil
def page_main_class("pdp"), do: "page-container"
def page_main_class("search"), do: "page-container"
def page_main_class("about"), do: "content-page"
def page_main_class("delivery"), do: "content-page"
def page_main_class("privacy"), do: "content-page"
def page_main_class("terms"), do: "content-page"
def page_main_class(_), do: nil
# Extracts the assigns that individual blocks need (everything except page metadata).
# Preserves __changed__ so assign/3 works inside render_block functions.
defp block_assigns(assigns) do
Map.drop(assigns, [:page, :block_assigns])
end
defp hero_variant("page"), do: :page
defp hero_variant("sunken"), do: :default
defp hero_variant("error"), do: :error
defp hero_variant(_), do: :default
defp hero_background("sunken"), do: :sunken
defp hero_background(_), do: :base
defp card_variant("minimal"), do: :minimal
defp card_variant("compact"), do: :compact
defp card_variant("default"), do: :default
defp card_variant(_), do: :featured
defp grid_columns("fixed-4"), do: :fixed_4
defp grid_columns(_), do: nil
defp breadcrumb_items(%{category: cat, title: title}) when not is_nil(cat) do
slug = cat |> String.downcase() |> String.replace(" ", "-")
[
%{label: cat, page: "collection", href: "/collections/#{slug}"},
%{label: title, current: true}
]
end
defp breadcrumb_items(%{title: title}) do
[%{label: title, current: true}]
end
defp breadcrumb_items(_), do: []
defp collection_path(slug, "featured"), do: ~p"/collections/#{slug}"
defp collection_path(slug, sort), do: ~p"/collections/#{slug}?sort=#{sort}"
def format_order_status("unfulfilled"), do: "Being prepared"
def format_order_status("submitted"), do: "Sent to printer"
def format_order_status("processing"), do: "In production"
def format_order_status("shipped"), do: "On its way"
def format_order_status("delivered"), do: "Delivered"
def format_order_status("failed"), do: "Issue — contact us"
def format_order_status("cancelled"), do: "Cancelled"
def format_order_status(status), do: status
end