feat(templates): add localized push templates and actions
Share template keys across email and push with independent editors and revisions. Render localized actions and preserve version-one delivery. Ref: IT-1627
This commit is contained in:
@@ -2,16 +2,16 @@
|
||||
@model EmailTemplateDefinitionDeleteViewModel
|
||||
@{ ViewData["Title"] = "Delete email template"; var returnUrl = Url.Action("Index", "AdminTemplates", new { serviceId = Model.ServiceId, key = Model.KeyFilter }); }
|
||||
<div class="admin-delete-confirmation">
|
||||
<nav aria-label="Breadcrumb"><ol class="breadcrumb"><li class="breadcrumb-item"><a href="/admin/services">Services</a></li><li class="breadcrumb-item"><a href="@returnUrl">@Model.ServiceName</a></li><li class="breadcrumb-item active" aria-current="page">Delete template</li></ol></nav>
|
||||
<nav aria-label="Breadcrumb"><ol class="breadcrumb"><li class="breadcrumb-item"><a href="/admin/services">Services</a></li><li class="breadcrumb-item"><a href="@returnUrl">@Model.ServiceName</a></li><li class="breadcrumb-item active" aria-current="page">Delete Email template</li></ol></nav>
|
||||
<h2>Delete template @Model.Key?</h2>
|
||||
<div asp-validation-summary="All" class="text-danger" role="alert"></div>
|
||||
<div class="alert alert-danger">This permanently deletes the definition, shared variables and <strong>@Model.LanguageCodes.Count translation(s)</strong> from service @Model.ServiceName.</div>
|
||||
<div class="alert alert-danger">This permanently deletes the Email definition, its shared variables and <strong>@Model.LanguageCodes.Count translation(s)</strong> from service @Model.ServiceName.</div>
|
||||
<p>Languages: @(Model.LanguageCodes.Count == 0 ? "No translations" : string.Join(", ", Model.LanguageCodes))</p>
|
||||
<p>The service, its language catalog and email channels are retained.</p>
|
||||
<p>The Push template, service, language catalog and delivery channels are retained.</p>
|
||||
<form asp-action="DeleteDefinition" asp-route-serviceId="@Model.ServiceId" asp-route-definitionId="@Model.DefinitionId" method="post">
|
||||
@Html.AntiForgeryToken()
|
||||
<input asp-for="Revision" type="hidden" /><input asp-for="KeyFilter" type="hidden" />
|
||||
<div class="form-check mb-3"><input asp-for="ConfirmDelete" class="form-check-input" /><label asp-for="ConfirmDelete" class="form-check-label">I confirm deletion of @Model.Key and its @Model.LanguageCodes.Count translation(s).</label></div>
|
||||
<div class="form-check mb-3"><input asp-for="ConfirmDelete" class="form-check-input" /><label asp-for="ConfirmDelete" class="form-check-label">I confirm deletion of the Email template @Model.Key and its @Model.LanguageCodes.Count translation(s).</label></div>
|
||||
<div class="d-flex flex-wrap gap-2"><button type="submit" class="btn btn-danger">Delete template</button><a class="btn btn-secondary" href="@returnUrl">Cancel</a></div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -29,13 +29,14 @@
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label asp-for="Key" class="form-label fw-semibold">Template Key</label>
|
||||
<input asp-for="Key" class="form-control" readonly="@(!Model.IsNew)" />
|
||||
<input asp-for="Key" class="form-control" readonly="@(!Model.IsNew || Model.LockKey)" />
|
||||
</div>
|
||||
</div>
|
||||
<partial name="_TemplateChannelNavigation" model="@(new TemplateChannelNavigationViewModel { ServiceId = Model.ServiceId, Key = Model.Key, KeyFilter = Model.KeyFilter })" />
|
||||
<div class="mb-3">
|
||||
<label asp-for="VariablesJson" class="form-label fw-semibold">Shared variables (JSON)</label>
|
||||
<textarea asp-for="VariablesJson" class="form-control font-monospace" rows="14"></textarea>
|
||||
<div class="form-text">One contract for every language: <code>[{"name":"UserName","required":true}]</code></div>
|
||||
<div class="form-text">Email variables, shared across its languages: <code>[{"name":"UserName","required":true}]</code></div>
|
||||
</div>
|
||||
<p class="text-muted">Every configured service language has a tab. Present and Missing show saved versions. Leave all three content fields blank for an unused language; clear them to remove an existing version. Shared variables and the template remain.</p>
|
||||
@if (Model.Languages.Count == 0) { <p>No service languages configured. You can save the key and shared variables now, then add language codes in Edit Service.</p> }
|
||||
@@ -102,6 +103,7 @@
|
||||
<a id="cancelTemplate" href="@Url.Action("Index", "AdminTemplates", new { serviceId = Model.ServiceId, key = Model.KeyFilter })" class="btn btn-secondary">Cancel</a>
|
||||
}
|
||||
@section Scripts {
|
||||
<script src="~/js/template-channel-navigation.js" asp-append-version="true"></script>
|
||||
<script src="~/js/email-template-preview.js" asp-append-version="true"></script>
|
||||
<script src="~/js/email-template-editor.js" asp-append-version="true"></script>
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
}
|
||||
</div>
|
||||
<div class="page-header">
|
||||
<h3 class="h4">Email Templates</h3>
|
||||
<h3 class="h4">Templates</h3>
|
||||
<a asp-action="Create" asp-route-serviceId="@serviceId" asp-route-keyFilter="@keyFilter" class="btn btn-primary">Create template</a>
|
||||
</div>
|
||||
<div asp-validation-summary="All" class="text-danger mb-3" role="alert"></div>
|
||||
@@ -40,12 +40,13 @@
|
||||
</div>
|
||||
</form>
|
||||
</th>
|
||||
<th scope="col">Languages</th>
|
||||
<th scope="col">Email</th>
|
||||
<th scope="col">Push</th>
|
||||
<th scope="col" class="template-actions">Action</th>
|
||||
</tr></thead><tbody>
|
||||
@if (Model.Definitions.Count == 0)
|
||||
@if (Model.Definitions.Count == 0 && Model.PushDefinitions.Count == 0)
|
||||
{
|
||||
<tr><td colspan="3"><div class="empty-state">
|
||||
<tr><td colspan="4"><div class="empty-state">
|
||||
@if (!ViewData.ModelState.IsValid)
|
||||
{
|
||||
<p class="mb-0">Templates are unavailable. Review the error and try again.</p>
|
||||
@@ -56,18 +57,21 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
<i class="bi bi-envelope" aria-hidden="true"></i><p class="mt-2 mb-0">No email templates for this service yet.</p><a asp-action="Create" asp-route-serviceId="@serviceId" class="btn btn-primary btn-sm mt-3">Create First Template</a>
|
||||
<i class="bi bi-envelope" aria-hidden="true"></i><p class="mt-2 mb-0">No templates for this service yet.</p><a asp-action="Create" asp-route-serviceId="@serviceId" class="btn btn-primary btn-sm mt-3">Create First Template</a>
|
||||
}
|
||||
</div></td></tr>
|
||||
}
|
||||
@foreach (var template in Model.Definitions)
|
||||
@foreach (var templateKey in Model.Definitions.Select(d => d.Key).Concat(Model.PushDefinitions.Select(d => d.Key)).Distinct(StringComparer.Ordinal).OrderBy(k => k, StringComparer.Ordinal))
|
||||
{
|
||||
var editUrl = Url.Action("Edit", "AdminTemplates", new { serviceId, definitionId = template.Id, keyFilter });
|
||||
var presentCodes = template.Languages.Select(l => l.LanguageCode).ToHashSet(StringComparer.Ordinal);
|
||||
var email = Model.Definitions.SingleOrDefault(d => d.Key == templateKey);
|
||||
var push = Model.PushDefinitions.SingleOrDefault(d => d.Key == templateKey);
|
||||
var editUrl = email is null ? Url.Action("Edit", "AdminPushTemplates", new { serviceId, key = templateKey, keyFilter }) : Url.Action("Edit", "AdminTemplates", new { serviceId, definitionId = email.Id, keyFilter });
|
||||
var presentCodes = email?.Languages.Select(l => l.LanguageCode).ToHashSet(StringComparer.Ordinal) ?? [];
|
||||
<tr>
|
||||
<td>@template.Key</td>
|
||||
<td>@templateKey</td>
|
||||
<td>
|
||||
@if (Model.Service.Languages.Count == 0)
|
||||
@if (email is null) { <span>Not configured</span> }
|
||||
else if (Model.Service.Languages.Count == 0)
|
||||
{
|
||||
<span>No configured languages</span>
|
||||
}
|
||||
@@ -84,7 +88,22 @@
|
||||
</ul>
|
||||
}
|
||||
</td>
|
||||
<td class="template-actions"><div class="list-action-links"><a href="@editUrl" class="btn btn-outline-primary btn-sm" aria-label="Edit template @template.Key">Edit</a><a asp-action="ConfirmDefinitionDelete" asp-route-serviceId="@serviceId" asp-route-definitionId="@template.Id" asp-route-keyFilter="@keyFilter" class="btn btn-outline-danger btn-sm" aria-label="Delete template @template.Key">Delete</a></div></td>
|
||||
<td>
|
||||
@if (push is null) { <span>Not configured</span> }
|
||||
else if (Model.Service.Languages.Count == 0) { <span>No configured languages</span> }
|
||||
else {
|
||||
var pushCodes = push.Languages.Select(l => l.LanguageCode).ToHashSet(StringComparer.Ordinal);
|
||||
<ul class="template-language-statuses">
|
||||
@foreach (var language in Model.Service.Languages) {
|
||||
var present = pushCodes.Contains(language.Code);
|
||||
<li class="template-language-status @(present ? "template-language-present" : "template-language-missing")" data-language-code="@language.Code" data-state="@(present ? "present" : "missing")"><span>@language.Code:</span><span>@(present ? "Present" : "Missing")</span></li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
</td>
|
||||
<td class="template-actions"><div class="list-action-links"><a href="@editUrl" class="btn btn-outline-primary btn-sm" aria-label="Edit template @templateKey">Edit</a>@if (email is not null) { <a asp-action="ConfirmDefinitionDelete" asp-route-serviceId="@serviceId" asp-route-definitionId="@email.Id" asp-route-keyFilter="@keyFilter" class="btn btn-outline-danger btn-sm" aria-label="Delete Email template @templateKey">Delete</a> }
|
||||
@if (push is not null) { <a asp-action="ConfirmDelete" asp-controller="AdminPushTemplates" asp-route-serviceId="@serviceId" asp-route-key="@templateKey" asp-route-keyFilter="@keyFilter" class="btn btn-outline-danger btn-sm">Delete Push</a> }
|
||||
</div></td>
|
||||
</tr>
|
||||
}
|
||||
</tbody></table></div>
|
||||
|
||||
Reference in New Issue
Block a user