Current section
Files
Jump to
Current section
Files
lib/phoenix_kit/modules/emails/web/templates.html.heex
<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>
<label class="select">
<select name="filter[category]">
<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>
</label>
</div>
<%!-- Status Filter --%>
<div class="form-control">
<label class="label">
<span class="label-text">{gettext("Status")}</span>
</label>
<label class="select">
<select name="filter[status]">
<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>
</label>
</div>
<%!-- System/Custom Filter --%>
<div class="form-control">
<label class="label">
<span class="label-text">{gettext("Type")}</span>
</label>
<label class="select">
<select name="filter[is_system]">
<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>
</label>
</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>
<.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>