refactor admin CSS: replace utility classes with semantic styles

Replace Tailwind utility soup across admin templates with semantic
CSS classes. Add layout primitives (stack, row, cluster, grid),
extract JS transition helpers into transitions.css, and refactor
core_components, layouts, settings, newsletter, order_show, providers,
and theme editor templates.

Utility occurrences reduced from 290+ to 127 across admin files.
All 1679 tests pass.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
jamey
2026-03-01 17:15:25 +00:00
parent edef628214
commit b7ec41b0cf
13 changed files with 2661 additions and 1643 deletions

View File

@@ -39,19 +39,16 @@ defmodule BerrypodWeb.Admin.OrderShow do
def render(assigns) do
~H"""
<.header>
<.link
navigate={~p"/admin/orders"}
class="text-sm font-normal text-base-content/60 hover:underline"
>
<.link navigate={~p"/admin/orders"} class="admin-link-subtle" style="font-weight: 400;">
&larr; Orders
</.link>
<div class="flex items-center gap-3 mt-1">
<span class="text-2xl font-bold">{@order.order_number}</span>
<div class="admin-row" style="--admin-row-gap: 0.75rem; margin-top: 0.25rem;">
<span style="font-size: 1.5rem; font-weight: 700;">{@order.order_number}</span>
<.status_badge status={@order.payment_status} />
</div>
</.header>
<div class="grid gap-6 mt-6 lg:grid-cols-2">
<div class="admin-grid" style="--admin-grid-min: 20rem; --admin-grid-gap: 1.5rem; margin-top: 1.5rem;">
<%!-- order info --%>
<div class="admin-card">
<div class="admin-card-body">
@@ -63,7 +60,7 @@ defmodule BerrypodWeb.Admin.OrderShow do
<.status_badge status={@order.payment_status} />
</:item>
<:item :if={@order.stripe_payment_intent_id} title="Stripe payment">
<code class="text-xs">{@order.stripe_payment_intent_id}</code>
<code style="font-size: 0.75rem;">{@order.stripe_payment_intent_id}</code>
</:item>
<:item title="Currency">{String.upcase(@order.currency)}</:item>
</.list>
@@ -99,20 +96,20 @@ defmodule BerrypodWeb.Admin.OrderShow do
</:item>
</.list>
<% else %>
<p class="text-base-content/60 text-sm">No shipping address provided</p>
<p class="admin-section-desc" style="margin-top: 0;">No shipping address provided</p>
<% end %>
</div>
</div>
</div>
<%!-- timeline --%>
<div class="admin-card mt-6">
<div class="admin-card" style="margin-top: 1.5rem;">
<div class="admin-card-body">
<div class="flex items-center justify-between">
<div class="admin-row" style="justify-content: space-between;">
<h3 class="admin-card-title">Timeline</h3>
<.fulfilment_badge status={@order.fulfilment_status} />
</div>
<div class="flex gap-2 mt-2 mb-4">
<div class="admin-row" style="margin-top: 0.5rem; margin-bottom: 1rem;">
<button
:if={can_submit?(@order)}
phx-click="submit_to_provider"
@@ -133,16 +130,17 @@ defmodule BerrypodWeb.Admin.OrderShow do
</div>
<div
:if={@order.tracking_number not in [nil, ""]}
class="flex items-center gap-2 mb-4 text-sm"
class="admin-row"
style="margin-bottom: 1rem; font-size: 0.875rem;"
>
<.icon name="hero-truck-mini" class="size-4 text-base-content/60" />
<span class="font-medium">{@order.tracking_number}</span>
<span class="admin-text-secondary"><.icon name="hero-truck-mini" class="size-4" /></span>
<span style="font-weight: 500;">{@order.tracking_number}</span>
<a
:if={@order.tracking_url not in [nil, ""]}
href={@order.tracking_url}
target="_blank"
rel="noopener"
class="text-primary hover:underline"
class="admin-link"
>
Track shipment &rarr;
</a>
@@ -152,7 +150,7 @@ defmodule BerrypodWeb.Admin.OrderShow do
</div>
<%!-- line items --%>
<div class="admin-card mt-6">
<div class="admin-card" style="margin-top: 1.5rem;">
<div class="admin-card-body">
<h3 class="admin-card-title">Items</h3>
<table class="admin-table admin-table-zebra">
@@ -160,28 +158,28 @@ defmodule BerrypodWeb.Admin.OrderShow do
<tr>
<th>Product</th>
<th>Variant</th>
<th class="text-right">Qty</th>
<th class="text-right">Unit price</th>
<th class="text-right">Total</th>
<th style="text-align: end;">Qty</th>
<th style="text-align: end;">Unit price</th>
<th style="text-align: end;">Total</th>
</tr>
</thead>
<tbody>
<tr :for={item <- @order.items}>
<td>{item.product_name}</td>
<td>{item.variant_title}</td>
<td class="text-right">{item.quantity}</td>
<td class="text-right">{Cart.format_price(item.unit_price)}</td>
<td class="text-right">{Cart.format_price(item.unit_price * item.quantity)}</td>
<td style="text-align: end;">{item.quantity}</td>
<td style="text-align: end;">{Cart.format_price(item.unit_price)}</td>
<td style="text-align: end;">{Cart.format_price(item.unit_price * item.quantity)}</td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="4" class="text-right font-medium">Subtotal</td>
<td class="text-right font-medium">{Cart.format_price(@order.subtotal)}</td>
<td colspan="4" style="text-align: end; font-weight: 500;">Subtotal</td>
<td style="text-align: end; font-weight: 500;">{Cart.format_price(@order.subtotal)}</td>
</tr>
<tr class="text-lg">
<td colspan="4" class="text-right font-bold">Total</td>
<td class="text-right font-bold">{Cart.format_price(@order.total)}</td>
<tr style="font-size: 1.125rem;">
<td colspan="4" style="text-align: end; font-weight: 700;">Total</td>
<td style="text-align: end; font-weight: 700;">{Cart.format_price(@order.total)}</td>
</tr>
</tfoot>
</table>
@@ -236,7 +234,7 @@ defmodule BerrypodWeb.Admin.OrderShow do
defp order_timeline(assigns) do
~H"""
<div :if={@entries == []} class="text-sm text-base-content/60 py-4">
<div :if={@entries == []} class="admin-section-desc" style="padding-block: 1rem; margin-top: 0;">
No activity recorded yet.
</div>
<ol :if={@entries != []} class="admin-timeline" id="order-timeline">
@@ -279,76 +277,40 @@ defmodule BerrypodWeb.Admin.OrderShow do
end
defp fulfilment_badge(assigns) do
{bg, text, ring, icon} =
{color, icon} =
case assigns.status do
"submitted" ->
{"bg-blue-50", "text-blue-700", "ring-blue-600/20", "hero-paper-airplane-mini"}
"processing" ->
{"bg-amber-50", "text-amber-700", "ring-amber-600/20", "hero-cog-6-tooth-mini"}
"shipped" ->
{"bg-purple-50", "text-purple-700", "ring-purple-600/20", "hero-truck-mini"}
"delivered" ->
{"bg-green-50", "text-green-700", "ring-green-600/20", "hero-check-circle-mini"}
"failed" ->
{"bg-red-50", "text-red-700", "ring-red-600/20", "hero-x-circle-mini"}
"cancelled" ->
{"bg-base-200/50", "text-base-content/60", "ring-base-content/10",
"hero-no-symbol-mini"}
_ ->
{"bg-base-200/50", "text-base-content/60", "ring-base-content/10",
"hero-minus-circle-mini"}
"submitted" -> {"admin-status-pill-blue", "hero-paper-airplane-mini"}
"processing" -> {"admin-status-pill-amber", "hero-cog-6-tooth-mini"}
"shipped" -> {"admin-status-pill-purple", "hero-truck-mini"}
"delivered" -> {"admin-status-pill-green", "hero-check-circle-mini"}
"failed" -> {"admin-status-pill-red", "hero-x-circle-mini"}
"cancelled" -> {"admin-status-pill-zinc", "hero-no-symbol-mini"}
_ -> {"admin-status-pill-zinc", "hero-minus-circle-mini"}
end
assigns = assign(assigns, bg: bg, text: text, ring: ring, icon: icon)
assigns = assign(assigns, color: color, icon: icon)
~H"""
<span class={[
"inline-flex items-center gap-1 rounded-full px-2 py-1 text-xs font-medium ring-1 ring-inset",
@bg,
@text,
@ring
]}>
<span class={["admin-status-pill", @color]}>
<.icon name={@icon} class="size-3" /> {@status}
</span>
"""
end
defp status_badge(assigns) do
{bg, text, ring, icon} =
{color, icon} =
case assigns.status do
"paid" ->
{"bg-green-50", "text-green-700", "ring-green-600/20", "hero-check-circle-mini"}
"pending" ->
{"bg-amber-50", "text-amber-700", "ring-amber-600/20", "hero-clock-mini"}
"failed" ->
{"bg-red-50", "text-red-700", "ring-red-600/20", "hero-x-circle-mini"}
"refunded" ->
{"bg-base-200/50", "text-base-content/60", "ring-base-content/10",
"hero-arrow-uturn-left-mini"}
_ ->
{"bg-base-200/50", "text-base-content/60", "ring-base-content/10",
"hero-question-mark-circle-mini"}
"paid" -> {"admin-status-pill-green", "hero-check-circle-mini"}
"pending" -> {"admin-status-pill-amber", "hero-clock-mini"}
"failed" -> {"admin-status-pill-red", "hero-x-circle-mini"}
"refunded" -> {"admin-status-pill-zinc", "hero-arrow-uturn-left-mini"}
_ -> {"admin-status-pill-zinc", "hero-question-mark-circle-mini"}
end
assigns = assign(assigns, bg: bg, text: text, ring: ring, icon: icon)
assigns = assign(assigns, color: color, icon: icon)
~H"""
<span class={[
"inline-flex items-center gap-1 rounded-full px-2 py-1 text-xs font-medium ring-1 ring-inset",
@bg,
@text,
@ring
]}>
<span class={["admin-status-pill", @color]}>
<.icon name={@icon} class="size-3" /> {@status}
</span>
"""