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:
@@ -0,0 +1,12 @@
|
||||
@model HrynCo.NotificationService.Web.Controllers.Admin.ViewModels.PushTemplateEditViewModel
|
||||
@{ ViewData["Title"] = "Delete Push Template"; }
|
||||
<h1 class="h3">Delete Push Template</h1>
|
||||
<p>Delete the Push template <strong>@Model.Key</strong> and its @Model.Languages.Count language versions from <strong>@Model.ServiceName</strong>? The Email template is retained.</p>
|
||||
<form method="post" asp-action="Delete" asp-route-serviceId="@Model.ServiceId">
|
||||
@Html.AntiForgeryToken()
|
||||
<input asp-for="Key" type="hidden" /><input asp-for="Revision" type="hidden" /><input asp-for="KeyFilter" type="hidden" />
|
||||
<div asp-validation-summary="All" class="text-danger" role="alert"></div>
|
||||
<div class="form-check my-3"><input asp-for="ConfirmDelete" class="form-check-input" /><label asp-for="ConfirmDelete" class="form-check-label">Confirm deletion of this Push template and its translations</label></div>
|
||||
<button class="btn btn-danger" type="submit">Delete Push Template</button>
|
||||
<a class="btn btn-secondary" href="@Url.Action("Index", "AdminTemplates", new { serviceId = Model.ServiceId, key = Model.KeyFilter })">Cancel</a>
|
||||
</form>
|
||||
@@ -0,0 +1,111 @@
|
||||
@using HrynCo.NotificationService.Web.Controllers.Admin.ViewModels
|
||||
|
||||
@model PushTemplateEditViewModel
|
||||
@{
|
||||
Layout = "~/Views/Shared/_EditorLayout.cshtml";
|
||||
ViewData["Title"] = Model.PageTitle;
|
||||
ViewData["EditorTitle"] = Model.PageTitle;
|
||||
|
||||
var active = Math.Clamp(Model.ActiveLanguageIndex, 0, Math.Max(0, Model.Languages.Count - 1));
|
||||
}
|
||||
@section Breadcrumbs {
|
||||
<nav aria-label="Breadcrumb" class="template-editor-breadcrumbs"><ol class="breadcrumb"><li class="breadcrumb-item"><a href="/admin/services">Services</a></li><li class="breadcrumb-item"><a href="/admin/services/@Model.ServiceId">@Model.ServiceName</a></li><li class="breadcrumb-item active" aria-current="page">@Model.PageTitle</li></ol></nav>
|
||||
}
|
||||
<form id="templateForm" asp-action="Save" asp-controller="AdminPushTemplates" asp-route-serviceId="@Model.ServiceId" method="post">
|
||||
@Html.AntiForgeryToken()
|
||||
<input asp-for="Id" type="hidden" />
|
||||
<input asp-for="Revision" type="hidden" />
|
||||
<input asp-for="ServiceId" type="hidden" /><input asp-for="ServiceName" type="hidden" />
|
||||
<input asp-for="KeyFilter" type="hidden" />
|
||||
<input type="hidden" id="ActiveLanguageIndex" name="ActiveLanguageIndex" value="@active" />
|
||||
<div asp-validation-summary="All" class="text-danger mb-3" role="alert"></div>
|
||||
<div class="row g-3 mb-3">
|
||||
<div class="col-md-6">
|
||||
<dl class="template-service-context">
|
||||
<dt>Service</dt>
|
||||
<dd>@Model.ServiceName</dd>
|
||||
</dl>
|
||||
<div class="form-text"><a href="/admin/services/@Model.ServiceId/edit" class="template-service-edit-link">Edit Service and language codes</a></div>
|
||||
</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.LockKey)" />
|
||||
</div>
|
||||
</div>
|
||||
<partial name="_TemplateChannelNavigation" model="@(new TemplateChannelNavigationViewModel { ServiceId = Model.ServiceId, Key = Model.Key, KeyFilter = Model.KeyFilter, IsPush = true })" />
|
||||
<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">Push 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. To remove a language version, clear Title and Body and set Actions to {}. 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> }
|
||||
<div class="template-language-sheet mb-3" hidden="@(Model.Languages.Count == 0)">
|
||||
<div class="nav nav-tabs" role="tablist" aria-label="Template languages" id="languageTabs">
|
||||
@for (var i = 0; i < Model.Languages.Count; i++)
|
||||
{
|
||||
var language = Model.Languages[i];
|
||||
<button type="button" role="tab" id="language-tab-@i" aria-controls="language-pane-@i"
|
||||
aria-selected="@(i == active ? "true" : "false")" tabindex="@(i == active ? 0 : -1)"
|
||||
class="nav-link @(i == active ? "active" : "")" data-pane="language-pane-@i">
|
||||
<span>@language.LanguageCode</span>
|
||||
<span class="template-language-status @(language.IsPresent ? "template-language-present" : "template-language-missing")">@(language.IsPresent ? "Present" : "Missing")</span>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
<div id="languagePanes">
|
||||
@for (var i = 0; i < Model.Languages.Count; i++)
|
||||
{
|
||||
<section class="language-pane" id="language-pane-@i" role="tabpanel" aria-labelledby="language-tab-@i" hidden="@(i != active)">
|
||||
<input asp-for="Languages[i].LanguageCode" type="hidden" class="language-code" />
|
||||
<div class="mb-3">
|
||||
<label asp-for="Languages[i].Title" class="form-label fw-semibold">Title</label>
|
||||
<input asp-for="Languages[i].Title" class="form-control" data-content="title" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label asp-for="Languages[i].Body" class="form-label fw-semibold">Body</label>
|
||||
<textarea asp-for="Languages[i].Body" class="form-control font-monospace" rows="8" data-content="body"></textarea>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label asp-for="Languages[i].ActionsJson" class="form-label fw-semibold">Actions (JSON)</label>
|
||||
<textarea asp-for="Languages[i].ActionsJson" class="form-control font-monospace" rows="6" data-content="actions"></textarea>
|
||||
<div class="form-text">Labels use this language. Action keys must match the message metadata; object IDs are optional for section navigation. Up to three buttons. Example: <code>{"defaultAction":"openSection","buttons":[{"action":"openSection","label":"Перейти в розділ"}]}</code>. Use <code>{}</code> for no actions.</div>
|
||||
</div>
|
||||
</section>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="template-preview-panel mb-3">
|
||||
<div class="template-preview-panel-header">
|
||||
<div>
|
||||
<div class="template-preview-title">Selected language preview: <span id="previewLanguage"></span></div>
|
||||
<div class="template-preview-subtitle">Preview uses the Push variables. Content is plain text; this does not send a notification.</div>
|
||||
</div>
|
||||
<span id="previewStatus" class="badge text-bg-secondary" role="status">Ready</span>
|
||||
</div>
|
||||
<div class="template-preview-body">
|
||||
<div class="template-preview-source template-preview-section-block">
|
||||
<div class="template-preview-section-title">Sample values</div>
|
||||
<div id="previewVariables" class="template-preview-variables"></div>
|
||||
</div>
|
||||
<div class="template-preview-output">
|
||||
<div class="template-preview-section-title">Rendered title</div>
|
||||
<div id="previewTitle" class="template-preview-subject"></div>
|
||||
<div class="template-preview-section-title mt-3">Rendered body</div>
|
||||
<pre id="previewBody" class="template-preview-text mb-0"></pre>
|
||||
<div class="template-preview-section-title mt-3">Buttons</div>
|
||||
<div id="previewActions" class="d-flex flex-wrap gap-2" aria-label="Button preview"></div>
|
||||
<div id="previewDefaultAction" class="form-text"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@section FormActions {
|
||||
<button type="submit" form="templateForm" class="btn btn-primary"><i class="bi bi-floppy me-1"></i> Save</button>
|
||||
<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-editor.js" asp-append-version="true"></script>
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
@using HrynCo.NotificationService.Web.Controllers.Admin.ViewModels
|
||||
@model TemplateChannelNavigationViewModel
|
||||
@{
|
||||
Layout = "~/Views/Shared/_EditorLayout.cshtml";
|
||||
ViewData["Title"] = "Template: " + Model.Key;
|
||||
ViewData["EditorTitle"] = ViewData["Title"];
|
||||
var channel = Model.IsPush ? "Push" : "Email";
|
||||
}
|
||||
@section Breadcrumbs {
|
||||
<nav aria-label="Breadcrumb" class="template-editor-breadcrumbs"><ol class="breadcrumb"><li class="breadcrumb-item"><a href="/admin/services">Services</a></li><li class="breadcrumb-item"><a href="/admin/services/@Model.ServiceId">@Model.ServiceName</a></li><li class="breadcrumb-item active" aria-current="page">Template</li></ol></nav>
|
||||
}
|
||||
<dl class="template-service-context"><dt>Service</dt><dd>@Model.ServiceName</dd><dt>Template Key</dt><dd>@Model.Key</dd></dl>
|
||||
<partial name="_TemplateChannelNavigation" model="Model" />
|
||||
<div class="empty-state"><p>@channel template has not been created for this key.</p>
|
||||
@if (Model.IsPush) {
|
||||
<a class="btn btn-primary" asp-action="Create" asp-route-serviceId="@Model.ServiceId" asp-route-key="@Model.Key" asp-route-keyFilter="@Model.KeyFilter">Create Push Template</a>
|
||||
} else {
|
||||
<a class="btn btn-primary" asp-action="Create" asp-controller="AdminTemplates" asp-route-serviceId="@Model.ServiceId" asp-route-templateKey="@Model.Key" asp-route-keyFilter="@Model.KeyFilter">Create Email Template</a>
|
||||
}
|
||||
</div>
|
||||
@section FormActions {
|
||||
<a href="@Url.Action("Index", "AdminTemplates", new { serviceId = Model.ServiceId, key = Model.KeyFilter })" class="btn btn-secondary">Back to Templates</a>
|
||||
}
|
||||
Reference in New Issue
Block a user