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:
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user