Packages

phoenix_kit

1.7.49
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
phoenix_kit lib modules emails web details.html.heex
Raw

lib/modules/emails/web/details.html.heex

<PhoenixKitWeb.Components.LayoutWrapper.app_layout
flash={@flash}
phoenix_kit_current_scope={assigns[:phoenix_kit_current_scope]}
page_title={"Email #{@email_id}"}
current_path={@url_path}
project_title={@project_title}
>
<div class="container flex-col mx-auto px-4 py-6">
<.admin_page_header back={Routes.path("/admin/emails")}>
<h1 class="text-xl sm:text-2xl lg:text-3xl font-bold text-base-content">
Email Details #{@email_id}
</h1>
<%= if @email_log do %>
<p class="text-sm text-base-content/60 mt-0.5">{@email_log.to}</p>
<% end %>
</.admin_page_header>
<%!-- Main Content --%>
<div class="email-details-container">
<%= 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 email details...</span>
</div>
<% else %>
<%= if @email_log do %>
<%!-- Page Header with Actions --%>
<div class="flex flex-col lg:flex-row lg:items-center lg:justify-between mb-6">
<div>
<h1 class="text-2xl font-bold text-base-content mb-2">
Email Details #{@email_log.id}
</h1>
<div class="flex items-center gap-4 text-sm text-base-content/70">
<span>To: {@email_log.to}</span>
<span>•</span>
<span>
Status: <.email_status_badge status={@email_log.status} />
</span>
<span>•</span>
<span>
Sent: {UtilsDate.format_datetime_with_user_format(@email_log.sent_at)}
</span>
</div>
</div>
<div class="flex gap-2 mt-4 lg:mt-0">
<.link
href={Routes.path("/admin/emails/#{@email_id}/export")}
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="sync_status"
class="btn btn-outline btn-sm"
disabled={assigns[:syncing]}
>
<%= if assigns[:syncing] do %>
<span class="loading loading-spinner loading-xs mr-1"></span> Syncing...
<% else %>
<.icon name="hero-arrow-path-rounded-square" class="w-4 h-4 mr-1" /> Sync Status
<% end %>
</button>
<button
phx-click="refresh"
class="btn btn-outline btn-sm"
disabled={assigns[:loading]}
>
<%= if assigns[:loading] do %>
<span class="loading loading-spinner loading-xs mr-1"></span> Loading...
<% else %>
<.icon name="hero-arrow-path" class="w-4 h-4 mr-1" /> Refresh
<% end %>
</button>
</div>
</div>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<%!-- Main Content Column --%>
<div class="lg:col-span-2 space-y-6">
<%!-- Email Overview Card --%>
<div class="card bg-base-100 shadow-sm">
<div class="card-body">
<h2 class="card-title text-lg mb-4">Email Overview</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="space-y-3">
<div>
<label class="text-sm font-medium text-base-content/70">Subject</label>
<p class="text-sm">{@email_log.subject || "(no subject)"}</p>
</div>
<div>
<label class="text-sm font-medium text-base-content/70">From</label>
<p class="text-sm">{@email_log.from}</p>
</div>
<div>
<label class="text-sm font-medium text-base-content/70">To</label>
<p class="text-sm">{@email_log.to}</p>
</div>
<%= if @email_log.template_name do %>
<div>
<label class="text-sm font-medium text-base-content/70">Template</label>
<div class="flex items-center gap-2">
<span class="badge badge-primary badge-sm h-auto">
{@email_log.template_name}
</span>
<button
phx-click="view_related"
phx-value-template_name={@email_log.template_name}
class="text-xs text-blue-600 hover:underline"
>
View related emails
</button>
</div>
</div>
<% end %>
</div>
<div class="space-y-3">
<div>
<label class="text-sm font-medium text-base-content/70">Status</label>
<div>
<.email_status_badge status={@email_log.status} />
</div>
</div>
<div>
<label class="text-sm font-medium text-base-content/70">Provider</label>
<div class="badge badge-outline badge-sm h-auto">
{@email_log.provider}
</div>
</div>
<div>
<label class="text-sm font-medium text-base-content/70">Size</label>
<p class="text-sm">
<.file_size bytes={@email_log.size_bytes} />
</p>
</div>
<div>
<label class="text-sm font-medium text-base-content/70">
Attachments
</label>
<p class="text-sm">{@email_log.attachments_count}</p>
</div>
</div>
</div>
<%!-- Message IDs Section --%>
<div class="border-t pt-4 mt-4 space-y-3">
<div>
<label class="text-sm font-medium text-base-content/70">
Message ID (Internal)
</label>
<p class="text-xs font-mono text-base-content/90 break-all">
{@email_log.message_id}
</p>
</div>
<%= if @email_log.aws_message_id do %>
<div>
<label class="text-sm font-medium text-base-content/70">
AWS Message ID
</label>
<p class="text-xs font-mono text-base-content/90 break-all">
{@email_log.aws_message_id}
</p>
</div>
<% end %>
</div>
<%= if @email_log.campaign_id do %>
<div class="border-t pt-4 mt-4">
<label class="text-sm font-medium text-base-content/70">Campaign</label>
<div class="flex items-center gap-2 mt-1">
<span class="badge badge-secondary badge-sm h-auto">
{@email_log.campaign_id}
</span>
<button
phx-click="view_related"
phx-value-campaign_id={@email_log.campaign_id}
class="text-xs text-blue-600 hover:underline"
>
View campaign emails
</button>
</div>
</div>
<% end %>
<%= if @email_log.error_message do %>
<div class="alert alert-error mt-4">
<.icon name="hero-exclamation-triangle" class="w-5 h-5" />
<div>
<strong>Error:</strong> {@email_log.error_message}
</div>
</div>
<% end %>
</div>
</div>
<%!-- Email Content Card --%>
<div class="card bg-base-100 shadow-sm">
<div class="card-body">
<div class="flex items-center justify-between mb-4">
<h2 class="card-title text-lg">Email Content</h2>
<div class="flex gap-2">
<%= if @email_log.body_full do %>
<button
phx-click="toggle_body"
class="btn btn-outline btn-sm"
>
{if assigns[:show_body], do: "Hide", else: "Show"} Full Body
</button>
<% end %>
<%= if @email_log.headers && map_size(@email_log.headers) > 0 do %>
<button
phx-click="toggle_headers"
class="btn btn-outline btn-sm"
>
{if assigns[:show_headers], do: "Hide", else: "Show"} Headers
</button>
<% end %>
</div>
</div>
<%!-- Body Preview --%>
<div class="mb-4">
<label class="text-sm font-medium text-base-content/70 block mb-2">
Body Preview
</label>
<div class="bg-base-200 p-3 rounded text-sm max-h-40 overflow-y-auto">
<%= if @email_log.body_preview do %>
<pre class="whitespace-pre-wrap"><%= @email_log.body_preview %></pre>
<% else %>
<em class="text-base-content/60">No preview available</em>
<% end %>
</div>
</div>
<%!-- Full Body (collapsible) --%>
<%= if assigns[:show_body] and @email_log.body_full do %>
<div class="mb-4">
<label class="text-sm font-medium text-base-content/70 block mb-2">
Full Body
</label>
<div class="bg-base-200 p-3 rounded text-sm max-h-60 overflow-y-auto">
<pre class="whitespace-pre-wrap"><%= @email_log.body_full %></pre>
</div>
</div>
<% end %>
<%!-- Headers (collapsible) --%>
<%= if assigns[:show_headers] and @email_log.headers && map_size(@email_log.headers) > 0 do %>
<div>
<label class="text-sm font-medium text-base-content/70 block mb-2">
Headers
</label>
<div class="bg-base-200 p-3 rounded text-sm max-h-40 overflow-y-auto">
<.table_default size="xs" class="w-full">
<.table_default_header>
<.table_default_row>
<.table_default_header_cell>Header</.table_default_header_cell>
<.table_default_header_cell>Value</.table_default_header_cell>
</.table_default_row>
</.table_default_header>
<.table_default_body>
<%= for {key, value} <- @email_log.headers do %>
<.table_default_row>
<.table_default_cell class="font-medium">
{key}
</.table_default_cell>
<.table_default_cell class="break-all">
{value}
</.table_default_cell>
</.table_default_row>
<% end %>
</.table_default_body>
</.table_default>
</div>
</div>
<% end %>
<%!-- Show message when headers are empty but toggle is on --%>
<%= if assigns[:show_headers] and (is_nil(@email_log.headers) or map_size(@email_log.headers) == 0) do %>
<div>
<label class="text-sm font-medium text-base-content/70 block mb-2">
Headers
</label>
<div class="bg-base-200 p-3 rounded text-sm text-base-content/60 italic">
No headers available for this email
</div>
</div>
<% end %>
</div>
</div>
<%!-- Events Timeline --%>
<div class="card bg-base-100 shadow-sm">
<div class="card-body">
<h2 class="card-title text-lg mb-4">Events Timeline</h2>
<%= if length(@events) > 0 do %>
<div class="space-y-4">
<%= for event <- @events do %>
<.event_timeline_item event={event} log={@email_log} />
<% end %>
</div>
<% else %>
<div class="text-center py-8 text-base-content/60">
<.icon
name="hero-clock"
class="w-12 h-12 mx-auto mb-2 text-base-content/30"
/>
<p>No events recorded for this email</p>
</div>
<% end %>
</div>
</div>
</div>
<%!-- Sidebar --%>
<div class="space-y-6">
<%!-- Quick Stats --%>
<div class="card bg-base-100 shadow-sm">
<div class="card-body">
<h3 class="card-title text-base mb-4">Quick Stats</h3>
<div class="stats stats-vertical shadow-none bg-transparent">
<div class="stat px-0 py-2">
<div class="stat-title text-xs">Message ID</div>
<div class="stat-value text-xs font-mono break-all">
{@email_log.message_id}
</div>
</div>
<%= if @email_log.aws_message_id do %>
<div class="stat px-0 py-2">
<div class="stat-title text-xs">AWS Message ID</div>
<div class="stat-value text-xs font-mono break-all">
{@email_log.aws_message_id}
</div>
</div>
<% end %>
<div class="stat px-0 py-2">
<div class="stat-title text-xs">Sent At</div>
<div class="stat-value text-sm">
{UtilsDate.format_datetime_with_user_format(@email_log.sent_at)}
</div>
</div>
<%= if @email_log.delivered_at do %>
<div class="stat px-0 py-2">
<div class="stat-title text-xs">Delivered At</div>
<div class="stat-value text-sm">
{UtilsDate.format_datetime_with_user_format(@email_log.delivered_at)}
</div>
<div class="stat-desc text-xs">
<.duration_display
start_time={@email_log.sent_at}
end_time={@email_log.delivered_at}
/> delivery time
</div>
</div>
<% end %>
<div class="stat px-0 py-2">
<div class="stat-title text-xs">Events</div>
<div class="stat-value text-lg">{length(@events)}</div>
</div>
<%= if @email_log.retry_count > 0 do %>
<div class="stat px-0 py-2">
<div class="stat-title text-xs">Retries</div>
<div class="stat-value text-lg text-warning">
{@email_log.retry_count}
</div>
</div>
<% end %>
</div>
</div>
</div>
<%!-- Message Tags --%>
<%= if map_size(@email_log.message_tags) > 0 do %>
<div class="card bg-base-100 shadow-sm">
<div class="card-body">
<h3 class="card-title text-base mb-4">Message Tags</h3>
<div class="space-y-2">
<%= for {key, value} <- @email_log.message_tags do %>
<div class="flex justify-between items-center text-sm">
<span class="font-medium">{key}</span>
<span class="badge badge-outline badge-xs h-auto">{value}</span>
</div>
<% end %>
</div>
</div>
</div>
<% end %>
<%!-- Related Emails --%>
<%= if length(@related_emails) > 0 do %>
<div class="card bg-base-100 shadow-sm">
<div class="card-body">
<h3 class="card-title text-base mb-4">Related Emails</h3>
<div class="space-y-2">
<%= for related <- Enum.take(@related_emails, 5) do %>
<.link
navigate={Routes.path("/admin/emails/email/#{related.uuid}")}
class="block p-2 rounded hover:bg-base-200 text-sm"
>
<div class="font-medium truncate">{related.to}</div>
<div class="text-xs text-base-content/60 flex items-center justify-between">
<span>
{UtilsDate.format_datetime_with_user_format(related.sent_at)}
</span>
<.email_status_badge status={related.status} size={:xs} />
</div>
</.link>
<% end %>
<%= if length(@related_emails) > 5 do %>
<div class="text-center pt-2">
<button
phx-click="view_related"
phx-value-campaign_id={@email_log.campaign_id}
class="text-xs text-blue-600 hover:underline"
>
View all {length(@related_emails)} related emails
</button>
</div>
<% end %>
</div>
</div>
</div>
<% end %>
</div>
</div>
<% else %>
<div class="text-center py-12">
<.icon name="hero-envelope-open" class="w-16 h-16 mx-auto mb-4 text-base-content/30" />
<h2 class="text-xl font-semibold text-base-content/70 mb-2">Email Not Found</h2>
<p class="text-base-content/60 mb-4">
The email with ID {@email_id} could not be found.
</p>
<.link navigate={Routes.path("/admin/emails")} class="btn btn-primary">
<.icon name="hero-arrow-left" class="w-4 h-4" />
</.link>
</div>
<% end %>
<% end %>
</div>
</div>
</PhoenixKitWeb.Components.LayoutWrapper.app_layout>