Packages

phoenix_kit

1.7.80
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 templates.html.heex
Raw

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={gettext("Email Templates")}
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")}
title={gettext("Email Templates")}
subtitle={gettext("Manage and organize your email templates")}
>
<:actions>
<.link
navigate={Routes.path("/admin/emails/templates/new")}
class="btn btn-primary btn-sm"
>
<.icon name="hero-plus" class="w-4 h-4 mr-1" /> {gettext("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" /> {gettext("Refresh")}
</.button>
</:actions>
</.admin_page_header>
<%!-- 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={gettext("Total Templates")}
subtitle={gettext("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={gettext("Active")}
subtitle={gettext("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={gettext("System")}
subtitle={gettext("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={gettext("Most Used")}
subtitle={
if @stats[:most_used], do: @stats[:most_used].name, else: gettext("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={gettext("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">
{gettext("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">{gettext("Category")}</span>
</label>
<select name="filter[category]" class="select select-bordered">
<option value="">{gettext("All Categories")}</option>
<option value="system" selected={@filters.category == "system"}>
{gettext("System")}
</option>
<option value="transactional" selected={@filters.category == "transactional"}>
{gettext("Transactional")}
</option>
<option value="marketing" selected={@filters.category == "marketing"}>
{gettext("Marketing")}
</option>
<option value="newsletters" selected={@filters.category == "newsletters"}>
{gettext("Newsletters")}
</option>
</select>
</div>
<%!-- Status Filter --%>
<div class="form-control">
<label class="label">
<span class="label-text">{gettext("Status")}</span>
</label>
<select name="filter[status]" class="select select-bordered">
<option value="">{gettext("All Statuses")}</option>
<option value="active" selected={@filters.status == "active"}>
{gettext("Active")}
</option>
<option value="draft" selected={@filters.status == "draft"}>
{gettext("Draft")}
</option>
<option value="archived" selected={@filters.status == "archived"}>
{gettext("Archived")}
</option>
</select>
</div>
<%!-- System/Custom Filter --%>
<div class="form-control">
<label class="label">
<span class="label-text">{gettext("Type")}</span>
</label>
<select name="filter[is_system]" class="select select-bordered">
<option value="">{gettext("All Types")}</option>
<option value="true" selected={@filters.is_system == "true"}>
{gettext("System")}
</option>
<option value="false" selected={@filters.is_system == "false"}>
{gettext("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">{gettext("Loading templates...")}</span>
</div>
<% else %>
<%= if length(@templates) > 0 do %>
<.table_default
id="templates-table"
variant="zebra"
size="sm"
class="w-full"
toggleable={true}
items={@templates}
card_fields={
fn t ->
subject =
PhoenixKit.Modules.Emails.Template.get_translation(t.subject, @display_locale)
last_used =
if t.last_used_at,
do: UtilsDate.format_date_with_user_format(t.last_used_at),
else: gettext("Never")
[
%{label: gettext("Subject"), value: subject},
%{label: gettext("Category"), value: String.capitalize(t.category || "")},
%{label: gettext("Status"), value: t.status},
%{
label: gettext("Usage"),
value: "#{t.usage_count} (#{gettext("Last")}: #{last_used})"
}
]
end
}
>
<:card_header :let={template}>
<div class="space-y-0.5">
<div class="flex flex-wrap items-center gap-2">
<span class="font-medium text-sm">
{PhoenixKit.Modules.Emails.Template.get_translation(
template.display_name,
@display_locale
)}
</span>
<%= if template.is_system do %>
<span class="badge badge-info badge-xs h-auto">{gettext("System")}</span>
<% end %>
</div>
<div class="text-xs text-base-content/70">{template.name}</div>
</div>
</:card_header>
<:card_actions :let={template}>
<div class="flex flex-wrap gap-1">
<%!-- Edit --%>
<button
phx-click="edit_template"
phx-value-uuid={template.uuid}
class="btn btn-xs btn-outline btn-primary tooltip tooltip-bottom"
data-tip={gettext("Edit Template")}
>
<.icon name="hero-pencil" class="w-3 h-3" />
</button>
<%!-- Clone --%>
<button
phx-click="show_clone_modal"
phx-value-uuid={template.uuid}
class="btn btn-xs btn-outline tooltip tooltip-bottom"
data-tip={gettext("Clone Template")}
>
<.icon name="hero-document-duplicate" class="w-3 h-3" />
</button>
<%!-- Archive / Activate (non-system only) --%>
<%= unless template.is_system do %>
<%= if template.status == "active" do %>
<button
phx-click="archive_template"
phx-value-uuid={template.uuid}
class="btn btn-xs btn-outline tooltip tooltip-bottom"
data-tip={gettext("Archive Template")}
>
<.icon name="hero-archive-box" class="w-3 h-3" />
</button>
<% else %>
<button
phx-click="activate_template"
phx-value-uuid={template.uuid}
class="btn btn-xs btn-outline btn-success tooltip tooltip-bottom"
data-tip={gettext("Activate Template")}
>
<.icon name="hero-check-circle" class="w-3 h-3" />
</button>
<% end %>
<%!-- Delete --%>
<button
phx-click="request_delete"
phx-value-uuid={template.uuid}
phx-value-name={template.name}
class="btn btn-xs btn-outline text-error hover:btn-error tooltip tooltip-bottom"
data-tip={gettext("Delete Template")}
>
<.icon name="hero-trash" class="w-3 h-3" />
</button>
<% end %>
</div>
</:card_actions>
<.table_default_header>
<.table_default_row>
<.table_default_header_cell>{gettext("Template")}</.table_default_header_cell>
<.table_default_header_cell>{gettext("Subject")}</.table_default_header_cell>
<.table_default_header_cell>{gettext("Category")}</.table_default_header_cell>
<.table_default_header_cell>{gettext("Status")}</.table_default_header_cell>
<.table_default_header_cell>{gettext("Usage")}</.table_default_header_cell>
<.table_default_header_cell>{gettext("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 class="max-w-xs">
<div class="space-y-1">
<div class="font-medium text-sm truncate">
{PhoenixKit.Modules.Emails.Template.get_translation(
template.display_name,
@display_locale
)}
</div>
<div class="text-xs text-base-content/70">{template.name}</div>
<%= if template.is_system do %>
<div class="badge badge-info badge-xs h-auto">{gettext("System")}</div>
<% end %>
</div>
</.table_default_cell>
<%!-- Subject Column --%>
<.table_default_cell class="max-w-xs">
<% subj =
PhoenixKit.Modules.Emails.Template.get_translation(
template.subject,
@display_locale
) %>
<div class="text-sm truncate" title={subj}>
{subj}
</div>
<% desc =
PhoenixKit.Modules.Emails.Template.get_translation(
template.description || %{},
@display_locale
) %>
<%= if desc != "" do %>
<div
class="text-xs text-base-content/70 truncate"
title={desc}
>
{desc}
</div>
<% end %>
</.table_default_cell>
<%!-- Category Column --%>
<.table_default_cell>
<%= if template.is_system do %>
<div class="badge badge-ghost badge-sm h-auto">
{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">
{gettext("%{count} uses", count: template.usage_count)}
</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">{gettext("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-uuid={template.uuid}
class="btn btn-xs btn-outline btn-primary tooltip tooltip-bottom"
data-tip={gettext("Edit Template")}
>
<.icon name="hero-pencil" class="w-3 h-3 hidden sm:inline" />
<span class="sm:hidden whitespace-nowrap">{gettext("Edit")}</span>
</button>
<%!-- Clone Button --%>
<button
phx-click="show_clone_modal"
phx-value-uuid={template.uuid}
class="btn btn-xs btn-outline tooltip tooltip-bottom"
data-tip={gettext("Clone Template")}
>
<.icon name="hero-document-duplicate" class="w-3 h-3 hidden sm:inline" />
<span class="sm:hidden whitespace-nowrap">{gettext("Clone")}</span>
</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-uuid={template.uuid}
class="btn btn-xs btn-outline tooltip tooltip-bottom"
data-tip={gettext("Archive Template")}
>
<.icon name="hero-archive-box" class="w-3 h-3 hidden sm:inline" />
<span class="sm:hidden whitespace-nowrap">{gettext("Archive")}</span>
</button>
<% else %>
<button
phx-click="activate_template"
phx-value-uuid={template.uuid}
class="btn btn-xs btn-outline btn-success tooltip tooltip-bottom"
data-tip={gettext("Activate Template")}
>
<.icon name="hero-check-circle" class="w-3 h-3 hidden sm:inline" />
<span class="sm:hidden whitespace-nowrap">{gettext("Activate")}</span>
</button>
<% end %>
<%!-- Delete Button --%>
<button
phx-click="request_delete"
phx-value-uuid={template.uuid}
phx-value-name={template.name}
class="btn btn-xs btn-outline text-error hover:btn-error tooltip tooltip-bottom"
data-tip={gettext("Delete Template")}
>
<.icon name="hero-trash" class="w-3 h-3 hidden sm:inline" />
<span class="sm:hidden whitespace-nowrap">{gettext("Delete")}</span>
</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">
{gettext("No templates found")}
</h3>
<p class="text-base-content/70 mb-4">
{gettext("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/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">{gettext("Clone Template")}</h3>
<%= if @clone_template do %>
<p class="text-sm text-base-content/70 mb-4">
{gettext("Creating a copy of")} "<strong>{PhoenixKit.Modules.Emails.Template.get_translation(@clone_template.display_name, @display_locale)}</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">{gettext("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">{gettext("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"
>
{gettext("Cancel")}
</button>
<button type="submit" class="btn btn-primary">
{gettext("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">
{gettext("Cancel")}
</button>
<button
class="btn btn-error"
phx-click="confirm_action"
phx-value-action={@confirmation_modal.action}
phx-value-uuid={@confirmation_modal.uuid}
>
{@confirmation_modal.button_text}
</button>
</div>
</div>
</div>
<% end %>
</div>
</PhoenixKitWeb.Components.LayoutWrapper.app_layout>