Packages
phoenix_kit
1.7.71
1.7.216
1.7.215
1.7.214
1.7.213
1.7.212
1.7.211
1.7.210
1.7.209
1.7.208
1.7.207
1.7.206
1.7.205
1.7.204
1.7.203
1.7.202
1.7.201
1.7.200
1.7.199
1.7.198
1.7.197
1.7.196
1.7.194
1.7.193
1.7.192
1.7.191
1.7.190
1.7.189
1.7.187
1.7.186
1.7.185
1.7.184
1.7.183
1.7.182
1.7.181
1.7.180
1.7.179
1.7.178
1.7.177
1.7.176
1.7.175
1.7.174
1.7.173
1.7.172
1.7.171
1.7.170
1.7.169
1.7.168
1.7.167
1.7.166
1.7.165
1.7.164
1.7.162
1.7.161
1.7.160
1.7.159
1.7.157
1.7.156
1.7.155
1.7.154
1.7.153
1.7.152
1.7.151
1.7.150
1.7.149
1.7.146
1.7.145
1.7.144
1.7.143
1.7.138
1.7.133
1.7.132
1.7.131
1.7.130
1.7.128
1.7.126
1.7.125
1.7.121
1.7.120
1.7.119
1.7.118
1.7.117
1.7.116
1.7.115
1.7.114
1.7.113
1.7.112
1.7.111
1.7.110
1.7.109
1.7.108
1.7.107
1.7.106
1.7.105
1.7.104
1.7.103
1.7.102
1.7.101
1.7.100
1.7.99
1.7.98
1.7.97
1.7.96
1.7.95
1.7.94
1.7.93
1.7.92
1.7.91
1.7.90
1.7.89
1.7.88
1.7.87
1.7.86
1.7.85
1.7.84
1.7.83
1.7.82
1.7.81
1.7.80
1.7.79
1.7.78
1.7.77
1.7.76
1.7.75
1.7.74
1.7.71
1.7.70
1.7.69
1.7.66
1.7.65
1.7.64
1.7.63
1.7.62
1.7.61
1.7.59
1.7.58
1.7.57
1.7.56
1.7.55
1.7.54
1.7.53
1.7.52
1.7.51
1.7.49
1.7.44
1.7.43
1.7.42
1.7.41
1.7.39
1.7.38
1.7.37
1.7.36
1.7.34
1.7.33
1.7.31
1.7.30
1.7.29
1.7.28
1.7.27
1.7.26
1.7.25
1.7.24
1.7.23
1.7.22
1.7.21
1.7.20
1.7.19
1.7.18
1.7.17
1.7.16
1.7.15
1.7.14
1.7.13
1.7.12
1.7.11
1.7.10
1.7.9
1.7.8
1.7.7
1.7.6
1.7.5
1.7.4
1.7.3
1.7.2
1.7.1
1.7.0
1.6.20
1.6.19
1.6.18
1.6.17
1.6.16
1.6.15
1.6.14
1.6.13
1.6.12
1.6.11
1.6.10
1.6.9
1.6.8
1.6.7
1.6.6
1.6.5
1.6.4
1.6.3
1.5.2
1.5.1
1.5.0
1.4.9
1.4.8
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.2
1.3.1
1.3.0
1.2.10
1.2.9
1.2.8
1.2.7
1.2.5
1.2.4
1.2.2
1.2.1
1.2.0
1.1.0
1.0.0
A foundation for building Elixir Phoenix apps — SaaS, social networks, ERP systems, marketplaces, and more
Current section
Files
Jump to
Current section
Files
lib/modules/newsletters/web/broadcast_details.html.heex
<PhoenixKitWeb.Components.LayoutWrapper.app_layout
flash={@flash}
phoenix_kit_current_scope={assigns[:phoenix_kit_current_scope]}
page_title={@page_title || "Broadcast Details"}
current_path={@url_path || Routes.path("/admin/newsletters/broadcasts")}
project_title={@project_title}
>
<div class="container flex-col mx-auto px-4 py-6">
<.admin_page_header back={Routes.path("/admin/newsletters/broadcasts")}>
<h1 class="text-xl sm:text-2xl font-bold text-base-content">
{if @broadcast, do: @broadcast.subject, else: "Broadcast Details"}
</h1>
</.admin_page_header>
<%!-- Confirm Modal --%>
<%= if @show_confirm_modal do %>
<div class="modal modal-open">
<div class="modal-box">
<h3 class="font-bold text-lg mb-4">{@confirm_title}</h3>
<p class="mb-4">{@confirm_message}</p>
<div class="modal-action">
<button phx-click="hide_confirm" class="btn btn-outline">Cancel</button>
<button phx-click="confirm_action" class="btn btn-error">{@confirm_title}</button>
</div>
</div>
</div>
<% end %>
<%= if @loading do %>
<div class="flex justify-center items-center h-32">
<span class="loading loading-spinner loading-md"></span>
<span class="ml-2">Loading...</span>
</div>
<% else %>
<%= if @broadcast do %>
<%!-- Header actions --%>
<div class="flex flex-wrap gap-2 mb-6">
<%= if @broadcast.status == "draft" do %>
<.pk_link
navigate={"/admin/newsletters/broadcasts/#{@broadcast.uuid}/edit"}
class="btn btn-outline btn-sm"
>
<.icon name="hero-pencil" class="w-4 h-4" /> Edit
</.pk_link>
<% end %>
<%= if @broadcast.status == "sending" do %>
<button
type="button"
phx-click="show_confirm"
phx-value-action="cancel_broadcast"
class="btn btn-error btn-sm"
>
<.icon name="hero-x-circle" class="w-4 h-4" /> Cancel Broadcast
</button>
<% end %>
<button type="button" phx-click="refresh" class="btn btn-ghost btn-sm ml-auto">
<.icon name="hero-arrow-path" class="w-4 h-4" /> Refresh
</button>
</div>
<%!-- Metadata --%>
<div class="card bg-base-200 mb-6">
<div class="card-body p-4">
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 text-sm">
<div>
<p class="text-base-content/60 text-xs font-medium uppercase mb-1">Status</p>
<span class={"badge #{status_badge_class(@broadcast.status)}"}>
{@broadcast.status}
</span>
</div>
<div>
<p class="text-base-content/60 text-xs font-medium uppercase mb-1">List</p>
<p class="font-medium">
{if @broadcast.list, do: @broadcast.list.name, else: "-"}
</p>
</div>
<div>
<p class="text-base-content/60 text-xs font-medium uppercase mb-1">Template</p>
<p class="font-medium">
{if @broadcast.template,
do:
PhoenixKit.Modules.Emails.Template.get_translation(
@broadcast.template.display_name,
"en"
) || @broadcast.template.name,
else: "-"}
</p>
</div>
<div>
<p class="text-base-content/60 text-xs font-medium uppercase mb-1">Sent At</p>
<p class="font-medium">{format_datetime(@broadcast.sent_at)}</p>
</div>
</div>
</div>
</div>
<%!-- Delivery Stats --%>
<div class="stats stats-horizontal shadow w-full mb-6 overflow-x-auto">
<div class="stat">
<div class="stat-title">Total Recipients</div>
<div class="stat-value text-2xl">{@broadcast.total_recipients}</div>
</div>
<div class="stat">
<div class="stat-title">Sent</div>
<div class="stat-value text-2xl text-info">{stat_value(@delivery_stats, "sent")}</div>
</div>
<div class="stat">
<div class="stat-title">Delivered</div>
<div class="stat-value text-2xl text-success">
{stat_value(@delivery_stats, "delivered")}
</div>
</div>
<div class="stat">
<div class="stat-title">Opened</div>
<div class="stat-value text-2xl text-primary">
{stat_value(@delivery_stats, "opened")}
</div>
</div>
<div class="stat">
<div class="stat-title">Bounced</div>
<div class="stat-value text-2xl text-warning">
{stat_value(@delivery_stats, "bounced")}
</div>
</div>
<div class="stat">
<div class="stat-title">Failed</div>
<div class="stat-value text-2xl text-error">
{stat_value(@delivery_stats, "failed")}
</div>
</div>
</div>
<%!-- Deliveries Table (desktop) --%>
<h2 class="text-lg font-semibold mb-3">Deliveries</h2>
<%= if @deliveries == [] do %>
<div class="text-center py-10 text-base-content/50">
<p>No deliveries yet</p>
</div>
<% else %>
<div class="hidden md:block">
<.table_default variant="zebra" size="sm" class="w-full">
<.table_default_header>
<.table_default_row>
<.table_default_header_cell>Recipient</.table_default_header_cell>
<.table_default_header_cell>Status</.table_default_header_cell>
<.table_default_header_cell>Sent At</.table_default_header_cell>
<.table_default_header_cell>Delivered At</.table_default_header_cell>
<.table_default_header_cell>Opened At</.table_default_header_cell>
<.table_default_header_cell>Error</.table_default_header_cell>
</.table_default_row>
</.table_default_header>
<.table_default_body>
<.table_default_row :for={delivery <- @deliveries}>
<.table_default_cell class="font-mono text-sm">
{if delivery.user, do: delivery.user.email, else: delivery.user_uuid}
</.table_default_cell>
<.table_default_cell>
<span class={"badge badge-sm #{delivery_badge_class(delivery.status)}"}>
{delivery.status}
</span>
</.table_default_cell>
<.table_default_cell class="text-sm text-base-content/70">
{format_datetime(delivery.sent_at)}
</.table_default_cell>
<.table_default_cell class="text-sm text-base-content/70">
{format_datetime(delivery.delivered_at)}
</.table_default_cell>
<.table_default_cell class="text-sm text-base-content/70">
{format_datetime(delivery.opened_at)}
</.table_default_cell>
<.table_default_cell class="text-sm text-error max-w-xs truncate">
{delivery.error || ""}
</.table_default_cell>
</.table_default_row>
</.table_default_body>
</.table_default>
</div>
<%!-- Deliveries Cards (mobile) --%>
<div class="md:hidden mb-6">
<div class="space-y-3">
<%= for delivery <- @deliveries do %>
<div class="card card-border card-sm bg-base-100">
<div class="card-body gap-3">
<%!-- Header: email + status --%>
<div class="flex items-start justify-between gap-2">
<span class="font-mono text-sm truncate">
{if delivery.user, do: delivery.user.email, else: delivery.user_uuid}
</span>
<span class={"badge badge-sm #{delivery_badge_class(delivery.status)}"}>
{delivery.status}
</span>
</div>
<%!-- Body: key/value grid --%>
<div class="grid grid-cols-2 gap-x-4 gap-y-2 text-sm">
<div class="text-base-content/60">Sent At</div>
<div>{format_datetime(delivery.sent_at)}</div>
<div class="text-base-content/60">Delivered At</div>
<div>{format_datetime(delivery.delivered_at)}</div>
<div class="text-base-content/60">Opened At</div>
<div>{format_datetime(delivery.opened_at)}</div>
<%= if delivery.error do %>
<div class="text-base-content/60">Error</div>
<div class="text-error text-xs">{delivery.error}</div>
<% end %>
</div>
</div>
</div>
<% end %>
</div>
</div>
<% end %>
<% end %>
<% end %>
</div>
</PhoenixKitWeb.Components.LayoutWrapper.app_layout>