2026-02-28 23:25:28 +00:00
|
|
|
defmodule BerrypodWeb.Admin.Newsletter do
|
|
|
|
|
use BerrypodWeb, :live_view
|
|
|
|
|
|
|
|
|
|
alias Berrypod.Newsletter
|
|
|
|
|
|
|
|
|
|
@impl true
|
|
|
|
|
def mount(_params, _session, socket) do
|
|
|
|
|
counts = Newsletter.count_subscribers_by_status()
|
2026-03-01 09:42:34 +00:00
|
|
|
sub_page = Newsletter.list_subscribers_paginated(page: 1)
|
|
|
|
|
camp_page = Newsletter.list_campaigns_paginated(page: 1)
|
2026-02-28 23:25:28 +00:00
|
|
|
|
|
|
|
|
{:ok,
|
|
|
|
|
socket
|
|
|
|
|
|> assign(:page_title, "Newsletter")
|
|
|
|
|
|> assign(:tab, "overview")
|
|
|
|
|
|> assign(:newsletter_enabled, Newsletter.newsletter_enabled?())
|
|
|
|
|
|> assign(:status_counts, counts)
|
2026-03-01 09:42:34 +00:00
|
|
|
|> assign(:subscriber_pagination, sub_page)
|
|
|
|
|
|> assign(:subscriber_count, sub_page.total_count)
|
|
|
|
|
|> assign(:campaign_pagination, camp_page)
|
|
|
|
|
|> assign(:campaign_count, camp_page.total_count)
|
2026-02-28 23:25:28 +00:00
|
|
|
|> assign(:status_filter, "all")
|
|
|
|
|
|> assign(:search, "")
|
2026-03-01 09:42:34 +00:00
|
|
|
|> stream(:subscribers, sub_page.items)
|
|
|
|
|
|> stream(:campaigns, camp_page.items)}
|
2026-02-28 23:25:28 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
@impl true
|
2026-03-01 09:42:34 +00:00
|
|
|
def handle_params(params, _uri, socket) do
|
|
|
|
|
tab =
|
|
|
|
|
if params["tab"] in ~w(overview subscribers campaigns), do: params["tab"], else: "overview"
|
|
|
|
|
|
|
|
|
|
page_num = Berrypod.Pagination.parse_page(params)
|
|
|
|
|
|
2026-02-28 23:25:28 +00:00
|
|
|
socket = assign(socket, :tab, tab)
|
|
|
|
|
|
|
|
|
|
socket =
|
|
|
|
|
case tab do
|
|
|
|
|
"subscribers" ->
|
2026-03-01 09:42:34 +00:00
|
|
|
page =
|
|
|
|
|
Newsletter.list_subscribers_paginated(
|
2026-02-28 23:25:28 +00:00
|
|
|
status: socket.assigns.status_filter,
|
2026-03-01 09:42:34 +00:00
|
|
|
search: socket.assigns.search,
|
|
|
|
|
page: page_num
|
2026-02-28 23:25:28 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
socket
|
2026-03-01 09:42:34 +00:00
|
|
|
|> assign(:subscriber_pagination, page)
|
|
|
|
|
|> assign(:subscriber_count, page.total_count)
|
|
|
|
|
|> stream(:subscribers, page.items, reset: true)
|
2026-02-28 23:25:28 +00:00
|
|
|
|
|
|
|
|
"campaigns" ->
|
2026-03-01 09:42:34 +00:00
|
|
|
page = Newsletter.list_campaigns_paginated(page: page_num)
|
2026-02-28 23:25:28 +00:00
|
|
|
|
|
|
|
|
socket
|
2026-03-01 09:42:34 +00:00
|
|
|
|> assign(:campaign_pagination, page)
|
|
|
|
|
|> assign(:campaign_count, page.total_count)
|
|
|
|
|
|> stream(:campaigns, page.items, reset: true)
|
2026-02-28 23:25:28 +00:00
|
|
|
|
|
|
|
|
_ ->
|
|
|
|
|
socket
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
{:noreply, socket}
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
@impl true
|
|
|
|
|
def handle_event("toggle_enabled", _params, socket) do
|
|
|
|
|
new_value = !socket.assigns.newsletter_enabled
|
|
|
|
|
Newsletter.set_newsletter_enabled(new_value)
|
|
|
|
|
|
|
|
|
|
msg = if new_value, do: "Newsletter signups enabled", else: "Newsletter signups disabled"
|
|
|
|
|
|
|
|
|
|
{:noreply,
|
|
|
|
|
socket
|
|
|
|
|
|> assign(:newsletter_enabled, new_value)
|
|
|
|
|
|> put_flash(:info, msg)}
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def handle_event("filter_subscribers", %{"status" => status}, socket) do
|
|
|
|
|
{:noreply,
|
|
|
|
|
socket
|
|
|
|
|
|> assign(:status_filter, status)
|
2026-03-01 09:42:34 +00:00
|
|
|
|> push_patch(to: ~p"/admin/newsletter?tab=subscribers")}
|
2026-02-28 23:25:28 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def handle_event("search_subscribers", %{"search" => term}, socket) do
|
|
|
|
|
{:noreply,
|
|
|
|
|
socket
|
|
|
|
|
|> assign(:search, term)
|
2026-03-01 09:42:34 +00:00
|
|
|
|> push_patch(to: ~p"/admin/newsletter?tab=subscribers")}
|
2026-02-28 23:25:28 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def handle_event("delete_subscriber", %{"id" => id}, socket) do
|
|
|
|
|
sub = Newsletter.get_subscriber!(id)
|
|
|
|
|
{:ok, _} = Newsletter.delete_subscriber(sub)
|
|
|
|
|
|
|
|
|
|
counts = Newsletter.count_subscribers_by_status()
|
|
|
|
|
|
|
|
|
|
{:noreply,
|
|
|
|
|
socket
|
|
|
|
|
|> assign(:status_counts, counts)
|
|
|
|
|
|> assign(:subscriber_count, socket.assigns.subscriber_count - 1)
|
|
|
|
|
|> stream_delete(:subscribers, sub)
|
|
|
|
|
|> put_flash(:info, "Subscriber deleted")}
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def handle_event("delete_campaign", %{"id" => id}, socket) do
|
|
|
|
|
campaign = Newsletter.get_campaign!(id)
|
|
|
|
|
|
|
|
|
|
case Newsletter.delete_campaign(campaign) do
|
|
|
|
|
{:ok, _} ->
|
|
|
|
|
{:noreply,
|
|
|
|
|
socket
|
|
|
|
|
|> assign(:campaign_count, socket.assigns.campaign_count - 1)
|
|
|
|
|
|> stream_delete(:campaigns, campaign)
|
|
|
|
|
|> put_flash(:info, "Campaign deleted")}
|
|
|
|
|
|
|
|
|
|
{:error, :not_draft} ->
|
|
|
|
|
{:noreply, put_flash(socket, :error, "Only draft campaigns can be deleted")}
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
@impl true
|
|
|
|
|
def render(assigns) do
|
|
|
|
|
~H"""
|
|
|
|
|
<.header>
|
|
|
|
|
Newsletter
|
|
|
|
|
<:subtitle>Manage subscribers and email campaigns</:subtitle>
|
|
|
|
|
</.header>
|
|
|
|
|
|
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>
2026-03-01 17:15:25 +00:00
|
|
|
<div class="admin-tabs">
|
2026-02-28 23:25:28 +00:00
|
|
|
<.tab_link label="Overview" tab="overview" active={@tab} />
|
|
|
|
|
<.tab_link
|
|
|
|
|
label="Subscribers"
|
|
|
|
|
tab="subscribers"
|
|
|
|
|
active={@tab}
|
|
|
|
|
count={total_subs(@status_counts)}
|
|
|
|
|
/>
|
|
|
|
|
<.tab_link label="Campaigns" tab="campaigns" active={@tab} count={@campaign_count} />
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div :if={@tab == "overview"}>
|
|
|
|
|
<.overview_tab
|
|
|
|
|
newsletter_enabled={@newsletter_enabled}
|
|
|
|
|
status_counts={@status_counts}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div :if={@tab == "subscribers"}>
|
|
|
|
|
<.subscribers_tab
|
|
|
|
|
streams={@streams}
|
|
|
|
|
status_filter={@status_filter}
|
|
|
|
|
status_counts={@status_counts}
|
|
|
|
|
subscriber_count={@subscriber_count}
|
2026-03-01 09:42:34 +00:00
|
|
|
subscriber_pagination={@subscriber_pagination}
|
2026-02-28 23:25:28 +00:00
|
|
|
search={@search}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div :if={@tab == "campaigns"}>
|
2026-03-01 09:42:34 +00:00
|
|
|
<.campaigns_tab
|
|
|
|
|
streams={@streams}
|
|
|
|
|
campaign_count={@campaign_count}
|
|
|
|
|
campaign_pagination={@campaign_pagination}
|
|
|
|
|
/>
|
2026-02-28 23:25:28 +00:00
|
|
|
</div>
|
|
|
|
|
"""
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
# ── Tab navigation ──────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
attr :label, :string, required: true
|
|
|
|
|
attr :tab, :string, required: true
|
|
|
|
|
attr :active, :string, required: true
|
|
|
|
|
attr :count, :integer, default: nil
|
|
|
|
|
|
|
|
|
|
defp tab_link(assigns) do
|
|
|
|
|
~H"""
|
|
|
|
|
<.link
|
|
|
|
|
patch={~p"/admin/newsletter?tab=#{@tab}"}
|
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>
2026-03-01 17:15:25 +00:00
|
|
|
class={["admin-tab", @tab == @active && "admin-tab-active"]}
|
2026-02-28 23:25:28 +00:00
|
|
|
>
|
|
|
|
|
{@label}
|
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>
2026-03-01 17:15:25 +00:00
|
|
|
<span :if={@count} class="admin-tab-count">{@count}</span>
|
2026-02-28 23:25:28 +00:00
|
|
|
</.link>
|
|
|
|
|
"""
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
# ── Overview tab ────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
attr :newsletter_enabled, :boolean, required: true
|
|
|
|
|
attr :status_counts, :map, required: true
|
|
|
|
|
|
|
|
|
|
defp overview_tab(assigns) do
|
|
|
|
|
~H"""
|
complete admin CSS refactor: delete utilities.css, add layout primitives
- Delete utilities.css (701 lines / 24 KB of Tailwind utility clones)
- Add layout.css with admin-stack, admin-row, admin-cluster, admin-grid
primitives and gap variants (sm, md, lg, xl)
- Add transitions.css import and layout.css import to admin.css entry point
- Replace all Tailwind utility classes across 26 admin templates with
semantic admin-*/theme-*/page-specific CSS classes
- Replace all non-dynamic inline styles with semantic classes
- Add ~100 new semantic classes to components.css (analytics, dashboard,
order detail, settings, theme editor, generic utilities)
- Fix stray text-error → admin-text-error in media.ex
- Add missing .truncate definition to admin CSS
- Only remaining inline styles are dynamic data values (progress bars,
chart dimensions) and one JS.toggle target
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 21:40:21 +00:00
|
|
|
<div class="admin-stack admin-stack-lg">
|
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>
2026-03-01 17:15:25 +00:00
|
|
|
<div class="admin-card">
|
complete admin CSS refactor: delete utilities.css, add layout primitives
- Delete utilities.css (701 lines / 24 KB of Tailwind utility clones)
- Add layout.css with admin-stack, admin-row, admin-cluster, admin-grid
primitives and gap variants (sm, md, lg, xl)
- Add transitions.css import and layout.css import to admin.css entry point
- Replace all Tailwind utility classes across 26 admin templates with
semantic admin-*/theme-*/page-specific CSS classes
- Replace all non-dynamic inline styles with semantic classes
- Add ~100 new semantic classes to components.css (analytics, dashboard,
order detail, settings, theme editor, generic utilities)
- Fix stray text-error → admin-text-error in media.ex
- Add missing .truncate definition to admin CSS
- Only remaining inline styles are dynamic data values (progress bars,
chart dimensions) and one JS.toggle target
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 21:40:21 +00:00
|
|
|
<div class="admin-card-body admin-row admin-row-xl">
|
|
|
|
|
<div class="admin-input-fill">
|
|
|
|
|
<h3 class="admin-text-medium">Newsletter signups</h3>
|
|
|
|
|
<p class="admin-section-desc">
|
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>
2026-03-01 17:15:25 +00:00
|
|
|
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"
|
2026-03-01 19:39:56 +00:00
|
|
|
class={[
|
|
|
|
|
"admin-switch",
|
|
|
|
|
if(@newsletter_enabled, do: "admin-switch-on", else: "admin-switch-off")
|
|
|
|
|
]}
|
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>
2026-03-01 17:15:25 +00:00
|
|
|
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>
|
2026-02-28 23:25:28 +00:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
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>
2026-03-01 17:15:25 +00:00
|
|
|
<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>
|
2026-02-28 23:25:28 +00:00
|
|
|
</div>
|
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>
2026-03-01 17:15:25 +00:00
|
|
|
<div class="admin-stat-card">
|
|
|
|
|
<p class="admin-stat-value">{@status_counts["pending"] || 0}</p>
|
|
|
|
|
<p class="admin-stat-label">Pending</p>
|
2026-02-28 23:25:28 +00:00
|
|
|
</div>
|
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>
2026-03-01 17:15:25 +00:00
|
|
|
<div class="admin-stat-card">
|
|
|
|
|
<p class="admin-stat-value">{@status_counts["unsubscribed"] || 0}</p>
|
|
|
|
|
<p class="admin-stat-label">Unsubscribed</p>
|
2026-02-28 23:25:28 +00:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
complete admin CSS refactor: delete utilities.css, add layout primitives
- Delete utilities.css (701 lines / 24 KB of Tailwind utility clones)
- Add layout.css with admin-stack, admin-row, admin-cluster, admin-grid
primitives and gap variants (sm, md, lg, xl)
- Add transitions.css import and layout.css import to admin.css entry point
- Replace all Tailwind utility classes across 26 admin templates with
semantic admin-*/theme-*/page-specific CSS classes
- Replace all non-dynamic inline styles with semantic classes
- Add ~100 new semantic classes to components.css (analytics, dashboard,
order detail, settings, theme editor, generic utilities)
- Fix stray text-error → admin-text-error in media.ex
- Add missing .truncate definition to admin CSS
- Only remaining inline styles are dynamic data values (progress bars,
chart dimensions) and one JS.toggle target
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 21:40:21 +00:00
|
|
|
<div class="admin-row admin-row-lg">
|
2026-03-01 19:39:56 +00:00
|
|
|
<.link
|
|
|
|
|
navigate={~p"/admin/newsletter?tab=subscribers"}
|
complete admin CSS refactor: delete utilities.css, add layout primitives
- Delete utilities.css (701 lines / 24 KB of Tailwind utility clones)
- Add layout.css with admin-stack, admin-row, admin-cluster, admin-grid
primitives and gap variants (sm, md, lg, xl)
- Add transitions.css import and layout.css import to admin.css entry point
- Replace all Tailwind utility classes across 26 admin templates with
semantic admin-*/theme-*/page-specific CSS classes
- Replace all non-dynamic inline styles with semantic classes
- Add ~100 new semantic classes to components.css (analytics, dashboard,
order detail, settings, theme editor, generic utilities)
- Fix stray text-error → admin-text-error in media.ex
- Add missing .truncate definition to admin CSS
- Only remaining inline styles are dynamic data values (progress bars,
chart dimensions) and one JS.toggle target
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 21:40:21 +00:00
|
|
|
class="admin-link admin-text-medium"
|
2026-03-01 19:39:56 +00:00
|
|
|
>
|
2026-02-28 23:25:28 +00:00
|
|
|
View subscribers
|
|
|
|
|
</.link>
|
2026-03-01 19:39:56 +00:00
|
|
|
<.link
|
|
|
|
|
navigate={~p"/admin/newsletter?tab=campaigns"}
|
complete admin CSS refactor: delete utilities.css, add layout primitives
- Delete utilities.css (701 lines / 24 KB of Tailwind utility clones)
- Add layout.css with admin-stack, admin-row, admin-cluster, admin-grid
primitives and gap variants (sm, md, lg, xl)
- Add transitions.css import and layout.css import to admin.css entry point
- Replace all Tailwind utility classes across 26 admin templates with
semantic admin-*/theme-*/page-specific CSS classes
- Replace all non-dynamic inline styles with semantic classes
- Add ~100 new semantic classes to components.css (analytics, dashboard,
order detail, settings, theme editor, generic utilities)
- Fix stray text-error → admin-text-error in media.ex
- Add missing .truncate definition to admin CSS
- Only remaining inline styles are dynamic data values (progress bars,
chart dimensions) and one JS.toggle target
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 21:40:21 +00:00
|
|
|
class="admin-link admin-text-medium"
|
2026-03-01 19:39:56 +00:00
|
|
|
>
|
2026-02-28 23:25:28 +00:00
|
|
|
View campaigns
|
|
|
|
|
</.link>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
"""
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
# ── Subscribers tab ─────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
attr :streams, :any, required: true
|
|
|
|
|
attr :status_filter, :string, required: true
|
|
|
|
|
attr :status_counts, :map, required: true
|
|
|
|
|
attr :subscriber_count, :integer, required: true
|
2026-03-01 09:42:34 +00:00
|
|
|
attr :subscriber_pagination, Berrypod.Pagination, required: true
|
2026-02-28 23:25:28 +00:00
|
|
|
attr :search, :string, required: true
|
|
|
|
|
|
|
|
|
|
defp subscribers_tab(assigns) do
|
|
|
|
|
~H"""
|
|
|
|
|
<div>
|
complete admin CSS refactor: delete utilities.css, add layout primitives
- Delete utilities.css (701 lines / 24 KB of Tailwind utility clones)
- Add layout.css with admin-stack, admin-row, admin-cluster, admin-grid
primitives and gap variants (sm, md, lg, xl)
- Add transitions.css import and layout.css import to admin.css entry point
- Replace all Tailwind utility classes across 26 admin templates with
semantic admin-*/theme-*/page-specific CSS classes
- Replace all non-dynamic inline styles with semantic classes
- Add ~100 new semantic classes to components.css (analytics, dashboard,
order detail, settings, theme editor, generic utilities)
- Fix stray text-error → admin-text-error in media.ex
- Add missing .truncate definition to admin CSS
- Only remaining inline styles are dynamic data values (progress bars,
chart dimensions) and one JS.toggle target
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 21:40:21 +00:00
|
|
|
<div class="admin-filter-row admin-filter-row-between">
|
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>
2026-03-01 17:15:25 +00:00
|
|
|
<div class="admin-cluster">
|
2026-02-28 23:25:28 +00:00
|
|
|
<.filter_pill
|
|
|
|
|
status="all"
|
|
|
|
|
label="All"
|
|
|
|
|
count={total_subs(@status_counts)}
|
|
|
|
|
active={@status_filter}
|
|
|
|
|
/>
|
|
|
|
|
<.filter_pill
|
|
|
|
|
status="confirmed"
|
|
|
|
|
label="Confirmed"
|
|
|
|
|
count={@status_counts["confirmed"]}
|
|
|
|
|
active={@status_filter}
|
|
|
|
|
/>
|
|
|
|
|
<.filter_pill
|
|
|
|
|
status="pending"
|
|
|
|
|
label="Pending"
|
|
|
|
|
count={@status_counts["pending"]}
|
|
|
|
|
active={@status_filter}
|
|
|
|
|
/>
|
|
|
|
|
<.filter_pill
|
|
|
|
|
status="unsubscribed"
|
|
|
|
|
label="Unsubscribed"
|
|
|
|
|
count={@status_counts["unsubscribed"]}
|
|
|
|
|
active={@status_filter}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<.link href={~p"/admin/newsletter/export"} class="admin-btn admin-btn-sm admin-btn-ghost">
|
|
|
|
|
<.icon name="hero-arrow-down-tray" class="size-4" /> Export CSV
|
|
|
|
|
</.link>
|
|
|
|
|
</div>
|
|
|
|
|
|
complete admin CSS refactor: delete utilities.css, add layout primitives
- Delete utilities.css (701 lines / 24 KB of Tailwind utility clones)
- Add layout.css with admin-stack, admin-row, admin-cluster, admin-grid
primitives and gap variants (sm, md, lg, xl)
- Add transitions.css import and layout.css import to admin.css entry point
- Replace all Tailwind utility classes across 26 admin templates with
semantic admin-*/theme-*/page-specific CSS classes
- Replace all non-dynamic inline styles with semantic classes
- Add ~100 new semantic classes to components.css (analytics, dashboard,
order detail, settings, theme editor, generic utilities)
- Fix stray text-error → admin-text-error in media.ex
- Add missing .truncate definition to admin CSS
- Only remaining inline styles are dynamic data values (progress bars,
chart dimensions) and one JS.toggle target
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 21:40:21 +00:00
|
|
|
<form phx-change="search_subscribers" class="admin-filter-row">
|
2026-02-28 23:25:28 +00:00
|
|
|
<.input
|
|
|
|
|
name="search"
|
|
|
|
|
value={@search}
|
|
|
|
|
type="search"
|
|
|
|
|
placeholder="Search by email..."
|
|
|
|
|
phx-debounce="300"
|
|
|
|
|
/>
|
|
|
|
|
</form>
|
|
|
|
|
|
|
|
|
|
<.table
|
|
|
|
|
:if={@subscriber_count > 0}
|
|
|
|
|
id="subscribers"
|
|
|
|
|
rows={@streams.subscribers}
|
|
|
|
|
row_item={fn {_id, sub} -> sub end}
|
|
|
|
|
>
|
|
|
|
|
<:col :let={sub} label="Email">{sub.email}</:col>
|
|
|
|
|
<:col :let={sub} label="Status"><.subscriber_status status={sub.status} /></:col>
|
|
|
|
|
<:col :let={sub} label="Subscribed">{format_date(sub.inserted_at)}</:col>
|
|
|
|
|
<:col :let={sub} label="Source">{sub.source}</:col>
|
|
|
|
|
<:action :let={sub}>
|
|
|
|
|
<button
|
|
|
|
|
phx-click="delete_subscriber"
|
|
|
|
|
phx-value-id={sub.id}
|
|
|
|
|
data-confirm="Permanently delete this subscriber? This cannot be undone."
|
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>
2026-03-01 17:15:25 +00:00
|
|
|
class="admin-link-danger"
|
2026-02-28 23:25:28 +00:00
|
|
|
>
|
|
|
|
|
Delete
|
|
|
|
|
</button>
|
|
|
|
|
</:action>
|
|
|
|
|
</.table>
|
|
|
|
|
|
2026-03-01 09:42:34 +00:00
|
|
|
<.admin_pagination
|
|
|
|
|
:if={@subscriber_count > 0}
|
|
|
|
|
page={@subscriber_pagination}
|
|
|
|
|
patch={~p"/admin/newsletter"}
|
|
|
|
|
params={%{"tab" => "subscribers"}}
|
|
|
|
|
/>
|
|
|
|
|
|
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>
2026-03-01 17:15:25 +00:00
|
|
|
<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>
|
2026-03-01 19:39:56 +00:00
|
|
|
<p class="admin-empty-state-text">
|
|
|
|
|
Subscribers will appear here when people sign up via your shop.
|
|
|
|
|
</p>
|
2026-02-28 23:25:28 +00:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
"""
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
# ── Campaigns tab ───────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
attr :streams, :any, required: true
|
|
|
|
|
attr :campaign_count, :integer, required: true
|
2026-03-01 09:42:34 +00:00
|
|
|
attr :campaign_pagination, Berrypod.Pagination, required: true
|
2026-02-28 23:25:28 +00:00
|
|
|
|
|
|
|
|
defp campaigns_tab(assigns) do
|
|
|
|
|
~H"""
|
|
|
|
|
<div>
|
complete admin CSS refactor: delete utilities.css, add layout primitives
- Delete utilities.css (701 lines / 24 KB of Tailwind utility clones)
- Add layout.css with admin-stack, admin-row, admin-cluster, admin-grid
primitives and gap variants (sm, md, lg, xl)
- Add transitions.css import and layout.css import to admin.css entry point
- Replace all Tailwind utility classes across 26 admin templates with
semantic admin-*/theme-*/page-specific CSS classes
- Replace all non-dynamic inline styles with semantic classes
- Add ~100 new semantic classes to components.css (analytics, dashboard,
order detail, settings, theme editor, generic utilities)
- Fix stray text-error → admin-text-error in media.ex
- Add missing .truncate definition to admin CSS
- Only remaining inline styles are dynamic data values (progress bars,
chart dimensions) and one JS.toggle target
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 21:40:21 +00:00
|
|
|
<div class="admin-tab-actions">
|
2026-02-28 23:25:28 +00:00
|
|
|
<.link navigate={~p"/admin/newsletter/campaigns/new"} class="admin-btn admin-btn-primary">
|
|
|
|
|
<.icon name="hero-plus" class="size-4" /> New campaign
|
|
|
|
|
</.link>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<.table
|
|
|
|
|
:if={@campaign_count > 0}
|
|
|
|
|
id="campaigns"
|
|
|
|
|
rows={@streams.campaigns}
|
|
|
|
|
row_item={fn {_id, c} -> c end}
|
|
|
|
|
row_click={fn {_id, c} -> JS.navigate(~p"/admin/newsletter/campaigns/#{c.id}") end}
|
|
|
|
|
>
|
|
|
|
|
<:col :let={c} label="Subject">{c.subject}</:col>
|
|
|
|
|
<:col :let={c} label="Status"><.campaign_status status={c.status} /></:col>
|
|
|
|
|
<:col :let={c} label="Sent">{c.sent_count}</:col>
|
|
|
|
|
<:col :let={c} label="Created">{format_date(c.inserted_at)}</:col>
|
|
|
|
|
<:action :let={c}>
|
|
|
|
|
<button
|
|
|
|
|
:if={c.status == "draft"}
|
|
|
|
|
phx-click="delete_campaign"
|
|
|
|
|
phx-value-id={c.id}
|
|
|
|
|
data-confirm="Delete this draft campaign?"
|
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>
2026-03-01 17:15:25 +00:00
|
|
|
class="admin-link-danger"
|
2026-02-28 23:25:28 +00:00
|
|
|
>
|
|
|
|
|
Delete
|
|
|
|
|
</button>
|
|
|
|
|
</:action>
|
|
|
|
|
</.table>
|
|
|
|
|
|
2026-03-01 09:42:34 +00:00
|
|
|
<.admin_pagination
|
|
|
|
|
:if={@campaign_count > 0}
|
|
|
|
|
page={@campaign_pagination}
|
|
|
|
|
patch={~p"/admin/newsletter"}
|
|
|
|
|
params={%{"tab" => "campaigns"}}
|
|
|
|
|
/>
|
|
|
|
|
|
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>
2026-03-01 17:15:25 +00:00
|
|
|
<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>
|
2026-02-28 23:25:28 +00:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
"""
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
# ── Components ──────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
attr :status, :string, required: true
|
|
|
|
|
attr :label, :string, required: true
|
|
|
|
|
attr :count, :integer, default: nil
|
|
|
|
|
attr :active, :string, required: true
|
|
|
|
|
|
|
|
|
|
defp filter_pill(assigns) do
|
|
|
|
|
count = assigns[:count] || 0
|
|
|
|
|
active = assigns.status == assigns.active
|
|
|
|
|
|
|
|
|
|
assigns = assign(assigns, count: count, active: active)
|
|
|
|
|
|
|
|
|
|
~H"""
|
|
|
|
|
<button
|
|
|
|
|
phx-click="filter_subscribers"
|
|
|
|
|
phx-value-status={@status}
|
|
|
|
|
class={[
|
|
|
|
|
"admin-btn admin-btn-sm",
|
|
|
|
|
@active && "admin-btn-primary",
|
|
|
|
|
!@active && "admin-btn-ghost"
|
|
|
|
|
]}
|
|
|
|
|
>
|
|
|
|
|
{@label}
|
complete admin CSS refactor: delete utilities.css, add layout primitives
- Delete utilities.css (701 lines / 24 KB of Tailwind utility clones)
- Add layout.css with admin-stack, admin-row, admin-cluster, admin-grid
primitives and gap variants (sm, md, lg, xl)
- Add transitions.css import and layout.css import to admin.css entry point
- Replace all Tailwind utility classes across 26 admin templates with
semantic admin-*/theme-*/page-specific CSS classes
- Replace all non-dynamic inline styles with semantic classes
- Add ~100 new semantic classes to components.css (analytics, dashboard,
order detail, settings, theme editor, generic utilities)
- Fix stray text-error → admin-text-error in media.ex
- Add missing .truncate definition to admin CSS
- Only remaining inline styles are dynamic data values (progress bars,
chart dimensions) and one JS.toggle target
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 21:40:21 +00:00
|
|
|
<span :if={@count > 0} class="admin-badge admin-badge-sm admin-badge-count">
|
|
|
|
|
{@count}
|
|
|
|
|
</span>
|
2026-02-28 23:25:28 +00:00
|
|
|
</button>
|
|
|
|
|
"""
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
attr :status, :string, required: true
|
|
|
|
|
|
|
|
|
|
defp subscriber_status(%{status: "confirmed"} = assigns) do
|
|
|
|
|
~H"""
|
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>
2026-03-01 17:15:25 +00:00
|
|
|
<span class="admin-status-dot admin-status-dot-green">Confirmed</span>
|
2026-02-28 23:25:28 +00:00
|
|
|
"""
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
defp subscriber_status(%{status: "pending"} = assigns) do
|
|
|
|
|
~H"""
|
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>
2026-03-01 17:15:25 +00:00
|
|
|
<span class="admin-status-dot admin-status-dot-amber">Pending</span>
|
2026-02-28 23:25:28 +00:00
|
|
|
"""
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
defp subscriber_status(%{status: "unsubscribed"} = assigns) do
|
|
|
|
|
~H"""
|
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>
2026-03-01 17:15:25 +00:00
|
|
|
<span class="admin-status-dot admin-status-dot-muted">Unsubscribed</span>
|
2026-02-28 23:25:28 +00:00
|
|
|
"""
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
defp subscriber_status(assigns) do
|
|
|
|
|
~H"""
|
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>
2026-03-01 17:15:25 +00:00
|
|
|
<span class="admin-status-dot admin-status-dot-muted">{@status}</span>
|
2026-02-28 23:25:28 +00:00
|
|
|
"""
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
attr :status, :string, required: true
|
|
|
|
|
|
|
|
|
|
defp campaign_status(%{status: "draft"} = assigns) do
|
|
|
|
|
~H"""
|
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>
2026-03-01 17:15:25 +00:00
|
|
|
<span class="admin-status-dot admin-status-dot-muted">Draft</span>
|
2026-02-28 23:25:28 +00:00
|
|
|
"""
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
defp campaign_status(%{status: "scheduled"} = assigns) do
|
|
|
|
|
~H"""
|
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>
2026-03-01 17:15:25 +00:00
|
|
|
<span class="admin-status-dot admin-status-dot-blue">Scheduled</span>
|
2026-02-28 23:25:28 +00:00
|
|
|
"""
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
defp campaign_status(%{status: "sending"} = assigns) do
|
|
|
|
|
~H"""
|
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>
2026-03-01 17:15:25 +00:00
|
|
|
<span class="admin-status-dot admin-status-dot-amber">Sending</span>
|
2026-02-28 23:25:28 +00:00
|
|
|
"""
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
defp campaign_status(%{status: "sent"} = assigns) do
|
|
|
|
|
~H"""
|
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>
2026-03-01 17:15:25 +00:00
|
|
|
<span class="admin-status-dot admin-status-dot-green">Sent</span>
|
2026-02-28 23:25:28 +00:00
|
|
|
"""
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
defp campaign_status(%{status: "cancelled"} = assigns) do
|
|
|
|
|
~H"""
|
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>
2026-03-01 17:15:25 +00:00
|
|
|
<span class="admin-status-dot admin-status-dot-red">Cancelled</span>
|
2026-02-28 23:25:28 +00:00
|
|
|
"""
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
defp campaign_status(assigns) do
|
|
|
|
|
~H"""
|
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>
2026-03-01 17:15:25 +00:00
|
|
|
<span class="admin-status-dot admin-status-dot-muted">{@status}</span>
|
2026-02-28 23:25:28 +00:00
|
|
|
"""
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
# ── Helpers ─────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
defp total_subs(counts) do
|
|
|
|
|
Enum.reduce(counts, 0, fn {_status, count}, acc -> acc + count end)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
defp format_date(nil), do: "—"
|
|
|
|
|
|
|
|
|
|
defp format_date(datetime) do
|
|
|
|
|
Calendar.strftime(datetime, "%-d %b %Y")
|
|
|
|
|
end
|
|
|
|
|
end
|