Packages
phoenix_kit
1.7.28
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/emails/web/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="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
<PhoenixKitWeb.Components.Core.StatCard.stat_card
compact={true}
rounded="xl"
value={@stats[:total_templates] || 0}
title="Total Templates"
subtitle="All templates"
>
<:icon>
<.icon name="hero-document-text" class="w-5 h-5" />
</:icon>
</PhoenixKitWeb.Components.Core.StatCard.stat_card>
<PhoenixKitWeb.Components.Core.StatCard.stat_card
compact={true}
rounded="xl"
value={@stats[:active_templates] || 0}
title="Active"
subtitle="Ready to use"
>
<:icon>
<.icon name="hero-check-circle" class="w-5 h-5" />
</:icon>
</PhoenixKitWeb.Components.Core.StatCard.stat_card>
<PhoenixKitWeb.Components.Core.StatCard.stat_card
compact={true}
rounded="xl"
value={@stats[:system_templates] || 0}
title="System"
subtitle="Core templates"
>
<:icon>
<.icon name="hero-cog-6-tooth" class="w-5 h-5" />
</:icon>
</PhoenixKitWeb.Components.Core.StatCard.stat_card>
<PhoenixKitWeb.Components.Core.StatCard.stat_card
compact={true}
rounded="xl"
value={if @stats[:most_used], do: @stats[:most_used].usage_count, else: 0}
title="Most Used"
subtitle={if @stats[:most_used], do: @stats[:most_used].name, else: "No usage yet"}
>
<:icon>
<.icon name="hero-star" class="w-5 h-5" />
</:icon>
</PhoenixKitWeb.Components.Core.StatCard.stat_card>
</div>
<%!-- Filters & Search --%>
<div class="bg-base-200 rounded-lg p-6 mb-6">
<.form for={%{}} phx-change="filter" phx-submit="filter" class="space-y-4">
<%!-- Search Bar --%>
<div class="flex gap-2 items-center">
<input
type="text"
name="search[query]"
value={@filters.search}
placeholder="Search by name, display name, or description..."
class="input input-bordered flex-1"
/>
<button type="button" phx-click="clear_filters" class="btn btn-ghost btn-sm">
Clear Filters
</button>
</div>
<%!-- Filter Row --%>
<div class="grid grid-cols-1 md:grid-cols-3 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>
</div>
</.form>
</div>
<%!-- Templates Table --%>
<div class="bg-base-100">
<%= 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 %>
<%= if length(@templates) > 0 do %>
<.table_default variant="zebra" class="w-full">
<.table_default_header>
<.table_default_row>
<.table_default_header_cell>Template</.table_default_header_cell>
<.table_default_header_cell>Subject</.table_default_header_cell>
<.table_default_header_cell>Category</.table_default_header_cell>
<.table_default_header_cell>Status</.table_default_header_cell>
<.table_default_header_cell>Usage</.table_default_header_cell>
<.table_default_header_cell>Actions</.table_default_header_cell>
</.table_default_row>
</.table_default_header>
<.table_default_body>
<%= for template <- @templates do %>
<.table_default_row>
<%!-- Template Column --%>
<.table_default_cell>
<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>
</.table_default_cell>
<%!-- Subject Column --%>
<.table_default_cell>
<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 %>
</.table_default_cell>
<%!-- Category Column --%>
<.table_default_cell>
<%= if template.is_system do %>
<div class="badge badge-ghost badge-sm">
{String.capitalize(template.category)}
</div>
<% else %>
<.category_badge category={template.category} />
<% end %>
</.table_default_cell>
<%!-- Status Column --%>
<.table_default_cell>
<.template_status_badge status={template.status} />
</.table_default_cell>
<%!-- Usage Column --%>
<.table_default_cell>
<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>
</.table_default_cell>
<%!-- Actions Column --%>
<.table_default_cell>
<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>
</.table_default_cell>
</.table_default_row>
<% end %>
</.table_default_body>
</.table_default>
<% else %>
<%!-- Empty state --%>
<div class="text-center py-12">
<.icon name="hero-document-text" class="h-16 w-16 mx-auto text-base-content/40 mb-4" />
<h3 class="text-lg font-medium text-base-content mb-2">
No templates found
</h3>
<p class="text-base-content/70 mb-4">
No templates found matching your criteria
</p>
</div>
<% end %>
<%!-- Pagination --%>
<%= if @total_count > @per_page do %>
<.pagination
current_page={@page}
total_pages={@total_pages}
base_path="/admin/modules/emails/templates"
params={
%{
"search" => @filters.search,
"category" => @filters.category,
"status" => @filters.status,
"is_system" => @filters.is_system,
"per_page" => @per_page
}
}
/>
<% end %>
<% end %>
</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>