Packages
phoenix_kit
1.4.0
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/phoenix_kit_web/live/modules/emails/templates.html.heex
<PhoenixKitWeb.Components.LayoutWrapper.app_layout
flash={@flash}
phoenix_kit_current_scope={assigns[:phoenix_kit_current_scope]}
page_title="Email Templates"
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/emails")}
class="btn btn-outline btn-primary btn-sm absolute left-0 top-0 -mb-12"
>
<.icon_arrow_left /> Back to Emails
</.link>
<%!-- Title Section --%>
<div class="text-center">
<h1 class="text-4xl font-bold text-base-content mb-3">Email Templates</h1>
<p class="text-lg text-base-content">Manage and organize your email templates</p>
</div>
</header>
<%!-- Action Buttons --%>
<div class="flex justify-end gap-2 mb-6">
<.link
navigate={Routes.path("/admin/modules/emails/templates/new")}
class="btn btn-primary btn-sm"
>
<.icon name="hero-plus" class="w-4 h-4 mr-1" /> New Template
</.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>
<%!-- Statistics Summary --%>
<div class="stats shadow mb-6">
<div class="stat">
<div class="stat-title">Total Templates</div>
<div class="stat-value text-primary">{@stats[:total_templates] || 0}</div>
<div class="stat-desc">All templates</div>
</div>
<div class="stat">
<div class="stat-title">Active</div>
<div class="stat-value text-success">{@stats[:active_templates] || 0}</div>
<div class="stat-desc">Ready to use</div>
</div>
<div class="stat">
<div class="stat-title">System</div>
<div class="stat-value text-info">{@stats[:system_templates] || 0}</div>
<div class="stat-desc">Core templates</div>
</div>
<div class="stat">
<div class="stat-title">Most Used</div>
<div class="stat-value text-secondary">
<%= if @stats[:most_used] do %>
{@stats[:most_used].usage_count}
<% else %>
0
<% end %>
</div>
<div class="stat-desc">
<%= if @stats[:most_used] do %>
{@stats[:most_used].name}
<% else %>
No usage yet
<% end %>
</div>
</div>
</div>
<%!-- Filters & Search --%>
<div class="card bg-base-100 shadow-sm mb-6">
<div class="card-body">
<.form for={%{}} phx-change="filter" phx-submit="filter" class="space-y-4">
<%!-- Search Bar --%>
<div class="form-control">
<input
type="text"
name="search[query]"
value={@filters.search}
placeholder="Search by name, display name, or description..."
class="input input-bordered w-full"
/>
</div>
<%!-- Filter Row --%>
<div class="grid grid-cols-1 md:grid-cols-4 gap-4">
<%!-- Category Filter --%>
<div class="form-control">
<label class="label">
<span class="label-text">Category</span>
</label>
<select name="filter[category]" class="select select-bordered">
<option value="">All Categories</option>
<option value="system" selected={@filters.category == "system"}>System</option>
<option value="transactional" selected={@filters.category == "transactional"}>
Transactional
</option>
<option value="marketing" selected={@filters.category == "marketing"}>
Marketing
</option>
</select>
</div>
<%!-- 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="active" selected={@filters.status == "active"}>Active</option>
<option value="draft" selected={@filters.status == "draft"}>Draft</option>
<option value="archived" selected={@filters.status == "archived"}>
Archived
</option>
</select>
</div>
<%!-- System/Custom Filter --%>
<div class="form-control">
<label class="label">
<span class="label-text">Type</span>
</label>
<select name="filter[is_system]" class="select select-bordered">
<option value="">All Types</option>
<option value="true" selected={@filters.is_system == "true"}>System</option>
<option value="false" selected={@filters.is_system == "false"}>Custom</option>
</select>
</div>
<%!-- Clear Filters Button --%>
<div class="form-control">
<label class="label">
<span class="label-text"> </span>
</label>
<button type="button" phx-click="clear_filters" class="btn btn-ghost">
Clear Filters
</button>
</div>
</div>
</.form>
</div>
</div>
<%!-- Templates 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 templates...</span>
</div>
<% else %>
<div class="w-full">
<table class="table table-hover w-full">
<thead>
<tr>
<th class="w-1/4">Template</th>
<th class="w-1/4">Subject</th>
<th class="w-1/8">Category</th>
<th class="w-1/8">Status</th>
<th class="w-1/6">Usage</th>
<th class="w-1/8">Actions</th>
</tr>
</thead>
<tbody>
<%= for template <- @templates do %>
<tr class="hover">
<%!-- Template Column --%>
<td>
<div class="space-y-1">
<div class="font-medium text-sm">{template.display_name}</div>
<div class="text-xs text-base-content/70">{template.name}</div>
<%= if template.is_system do %>
<div class="badge badge-info badge-xs">System</div>
<% end %>
</div>
</td>
<%!-- Subject Column --%>
<td>
<div class="text-sm truncate" title={template.subject}>
{template.subject}
</div>
<%= if template.description do %>
<div
class="text-xs text-base-content/70 truncate"
title={template.description}
>
{template.description}
</div>
<% end %>
</td>
<%!-- Category Column --%>
<td>
<%= if template.is_system do %>
<div class="badge badge-ghost badge-sm">
{String.capitalize(template.category)}
</div>
<% else %>
<.category_badge category={template.category} />
<% end %>
</td>
<%!-- Status Column --%>
<td>
<.template_status_badge status={template.status} />
</td>
<%!-- Usage Column --%>
<td>
<div class="space-y-1 text-xs">
<div class="font-medium">
{template.usage_count} uses
</div>
<%= if template.last_used_at do %>
<div class="text-base-content/70">
Last: {UtilsDate.format_date_with_user_format(template.last_used_at)}
</div>
<% else %>
<div class="text-base-content/70">Never used</div>
<% end %>
</div>
</td>
<%!-- Actions Column --%>
<td>
<div class="flex gap-1">
<%!-- Edit Button --%>
<button
phx-click="edit_template"
phx-value-id={template.id}
class="btn btn-xs btn-outline btn-primary"
title="Edit Template"
>
<.icon name="hero-pencil" class="w-3 h-3" />
</button>
<%!-- Clone Button --%>
<button
phx-click="show_clone_modal"
phx-value-id={template.id}
class="btn btn-xs btn-outline"
title="Clone Template"
>
<.icon name="hero-document-duplicate" class="w-3 h-3" />
</button>
<%!-- Archive/Activate Button (not for system templates) --%>
<%= unless template.is_system do %>
<%= if template.status == "active" do %>
<button
phx-click="archive_template"
phx-value-id={template.id}
class="btn btn-xs btn-outline"
title="Archive Template"
>
<.icon name="hero-archive-box" class="w-3 h-3" />
</button>
<% else %>
<button
phx-click="activate_template"
phx-value-id={template.id}
class="btn btn-xs btn-outline btn-success"
title="Activate Template"
>
<.icon name="hero-check-circle" class="w-3 h-3" />
</button>
<% end %>
<%!-- Delete Button --%>
<button
phx-click="request_delete"
phx-value-id={template.id}
phx-value-name={template.name}
class="btn btn-xs btn-outline text-error hover:btn-error"
title="Delete Template"
>
<.icon name="hero-trash" class="w-3 h-3" />
</button>
<% end %>
</div>
</td>
</tr>
<% end %>
<%= if length(@templates) == 0 and not @loading do %>
<tr>
<td colspan="6" class="text-center py-8 text-base-content/60">
No templates 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">
<.pagination_info
page={@page}
per_page={@per_page}
total_count={@total_count}
/>
<.pagination_controls
page={@page}
total_pages={@total_pages}
build_url={build_page_url(assigns)}
/>
</div>
<% end %>
<% end %>
</div>
</div>
<%!-- Clone Template Modal --%>
<div
:if={@show_clone_modal}
class="modal modal-open"
phx-click-away="hide_clone_modal"
>
<div class="modal-box">
<h3 class="font-bold text-lg mb-4">Clone Template</h3>
<%= if @clone_template do %>
<p class="text-sm text-base-content/70 mb-4">
Creating a copy of "<strong>{@clone_template.display_name}</strong>"
</p>
<% end %>
<.form
for={%{}}
phx-submit="clone_template"
phx-change="validate_clone"
class="space-y-4"
>
<div class="form-control">
<label class="label">
<span class="label-text">New Template Name</span>
</label>
<input
type="text"
name="clone[name]"
value={@clone_form[:name] || ""}
placeholder="welcome_email_copy"
class={[
"input input-bordered w-full",
@clone_form[:errors][:name] && "input-error"
]}
required
/>
<%= if @clone_form[:errors][:name] do %>
<label class="label">
<span class="label-text-alt text-error">
{@clone_form[:errors][:name]}
</span>
</label>
<% end %>
</div>
<div class="form-control">
<label class="label">
<span class="label-text">New Display Name</span>
</label>
<input
type="text"
name="clone[display_name]"
value={@clone_form[:display_name] || ""}
placeholder="Welcome Email (Copy)"
class={[
"input input-bordered w-full",
@clone_form[:errors][:display_name] && "input-error"
]}
required
/>
<%= if @clone_form[:errors][:display_name] do %>
<label class="label">
<span class="label-text-alt text-error">
{@clone_form[:errors][:display_name]}
</span>
</label>
<% end %>
</div>
<div class="modal-action">
<button
type="button"
phx-click="hide_clone_modal"
class="btn btn-ghost"
>
Cancel
</button>
<button type="submit" class="btn btn-primary">
Clone Template
</button>
</div>
</.form>
</div>
</div>
<%!-- Confirmation Modal --%>
<%= if assigns[:confirmation_modal] && @confirmation_modal.show do %>
<div class="modal modal-open">
<div class="modal-box">
<h3 class="font-bold text-lg">{@confirmation_modal.title}</h3>
<p class="py-4">{@confirmation_modal.message}</p>
<div class="modal-action">
<button class="btn btn-ghost" phx-click="cancel_confirmation">
Cancel
</button>
<button
class="btn btn-error"
phx-click="confirm_action"
phx-value-action={@confirmation_modal.action}
phx-value-id={@confirmation_modal.id}
>
{@confirmation_modal.button_text}
</button>
</div>
</div>
</div>
<% end %>
</div>
</PhoenixKitWeb.Components.LayoutWrapper.app_layout>