Packages
phoenix_kit
1.4.0
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/phoenix_kit_web/live/modules/emails/emails.html.heex
<PhoenixKitWeb.Components.LayoutWrapper.app_layout
flash={@flash}
phoenix_kit_current_scope={assigns[:phoenix_kit_current_scope]}
page_title="Emails"
current_path={@url_path}
project_title={@project_title}
>
<div class="container flex-col mx-auto px-4 py-6">
<%!-- Header Section --%>
<header class="w-full relative mb-6">
<%!-- Back Button (Left aligned) --%>
<.link
navigate={Routes.path("/admin/dashboard")}
class="btn btn-outline btn-primary btn-sm absolute left-0 top-0 -mb-12"
>
<.icon_arrow_left /> Back to Dashboard
</.link>
<%!-- Title Section --%>
<div class="text-center">
<h1 class="text-4xl font-bold text-base-content mb-3">Emails</h1>
<p class="text-lg text-base-content">Monitor and track all outgoing emails</p>
</div>
</header>
<%!-- Action Buttons --%>
<div class="flex justify-between items-center mb-6">
<div class="flex gap-2">
<.link
navigate={Routes.path("/admin/modules/emails/templates")}
class="btn btn-outline btn-secondary btn-sm"
>
<.icon name="hero-document-text" class="w-4 h-4 mr-1" /> Templates
</.link>
</div>
<div class="flex gap-2">
<.button phx-click="show_test_email_modal" class="btn btn-primary btn-sm">
<.icon name="hero-envelope" class="w-4 h-4 mr-1" /> Send Test Email
</.button>
<.link
href={build_export_url(@filters)}
target="_blank"
class="btn btn-outline btn-sm"
>
<.icon name="hero-arrow-down-tray" class="w-4 h-4 mr-1" /> Export CSV
</.link>
<.button phx-click="refresh" class="btn btn-outline btn-sm">
<.icon name="hero-arrow-path" class="w-4 h-4 mr-1" /> Refresh
</.button>
</div>
</div>
<%!-- Statistics Summary --%>
<div class="stats shadow mb-6">
<div class="stat">
<div class="stat-title">Total Sent</div>
<div class="stat-value text-primary">{@stats[:total_sent] || 0}</div>
<div class="stat-desc">Last 30 days</div>
</div>
<div class="stat">
<div class="stat-title">Delivered</div>
<div class="stat-value text-success">{@stats[:delivered] || 0}</div>
<div class="stat-desc">{@stats[:delivery_rate] || 0}% rate</div>
</div>
<div class="stat">
<div class="stat-title">Bounced</div>
<div class="stat-value text-error">{@stats[:bounced] || 0}</div>
<div class="stat-desc">{@stats[:bounce_rate] || 0}% rate</div>
</div>
<div class="stat">
<div class="stat-title">Opened</div>
<div class="stat-value text-info">{@stats[:opened] || 0}</div>
<div class="stat-desc">{@stats[:open_rate] || 0}% rate</div>
</div>
</div>
<%!-- Filters & Search --%>
<div class="card bg-base-100 shadow-sm mb-6">
<div class="card-body">
<.form for={%{}} phx-change="filter" class="space-y-4">
<%!-- Search Bar & Action Buttons (Single Row) --%>
<div class="flex gap-2 items-center">
<input
type="text"
name="search[query]"
value={@filters.search}
placeholder="Search by recipient, subject, or campaign..."
class="input input-bordered flex-1"
phx-debounce="300"
/>
<button type="button" phx-click="clear_filters" class="btn btn-ghost btn-sm">
Clear
</button>
</div>
<%!-- Filter Row --%>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<%!-- Status Filter --%>
<div class="form-control">
<label class="label">
<span class="label-text">Status</span>
</label>
<select name="filter[status]" class="select select-bordered">
<option value="">All Statuses</option>
<option value="sent" selected={@filters.status == "sent"}>Sent</option>
<option value="delivered" selected={@filters.status == "delivered"}>
Delivered
</option>
<option value="bounced" selected={@filters.status == "bounced"}>Bounced</option>
<option value="opened" selected={@filters.status == "opened"}>Opened</option>
<option value="clicked" selected={@filters.status == "clicked"}>Clicked</option>
<option value="failed" selected={@filters.status == "failed"}>Failed</option>
</select>
</div>
<%!-- Message Tags Filter --%>
<div class="form-control">
<label class="label">
<span class="label-text">Message Type</span>
</label>
<select name="filter[message_tag]" class="select select-bordered">
<option value="">All Types</option>
<option value="authentication" selected={@filters.message_tag == "authentication"}>
Authentication
</option>
<option value="registration" selected={@filters.message_tag == "registration"}>
Registration
</option>
<option value="marketing" selected={@filters.message_tag == "marketing"}>
Marketing
</option>
<option value="notification" selected={@filters.message_tag == "notification"}>
Notification
</option>
<option value="transactional" selected={@filters.message_tag == "transactional"}>
Transactional
</option>
</select>
</div>
<%!-- Date Range --%>
<div class="form-control">
<label class="label">
<span class="label-text">From Date</span>
</label>
<input
type="date"
name="filter[from_date]"
value={@filters.from_date}
class="input input-bordered"
/>
</div>
<div class="form-control">
<label class="label">
<span class="label-text">To Date</span>
</label>
<input
type="date"
name="filter[to_date]"
value={@filters.to_date}
class="input input-bordered"
/>
</div>
</div>
</.form>
</div>
</div>
<%!-- Emails Table --%>
<div class="card bg-base-100 shadow-sm">
<div class="card-body p-0">
<%= 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 emails...</span>
</div>
<% else %>
<div class="w-full">
<table class="table table-hover w-full">
<thead>
<tr>
<th class="w-1/4">Email</th>
<th class="w-1/4">Subject</th>
<th class="w-1/8">Status</th>
<th class="w-1/6">Details</th>
<th class="w-1/8">Actions</th>
</tr>
</thead>
<tbody>
<%= for log <- @logs do %>
<tr class="hover">
<%!-- Email Column --%>
<td>
<div class="space-y-1">
<div class="font-medium text-sm">{log.to}</div>
<%= if log.user_id do %>
<div class="badge badge-ghost badge-xs">User #{log.user_id}</div>
<% end %>
</div>
</td>
<%!-- Subject Column --%>
<td>
<div class="space-y-1">
<div class="text-sm truncate" title={log.subject}>
{log.subject || "(no subject)"}
</div>
<%= if log.template_name do %>
<div class="badge badge-outline badge-xs">
{log.template_name}
</div>
<% end %>
</div>
</td>
<%!-- Status Column --%>
<td>
<div class="space-y-1">
<div class={status_badge_class(log.status)}>
{log.status}
</div>
<%= if log.error_message do %>
<div class="text-xs text-error truncate" title={log.error_message}>
Error
</div>
<% end %>
</div>
</td>
<%!-- Details Column --%>
<td>
<div class="space-y-1 text-xs">
<div class="flex items-center gap-1">
<%= if get_message_tag(log.message_tags) do %>
<div class="badge badge-secondary badge-xs">
{get_message_tag(log.message_tags)}
</div>
<% else %>
<div class="badge badge-ghost badge-xs">no tag</div>
<% end %>
<%= if log.campaign_id do %>
<div class="badge badge-primary badge-xs">{log.campaign_id}</div>
<% end %>
</div>
<div class="text-base-content/70">
{UtilsDate.format_datetime_with_user_format(log.sent_at)}
</div>
<%= if log.delivered_at do %>
<div class="text-success">
✓ {UtilsDate.format_datetime_with_user_format(log.delivered_at)}
</div>
<% end %>
</div>
</td>
<%!-- Actions Column --%>
<td>
<button
phx-click="view_details"
phx-value-id={log.id}
class="btn btn-xs btn-outline btn-primary"
>
<.icon name="hero-eye" class="w-3 h-3 mr-1" /> View
</button>
</td>
</tr>
<% end %>
<%= if length(@logs) == 0 and not @loading do %>
<tr>
<td colspan="5" class="text-center py-8 text-base-content/60">
No emails found matching your criteria
</td>
</tr>
<% end %>
</tbody>
</table>
</div>
<%!-- Pagination --%>
<%= if @total_count > @per_page do %>
<div class="border-t bg-base-200 px-4 py-3 flex items-center justify-between">
<div class="text-sm text-base-content/70">
Showing {(@page - 1) * @per_page + 1} to {min(@page * @per_page, @total_count)} of {@total_count} results
</div>
<div class="btn-group">
<%= if @page > 1 do %>
<.link patch={build_page_url(@page - 1, assigns)} class="btn btn-sm">
« Prev
</.link>
<% end %>
<%= for page_num <- pagination_pages(@page, @total_pages) do %>
<.link
patch={build_page_url(page_num, assigns)}
class={pagination_class(page_num, @page)}
>
{page_num}
</.link>
<% end %>
<%= if @page < @total_pages do %>
<.link patch={build_page_url(@page + 1, assigns)} class="btn btn-sm">
Next »
</.link>
<% end %>
</div>
</div>
<% end %>
<% end %>
</div>
</div>
<%!-- Test Email Modal --%>
<div
:if={@show_test_email_modal}
class="modal modal-open"
phx-click-away="hide_test_email_modal"
>
<div class="modal-box">
<h3 class="font-bold text-lg mb-4">Send Test Email</h3>
<p class="text-sm text-base-content/70 mb-4">
Send a test email to verify that email is working correctly.
The test email will include test links.
</p>
<.form
for={%{}}
phx-submit="send_test_email"
phx-change="validate_test_email"
class="space-y-4"
>
<div class="form-control">
<label class="label">
<span class="label-text">Recipient Email Address</span>
</label>
<input
type="email"
name="test_email[recipient]"
value={@test_email_form[:recipient] || ""}
placeholder="admin@example.com"
class={[
"input input-bordered w-full",
@test_email_form[:errors][:recipient] && "input-error"
]}
required
/>
<%= if @test_email_form[:errors][:recipient] do %>
<label class="label">
<span class="label-text-alt text-error">
{@test_email_form[:errors][:recipient]}
</span>
</label>
<% end %>
</div>
<div class="alert alert-info">
<.icon name="hero-information-circle" class="w-5 h-5" />
<div class="text-sm">
<div class="font-semibold">This test email will:</div>
<ul class="mt-1 list-disc list-inside">
<li>Verify AWS SES configuration (if enabled)</li>
<li>Test email delivery management</li>
<li>Include trackable links for click testing</li>
<li>Appear in your emails with "TEST" campaign</li>
</ul>
</div>
</div>
<div class="modal-action">
<button
type="button"
phx-click="hide_test_email_modal"
class="btn btn-ghost"
>
Cancel
</button>
<button
type="submit"
class={[
"btn btn-primary",
@test_email_sending && "loading"
]}
disabled={@test_email_sending}
>
<%= if @test_email_sending do %>
Sending...
<% else %>
Send Test Email
<% end %>
</button>
</div>
</.form>
</div>
</div>
</div>
</PhoenixKitWeb.Components.LayoutWrapper.app_layout>