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"""
|
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-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>
|
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>
|
|
|
|
|
|
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-row" style="--admin-row-gap: 0.75rem;">
|
|
|
|
|
<.link navigate={~p"/admin/newsletter?tab=subscribers"} class="admin-link" style="font-weight: 500;">
|
2026-02-28 23:25:28 +00:00
|
|
|
View subscribers
|
|
|
|
|
</.link>
|
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
|
|
|
<.link navigate={~p"/admin/newsletter?tab=campaigns"} class="admin-link" style="font-weight: 500;">
|
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>
|
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-row" style="justify-content: space-between; margin-bottom: 1rem;">
|
|
|
|
|
<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>
|
|
|
|
|
|
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
|
|
|
<form phx-change="search_subscribers" style="margin-bottom: 1rem;">
|
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>
|
|
|
|
|
<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>
|
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 style="display: flex; justify-content: flex-end; margin-bottom: 1rem;">
|
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}
|
|
|
|
|
<span :if={@count > 0} class="admin-badge admin-badge-sm ml-1">{@count}</span>
|
|
|
|
|
</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
|