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

@@ -129,7 +129,7 @@ defmodule BerrypodWeb.Admin.Newsletter do
<:subtitle>Manage subscribers and email campaigns</:subtitle>
</.header>
<div class="flex gap-2 mt-6 mb-6 border-b border-base-200">
<div class="admin-tabs">
<.tab_link label="Overview" tab="overview" active={@tab} />
<.tab_link
label="Subscribers"
@@ -179,17 +179,10 @@ defmodule BerrypodWeb.Admin.Newsletter do
~H"""
<.link
patch={~p"/admin/newsletter?tab=#{@tab}"}
class={[
"px-3 py-2 text-sm font-medium border-b-2 -mb-px",
if(@tab == @active,
do: "border-base-content text-base-content",
else:
"border-transparent text-base-content/60 hover:text-base-content hover:border-base-300"
)
]}
class={["admin-tab", @tab == @active && "admin-tab-active"]}
>
{@label}
<span :if={@count} class="ml-1 text-xs text-base-content/40">{@count}</span>
<span :if={@count} class="admin-tab-count">{@count}</span>
</.link>
"""
end
@@ -201,52 +194,47 @@ defmodule BerrypodWeb.Admin.Newsletter do
defp overview_tab(assigns) do
~H"""
<div class="space-y-6">
<div class="flex items-center gap-4 p-4 rounded-lg border border-base-200">
<div class="flex-1">
<h3 class="font-medium">Newsletter signups</h3>
<p class="text-sm text-base-content/60 mt-0.5">
When enabled, the newsletter signup block on your shop pages will collect email addresses with double opt-in confirmation.
</p>
</div>
<button
phx-click="toggle_enabled"
class={[
"inline-flex items-center shrink-0 cursor-pointer rounded-full transition-colors",
if(@newsletter_enabled, do: "bg-green-600", else: "bg-base-300")
]}
style="width: 2.75rem; height: 1.5rem; padding: 0.125rem;"
role="switch"
aria-checked={to_string(@newsletter_enabled)}
aria-label="Toggle newsletter signups"
>
<span
class="pointer-events-none inline-block rounded-full bg-white shadow transition-transform"
style={"width: 1.25rem; height: 1.25rem; transform: translateX(#{if @newsletter_enabled, do: "1.25rem", else: "0"})"}
/>
</button>
</div>
<div class="grid grid-cols-3 gap-4">
<div class="p-4 rounded-lg border border-base-200 text-center">
<p class="text-2xl font-bold">{@status_counts["confirmed"] || 0}</p>
<p class="text-sm text-base-content/60">Confirmed</p>
</div>
<div class="p-4 rounded-lg border border-base-200 text-center">
<p class="text-2xl font-bold">{@status_counts["pending"] || 0}</p>
<p class="text-sm text-base-content/60">Pending</p>
</div>
<div class="p-4 rounded-lg border border-base-200 text-center">
<p class="text-2xl font-bold">{@status_counts["unsubscribed"] || 0}</p>
<p class="text-sm text-base-content/60">Unsubscribed</p>
<div class="admin-stack" style="--admin-stack-gap: 1.5rem;">
<div class="admin-card">
<div class="admin-card-body admin-row" style="--admin-row-gap: 1rem;">
<div style="flex: 1;">
<h3 style="font-weight: 500;">Newsletter signups</h3>
<p class="admin-section-desc" style="margin-top: 0.125rem;">
When enabled, the newsletter signup block on your shop pages will collect email addresses with double opt-in confirmation.
</p>
</div>
<button
phx-click="toggle_enabled"
class={["admin-switch", if(@newsletter_enabled, do: "admin-switch-on", else: "admin-switch-off")]}
role="switch"
aria-checked={to_string(@newsletter_enabled)}
aria-label="Toggle newsletter signups"
>
<span class={["admin-switch-thumb", @newsletter_enabled && "admin-switch-thumb-on"]} />
</button>
</div>
</div>
<div class="flex gap-3">
<.link navigate={~p"/admin/newsletter?tab=subscribers"} class="text-sm font-medium underline">
<div class="admin-stats-grid">
<div class="admin-stat-card">
<p class="admin-stat-value">{@status_counts["confirmed"] || 0}</p>
<p class="admin-stat-label">Confirmed</p>
</div>
<div class="admin-stat-card">
<p class="admin-stat-value">{@status_counts["pending"] || 0}</p>
<p class="admin-stat-label">Pending</p>
</div>
<div class="admin-stat-card">
<p class="admin-stat-value">{@status_counts["unsubscribed"] || 0}</p>
<p class="admin-stat-label">Unsubscribed</p>
</div>
</div>
<div class="admin-row" style="--admin-row-gap: 0.75rem;">
<.link navigate={~p"/admin/newsletter?tab=subscribers"} class="admin-link" style="font-weight: 500;">
View subscribers
</.link>
<.link navigate={~p"/admin/newsletter?tab=campaigns"} class="text-sm font-medium underline">
<.link navigate={~p"/admin/newsletter?tab=campaigns"} class="admin-link" style="font-weight: 500;">
View campaigns
</.link>
</div>
@@ -266,8 +254,8 @@ defmodule BerrypodWeb.Admin.Newsletter do
defp subscribers_tab(assigns) do
~H"""
<div>
<div class="flex items-center justify-between mb-4">
<div class="flex gap-2 flex-wrap">
<div class="admin-row" style="justify-content: space-between; margin-bottom: 1rem;">
<div class="admin-cluster">
<.filter_pill
status="all"
label="All"
@@ -298,7 +286,7 @@ defmodule BerrypodWeb.Admin.Newsletter do
</.link>
</div>
<form phx-change="search_subscribers" class="mb-4">
<form phx-change="search_subscribers" style="margin-bottom: 1rem;">
<.input
name="search"
value={@search}
@@ -323,7 +311,7 @@ defmodule BerrypodWeb.Admin.Newsletter do
phx-click="delete_subscriber"
phx-value-id={sub.id}
data-confirm="Permanently delete this subscriber? This cannot be undone."
class="text-sm text-red-600 hover:text-red-800"
class="admin-link-danger"
>
Delete
</button>
@@ -337,10 +325,10 @@ defmodule BerrypodWeb.Admin.Newsletter do
params={%{"tab" => "subscribers"}}
/>
<div :if={@subscriber_count == 0} class="text-center py-12 text-base-content/60">
<.icon name="hero-envelope" class="size-12 mx-auto mb-4" />
<p class="text-lg font-medium">No subscribers yet</p>
<p class="text-sm mt-1">Subscribers will appear here when people sign up via your shop.</p>
<div :if={@subscriber_count == 0} class="admin-empty-state">
<.icon name="hero-envelope" class="admin-empty-state-icon" />
<p class="admin-empty-state-title">No subscribers yet</p>
<p class="admin-empty-state-text">Subscribers will appear here when people sign up via your shop.</p>
</div>
</div>
"""
@@ -355,7 +343,7 @@ defmodule BerrypodWeb.Admin.Newsletter do
defp campaigns_tab(assigns) do
~H"""
<div>
<div class="flex justify-end mb-4">
<div style="display: flex; justify-content: flex-end; margin-bottom: 1rem;">
<.link navigate={~p"/admin/newsletter/campaigns/new"} class="admin-btn admin-btn-primary">
<.icon name="hero-plus" class="size-4" /> New campaign
</.link>
@@ -378,7 +366,7 @@ defmodule BerrypodWeb.Admin.Newsletter do
phx-click="delete_campaign"
phx-value-id={c.id}
data-confirm="Delete this draft campaign?"
class="text-sm text-red-600 hover:text-red-800"
class="admin-link-danger"
>
Delete
</button>
@@ -392,10 +380,10 @@ defmodule BerrypodWeb.Admin.Newsletter do
params={%{"tab" => "campaigns"}}
/>
<div :if={@campaign_count == 0} class="text-center py-12 text-base-content/60">
<.icon name="hero-megaphone" class="size-12 mx-auto mb-4" />
<p class="text-lg font-medium">No campaigns yet</p>
<p class="text-sm mt-1">Create your first campaign to reach your subscribers.</p>
<div :if={@campaign_count == 0} class="admin-empty-state">
<.icon name="hero-megaphone" class="admin-empty-state-icon" />
<p class="admin-empty-state-title">No campaigns yet</p>
<p class="admin-empty-state-text">Create your first campaign to reach your subscribers.</p>
</div>
</div>
"""
@@ -434,31 +422,25 @@ defmodule BerrypodWeb.Admin.Newsletter do
defp subscriber_status(%{status: "confirmed"} = assigns) do
~H"""
<span class="inline-flex items-center gap-1 text-sm text-green-700">
<span class="w-1.5 h-1.5 rounded-full bg-green-500"></span> Confirmed
</span>
<span class="admin-status-dot admin-status-dot-green">Confirmed</span>
"""
end
defp subscriber_status(%{status: "pending"} = assigns) do
~H"""
<span class="inline-flex items-center gap-1 text-sm text-amber-700">
<span class="w-1.5 h-1.5 rounded-full bg-amber-500"></span> Pending
</span>
<span class="admin-status-dot admin-status-dot-amber">Pending</span>
"""
end
defp subscriber_status(%{status: "unsubscribed"} = assigns) do
~H"""
<span class="inline-flex items-center gap-1 text-sm text-base-content/50">
<span class="w-1.5 h-1.5 rounded-full bg-base-300"></span> Unsubscribed
</span>
<span class="admin-status-dot admin-status-dot-muted">Unsubscribed</span>
"""
end
defp subscriber_status(assigns) do
~H"""
<span class="text-sm text-base-content/50">{@status}</span>
<span class="admin-status-dot admin-status-dot-muted">{@status}</span>
"""
end
@@ -466,47 +448,37 @@ defmodule BerrypodWeb.Admin.Newsletter do
defp campaign_status(%{status: "draft"} = assigns) do
~H"""
<span class="inline-flex items-center gap-1.5 text-sm text-base-content/60">
<span class="w-1.5 h-1.5 rounded-full bg-base-300"></span> Draft
</span>
<span class="admin-status-dot admin-status-dot-muted">Draft</span>
"""
end
defp campaign_status(%{status: "scheduled"} = assigns) do
~H"""
<span class="inline-flex items-center gap-1.5 text-sm text-blue-700">
<span class="w-1.5 h-1.5 rounded-full bg-blue-500"></span> Scheduled
</span>
<span class="admin-status-dot admin-status-dot-blue">Scheduled</span>
"""
end
defp campaign_status(%{status: "sending"} = assigns) do
~H"""
<span class="inline-flex items-center gap-1.5 text-sm text-amber-700">
<span class="w-1.5 h-1.5 rounded-full bg-amber-500"></span> Sending
</span>
<span class="admin-status-dot admin-status-dot-amber">Sending</span>
"""
end
defp campaign_status(%{status: "sent"} = assigns) do
~H"""
<span class="inline-flex items-center gap-1.5 text-sm text-green-700">
<span class="w-1.5 h-1.5 rounded-full bg-green-500"></span> Sent
</span>
<span class="admin-status-dot admin-status-dot-green">Sent</span>
"""
end
defp campaign_status(%{status: "cancelled"} = assigns) do
~H"""
<span class="inline-flex items-center gap-1.5 text-sm text-red-700">
<span class="w-1.5 h-1.5 rounded-full bg-red-500"></span> Cancelled
</span>
<span class="admin-status-dot admin-status-dot-red">Cancelled</span>
"""
end
defp campaign_status(assigns) do
~H"""
<span class="text-sm text-base-content/50">{@status}</span>
<span class="admin-status-dot admin-status-dot-muted">{@status}</span>
"""
end