Move email previews onto the Club identity page; add PDF previews
Three top-level tabs (Identity/Email/PDF) replace the standalone Settings > Email previews page and the small mock that used to live inside the Identity tab's own Email sub-tab. Email and PDF now show every real branded email/generated PDF this club can send, rendered with sample data via a shared _document_preview_card.html partial (email cards keep the HTML/plain-text toggle; PDF cards -- the underlying HTML WeasyPrint would turn into a PDF, shown directly rather than round-tripping through WeasyPrint -- don't need one). Added a password-reset email preview (previously missing from the audit -- it's allauth's own send path, not one of this app's, so it didn't show up in the earlier grep for EmailMultiAlternatives/.send() calls). Dropped parent-claim-approved: not directly used right now. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
This commit is contained in:
34
management/templates/management/_document_preview_card.html
Normal file
34
management/templates/management/_document_preview_card.html
Normal file
@@ -0,0 +1,34 @@
|
||||
{% load i18n %}
|
||||
|
||||
{% comment %}
|
||||
One card in the Club identity page's Email/PDF tabs -- included with
|
||||
`preview` (a dict: key/label/description, plus subject/text for an email),
|
||||
`render_url` (the iframe's src, already resolved via {% url %} by the
|
||||
caller since {% url %} can't take a variable view name here), and
|
||||
`show_text_toggle` (True for an email, which has a plain-text body to
|
||||
switch to; False for a PDF, which doesn't).
|
||||
{% endcomment %}
|
||||
<div class="card overflow-hidden" data-doc-card>
|
||||
<div class="flex flex-wrap items-start justify-between gap-3 border-b border-line px-4.5 py-3.5">
|
||||
<div>
|
||||
<div class="font-display text-sm font-extrabold tracking-wide text-ink uppercase">{{ preview.label }}</div>
|
||||
<p class="mt-1 max-w-2xl text-sm text-muted">{{ preview.description }}</p>
|
||||
{% if preview.subject %}
|
||||
<div class="mt-2 font-mono text-xs text-muted"><span class="text-dim">{% trans "Subject:" %}</span> {{ preview.subject }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if show_text_toggle %}
|
||||
<div class="flex items-center gap-1 rounded-full bg-steel p-1">
|
||||
<button type="button" class="view-toggle-btn active" data-view-btn="html">{% trans "Email" %}</button>
|
||||
<button type="button" class="view-toggle-btn" data-view-btn="text">{% trans "Plain text" %}</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="bg-subhead p-4" data-view-panel="html">
|
||||
<iframe class="h-[560px] w-full rounded-lg border border-line bg-white" src="{{ render_url }}" loading="lazy" title="{{ preview.label }}"></iframe>
|
||||
</div>
|
||||
{% if show_text_toggle %}
|
||||
<pre class="hidden overflow-x-auto p-4.5 font-mono text-sm whitespace-pre-wrap text-ink" data-view-panel="text">{{ preview.text }}</pre>
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -93,7 +93,6 @@
|
||||
<div class="flex flex-col">
|
||||
{% if is_club_admin %}
|
||||
<a class="nav-subitem {% if nav == 'club_settings' %}active{% endif %}" href="{% url 'management:club_settings' %}">{% trans "Club identity" %}</a>
|
||||
<a class="nav-subitem {% if nav == 'email_preview_list' %}active{% endif %}" href="{% url 'management:email_preview_list' %}">{% trans "Email previews" %}</a>
|
||||
{% endif %}
|
||||
<a class="nav-subitem {% if nav == 'onboarding_requirement_list' %}active{% endif %}" href="{% url 'management:onboarding_requirement_list' %}">{% trans "Onboarding requirements" %}</a>
|
||||
{% if is_club_admin %}
|
||||
|
||||
@@ -9,124 +9,128 @@
|
||||
closely for the Colours card (merged swatch+hex row, side-by-side
|
||||
contrast-check boxes) and the Logo card (dashed drop zone) -- markup
|
||||
pulled straight from the design canvas (RosterChief Platform.dc.html),
|
||||
not the prose summary. The live preview has two tabs rather than D9's
|
||||
three: App and Website are merged into one (they were always shown
|
||||
together anyway, and the mobile app isn't built yet -- the phone mock
|
||||
is the same colours applied ahead of it existing); Email is real and
|
||||
functional, a generic sample shaped like the actual branded emails this
|
||||
app sends (club/templates/club/email/*.html, members/.../claim_approved.html),
|
||||
not a preview of any one specific message. The colour math (content
|
||||
not the prose summary.
|
||||
|
||||
Three top-level tabs, not just a preview card: Identity (the settings
|
||||
form, plus a small live-updating App & Website mock -- colours from the
|
||||
unsaved form inputs, via the script below) is D9 as designed. Email and
|
||||
PDF are real, not mocks -- every branded email (management/email_previews.py)
|
||||
and generated PDF (management/pdf_previews.py) this club can actually send,
|
||||
rendered with sample data. Those two read the club's *saved* colours
|
||||
(server-rendered in an iframe, see EmailPreviewRenderView/PDFPreviewRenderView),
|
||||
so -- unlike the Identity tab's own mock -- they only reflect a colour
|
||||
change after it's saved, not as you type. The colour math (content
|
||||
colour, contrast ratio) mirrors Club._content_color_for exactly, so
|
||||
what's shown is the real number the server computes on save, not an
|
||||
approximation.
|
||||
what's shown on the Identity tab is the real number the server computes
|
||||
on save, not an approximation.
|
||||
{% endcomment %}
|
||||
|
||||
{% block panel_title %}{% trans "Club identity" %}{% endblock panel_title %}
|
||||
{% block heading %}{% trans "Club identity" %}{% endblock heading %}
|
||||
|
||||
{% block actions %}
|
||||
<button class="btn btn-primary gap-2" type="submit" form="club-settings-form">{% lucide "save" size=16 %} {% trans "Save changes" %}</button>
|
||||
<button class="btn btn-primary gap-2" type="submit" form="club-settings-form" data-identity-only>{% lucide "save" size=16 %} {% trans "Save changes" %}</button>
|
||||
{% endblock actions %}
|
||||
|
||||
{% block panel %}
|
||||
<div class="grid grid-cols-1 gap-5 xl:grid-cols-[1.2fr_1fr]">
|
||||
<form id="club-settings-form" method="post" enctype="multipart/form-data" class="flex flex-col gap-5">
|
||||
{% csrf_token %}
|
||||
<div class="mb-1 flex w-fit overflow-hidden rounded-md border border-line">
|
||||
<button type="button" class="preview-tab preview-tab-active" data-page-tab="identity">{% trans "Identity" %}</button>
|
||||
<button type="button" class="preview-tab" data-page-tab="email">{% trans "Email" %}</button>
|
||||
<button type="button" class="preview-tab" data-page-tab="pdf">{% trans "PDF" %}</button>
|
||||
</div>
|
||||
|
||||
{% for error in form.non_field_errors %}
|
||||
<div class="alert alert-error">
|
||||
{% lucide "circle-x" size=18 %}
|
||||
<span>{{ error }}</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<div data-page-panel="identity">
|
||||
<div class="grid grid-cols-1 gap-5 xl:grid-cols-[1.2fr_1fr]">
|
||||
<form id="club-settings-form" method="post" enctype="multipart/form-data" class="flex flex-col gap-5">
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="card-title">{% lucide "building-2" size=16 %} {% trans "Club" %}</div>
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
{% form_field form.name %}
|
||||
{% form_field form.legal_name %}
|
||||
{% form_field form.contact_email %}
|
||||
{% form_field form.website %}
|
||||
{% for error in form.non_field_errors %}
|
||||
<div class="alert alert-error">
|
||||
{% lucide "circle-x" size=18 %}
|
||||
<span>{{ error }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="flex items-center justify-between gap-2">
|
||||
<div class="card-title">{% lucide "palette" size=16 %} {% trans "Colours" %}</div>
|
||||
<span class="font-mono text-xs text-dim">{% trans "Accents only" %}</span>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<div class="mb-1.5 font-display text-[11px] font-bold tracking-[.12em] text-muted uppercase">{{ form.primary_color.label|capfirst }}</div>
|
||||
<label class="club-color-row">
|
||||
<input type="color" id="primary_color_swatch" class="club-color-swatch" value="{{ form.primary_color.value|default:"#0b1220" }}" aria-label="{% trans "Pick the primary colour" %}">
|
||||
<input type="text" name="{{ form.primary_color.html_name }}" id="{{ form.primary_color.id_for_label }}" value="{{ form.primary_color.value|default:"" }}" placeholder="#0b1220" class="club-color-hex-input">
|
||||
</label>
|
||||
{% if form.primary_color.errors %}<p class="mt-1 text-xs text-club-dark">{{ form.primary_color.errors|join:" " }}</p>{% endif %}
|
||||
</div>
|
||||
<div>
|
||||
<div class="mb-1.5 font-display text-[11px] font-bold tracking-[.12em] text-muted uppercase">{{ form.secondary_color.label|capfirst }}</div>
|
||||
<label class="club-color-row">
|
||||
<input type="color" id="secondary_color_swatch" class="club-color-swatch" value="{{ form.secondary_color.value|default:"#e4002b" }}" aria-label="{% trans "Pick the secondary colour" %}">
|
||||
<input type="text" name="{{ form.secondary_color.html_name }}" id="{{ form.secondary_color.id_for_label }}" value="{{ form.secondary_color.value|default:"" }}" placeholder="#e4002b" class="club-color-hex-input">
|
||||
</label>
|
||||
{% if form.secondary_color.errors %}<p class="mt-1 text-xs text-club-dark">{{ form.secondary_color.errors|join:" " }}</p>{% endif %}
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="card-title">{% lucide "building-2" size=16 %} {% trans "Club" %}</div>
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
{% form_field form.name %}
|
||||
{% form_field form.legal_name %}
|
||||
{% form_field form.contact_email %}
|
||||
{% form_field form.website %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<div class="mb-2 font-display text-[11px] font-bold tracking-[.12em] text-muted uppercase">{% trans "Contrast check" %}</div>
|
||||
<div class="flex gap-2">
|
||||
<div id="contrast-box-primary" class="club-contrast-box">
|
||||
<span id="contrast-label-primary"></span>
|
||||
<span id="contrast-ratio-primary" class="font-mono"></span>
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="flex items-center justify-between gap-2">
|
||||
<div class="card-title">{% lucide "palette" size=16 %} {% trans "Colours" %}</div>
|
||||
<span class="font-mono text-xs text-dim">{% trans "Accents only" %}</span>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<div class="mb-1.5 font-display text-[11px] font-bold tracking-[.12em] text-muted uppercase">{{ form.primary_color.label|capfirst }}</div>
|
||||
<label class="club-color-row">
|
||||
<input type="color" id="primary_color_swatch" class="club-color-swatch" value="{{ form.primary_color.value|default:"#0b1220" }}" aria-label="{% trans "Pick the primary colour" %}">
|
||||
<input type="text" name="{{ form.primary_color.html_name }}" id="{{ form.primary_color.id_for_label }}" value="{{ form.primary_color.value|default:"" }}" placeholder="#0b1220" class="club-color-hex-input">
|
||||
</label>
|
||||
{% if form.primary_color.errors %}<p class="mt-1 text-xs text-club-dark">{{ form.primary_color.errors|join:" " }}</p>{% endif %}
|
||||
</div>
|
||||
<div id="contrast-box-secondary" class="club-contrast-box">
|
||||
<span id="contrast-label-secondary"></span>
|
||||
<span id="contrast-ratio-secondary" class="font-mono"></span>
|
||||
<div>
|
||||
<div class="mb-1.5 font-display text-[11px] font-bold tracking-[.12em] text-muted uppercase">{{ form.secondary_color.label|capfirst }}</div>
|
||||
<label class="club-color-row">
|
||||
<input type="color" id="secondary_color_swatch" class="club-color-swatch" value="{{ form.secondary_color.value|default:"#e4002b" }}" aria-label="{% trans "Pick the secondary colour" %}">
|
||||
<input type="text" name="{{ form.secondary_color.html_name }}" id="{{ form.secondary_color.id_for_label }}" value="{{ form.secondary_color.value|default:"" }}" placeholder="#e4002b" class="club-color-hex-input">
|
||||
</label>
|
||||
{% if form.secondary_color.errors %}<p class="mt-1 text-xs text-club-dark">{{ form.secondary_color.errors|join:" " }}</p>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<div class="mb-2 font-display text-[11px] font-bold tracking-[.12em] text-muted uppercase">{% trans "Contrast check" %}</div>
|
||||
<div class="flex gap-2">
|
||||
<div id="contrast-box-primary" class="club-contrast-box">
|
||||
<span id="contrast-label-primary"></span>
|
||||
<span id="contrast-ratio-primary" class="font-mono"></span>
|
||||
</div>
|
||||
<div id="contrast-box-secondary" class="club-contrast-box">
|
||||
<span id="contrast-label-secondary"></span>
|
||||
<span id="contrast-ratio-secondary" class="font-mono"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="card-title">{% lucide "image" size=16 %} {% trans "Logo" %}</div>
|
||||
<div class="flex items-end gap-4">
|
||||
<div>
|
||||
<div class="mb-1.5 font-display text-[11px] font-bold tracking-[.12em] text-muted uppercase">{% trans "Crest" %}</div>
|
||||
<div class="club-logo-dropzone">
|
||||
{% if club.logo %}
|
||||
<img class="h-full w-full object-contain p-1.5" src="{{ club.logo.url }}" alt="">
|
||||
{% else %}
|
||||
<span class="font-display text-sm font-extrabold text-muted">{{ club.initials }}</span>
|
||||
{% endif %}
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="card-title">{% lucide "image" size=16 %} {% trans "Logo" %}</div>
|
||||
<div class="flex items-end gap-4">
|
||||
<div>
|
||||
<div class="mb-1.5 font-display text-[11px] font-bold tracking-[.12em] text-muted uppercase">{% trans "Crest" %}</div>
|
||||
<div class="club-logo-dropzone">
|
||||
{% if club.logo %}
|
||||
<img class="h-full w-full object-contain p-1.5" src="{{ club.logo.url }}" alt="">
|
||||
{% else %}
|
||||
<span class="font-display text-sm font-extrabold text-muted">{{ club.initials }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1">{% form_field form.logo %}</div>
|
||||
</div>
|
||||
<div class="flex-1">{% form_field form.logo %}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</form>
|
||||
|
||||
<div class="xl:sticky xl:top-4 xl:self-start">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="flex items-center justify-between gap-2">
|
||||
<div class="xl:sticky xl:top-4 xl:self-start">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="card-title">{% lucide "eye" size=16 %} {% trans "Live preview" %}</div>
|
||||
<div class="flex overflow-hidden rounded-md border border-line">
|
||||
<button type="button" class="preview-tab preview-tab-active" data-preview-tab="app">{% trans "App & Website" %}</button>
|
||||
<button type="button" class="preview-tab" data-preview-tab="email">{% trans "Email" %}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<div id="preview-panel-app" class="flex flex-wrap items-start gap-4" data-preview-panel="app">
|
||||
<div class="mt-3 flex flex-wrap items-start gap-4">
|
||||
<div class="preview-phone-lg">
|
||||
<div id="preview-phone-header" class="preview-phone-header">
|
||||
<div class="preview-phone-header-row">
|
||||
@@ -173,30 +177,31 @@
|
||||
<li class="flex items-center gap-2"><span id="preview-dot-1" class="preview-brand-dot"></span>{% trans "Sidebar, active nav item, primary buttons" %}</li>
|
||||
<li class="flex items-center gap-2"><span id="preview-dot-2" class="preview-brand-dot"></span>{% trans "Public site header and join button" %}</li>
|
||||
<li class="flex items-center gap-2"><span id="preview-dot-3" class="preview-brand-dot"></span>{% trans "Mobile app header and highlights" %}</li>
|
||||
<li class="flex items-center gap-2"><span id="preview-dot-4" class="preview-brand-dot"></span>{% trans "Transactional email crest and buttons" %}</li>
|
||||
<li class="flex items-center gap-2"><span id="preview-dot-4" class="preview-brand-dot"></span>{% trans "Emails and PDFs -- see the Email/PDF tabs above" %}</li>
|
||||
<li class="flex items-center gap-2"><span class="preview-brand-dot preview-brand-dot-neutral"></span>{% trans "Never: status colours, tables, form fields" %}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="preview-panel-email" class="preview-email hidden" data-preview-panel="email">
|
||||
<div class="preview-email-header">
|
||||
<span id="preview-email-crest" class="preview-crest preview-crest-sm preview-email-crest">{{ club.initials|default:"CL" }}</span>
|
||||
<span class="preview-email-name">{{ club.name|default:_("Your club") }}</span>
|
||||
</div>
|
||||
<div class="preview-email-body">
|
||||
<div class="preview-skeleton-line"></div>
|
||||
<div class="preview-skeleton-line preview-skeleton-line-short"></div>
|
||||
<span id="preview-email-button" class="preview-email-button">{% trans "View details" %}</span>
|
||||
<div class="preview-skeleton-line preview-skeleton-line-short"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hidden flex-col gap-4" data-page-panel="email">
|
||||
{% for preview in email_previews %}
|
||||
{% url 'management:email_preview_render' preview.key as render_url %}
|
||||
{% include "management/_document_preview_card.html" with preview=preview render_url=render_url show_text_toggle=True %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div class="hidden flex-col gap-4" data-page-panel="pdf">
|
||||
{% for preview in pdf_previews %}
|
||||
{% url 'management:pdf_preview_render' preview.key as render_url %}
|
||||
{% include "management/_document_preview_card.html" with preview=preview render_url=render_url show_text_toggle=False %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endblock panel %}
|
||||
|
||||
{% block extra_body %}
|
||||
@@ -257,15 +262,6 @@
|
||||
document.getElementById("preview-website-join").style.background = secondary;
|
||||
document.getElementById("preview-website-join").style.color = secondaryContent;
|
||||
|
||||
// Matches the real branded emails (club/templates/club/email/*.html):
|
||||
// a plain white header with a secondary-coloured crest badge, and a
|
||||
// primary-coloured button in the body -- same split as _button.html's
|
||||
// own bg=primary convention, not the phone/website mocks' solid bar.
|
||||
document.getElementById("preview-email-crest").style.background = secondary;
|
||||
document.getElementById("preview-email-crest").style.color = secondaryContent;
|
||||
document.getElementById("preview-email-button").style.background = primary;
|
||||
document.getElementById("preview-email-button").style.color = primaryContent;
|
||||
|
||||
["preview-dot-1", "preview-dot-2", "preview-dot-3", "preview-dot-4"].forEach((id) => {
|
||||
document.getElementById(id).style.background = secondary;
|
||||
});
|
||||
@@ -294,13 +290,29 @@
|
||||
|
||||
updatePreview();
|
||||
|
||||
// Live preview tabs -- toggles which .preview-panel is visible, App &
|
||||
// Website vs Email. Both panels stay in the DOM (and colour-bound) the
|
||||
// whole time; only which one is hidden changes.
|
||||
document.querySelectorAll("[data-preview-tab]").forEach((tab) => {
|
||||
// Top-level page tabs -- Identity/Email/PDF. "Save changes" (the
|
||||
// topbar action) only applies to the Identity tab's own form, so it
|
||||
// hides on the other two rather than sitting there doing nothing.
|
||||
document.querySelectorAll("[data-page-tab]").forEach((tab) => {
|
||||
tab.addEventListener("click", () => {
|
||||
document.querySelectorAll("[data-preview-tab]").forEach((other) => other.classList.toggle("preview-tab-active", other === tab));
|
||||
document.querySelectorAll("[data-preview-panel]").forEach((panel) => panel.classList.toggle("hidden", panel.dataset.previewPanel !== tab.dataset.previewTab));
|
||||
document.querySelectorAll("[data-page-tab]").forEach((other) => other.classList.toggle("preview-tab-active", other === tab));
|
||||
document.querySelectorAll("[data-page-panel]").forEach((panel) => {
|
||||
const active = panel.dataset.pagePanel === tab.dataset.pageTab;
|
||||
panel.classList.toggle("hidden", !active);
|
||||
panel.classList.toggle("flex", active && panel.dataset.pagePanel !== "identity");
|
||||
});
|
||||
document.querySelectorAll("[data-identity-only]").forEach((el) => el.classList.toggle("hidden", tab.dataset.pageTab !== "identity"));
|
||||
});
|
||||
});
|
||||
|
||||
// Email/PDF cards' own HTML/Plain text toggle -- scoped per card so
|
||||
// several cards on the same tab each keep independent state.
|
||||
document.querySelectorAll("[data-doc-card]").forEach((card) => {
|
||||
card.querySelectorAll("[data-view-btn]").forEach((button) => {
|
||||
button.addEventListener("click", () => {
|
||||
card.querySelectorAll("[data-view-btn]").forEach((b) => b.classList.toggle("active", b === button));
|
||||
card.querySelectorAll("[data-view-panel]").forEach((el) => el.classList.toggle("hidden", el.dataset.viewPanel !== button.dataset.viewBtn));
|
||||
});
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
{% extends "management/base.html" %}
|
||||
{% load i18n lucide %}
|
||||
|
||||
{% block heading %}{% trans "Email previews" %}{% endblock heading %}
|
||||
{% block topbar_context %}<span class="text-sm text-muted">{% trans "Exactly what a member or parent receives -- with sample data, nothing is sent." %}</span>{% endblock topbar_context %}
|
||||
|
||||
{% block panel %}
|
||||
{% for preview in previews %}
|
||||
<div class="card overflow-hidden" data-email-card>
|
||||
<div class="flex flex-wrap items-start justify-between gap-3 border-b border-line px-4.5 py-3.5">
|
||||
<div>
|
||||
<div class="font-display text-sm font-extrabold tracking-wide text-ink uppercase">{{ preview.label }}</div>
|
||||
<p class="mt-1 max-w-2xl text-sm text-muted">{{ preview.description }}</p>
|
||||
<div class="mt-2 font-mono text-xs text-muted"><span class="text-dim">{% trans "Subject:" %}</span> {{ preview.subject }}</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-1 rounded-full bg-steel p-1">
|
||||
<button type="button" class="view-toggle-btn active" data-view-btn="html">{% trans "Email" %}</button>
|
||||
<button type="button" class="view-toggle-btn" data-view-btn="text">{% trans "Plain text" %}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-subhead p-4" data-view-panel="html">
|
||||
<iframe class="h-[560px] w-full rounded-lg border border-line bg-white" src="{% url 'management:email_preview_render' preview.key %}" loading="lazy" title="{{ preview.label }}"></iframe>
|
||||
</div>
|
||||
<pre class="hidden overflow-x-auto p-4.5 font-mono text-sm whitespace-pre-wrap text-ink" data-view-panel="text">{{ preview.text }}</pre>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endblock panel %}
|
||||
|
||||
{% block extra_body %}
|
||||
<script>
|
||||
(() => {
|
||||
document.querySelectorAll("[data-email-card]").forEach((card) => {
|
||||
card.querySelectorAll("[data-view-btn]").forEach((button) => {
|
||||
button.addEventListener("click", () => {
|
||||
card.querySelectorAll("[data-view-btn]").forEach((b) => b.classList.toggle("active", b === button));
|
||||
card.querySelectorAll("[data-view-panel]").forEach((el) => el.classList.toggle("hidden", el.dataset.viewPanel !== button.dataset.viewBtn));
|
||||
});
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
{% endblock extra_body %}
|
||||
Reference in New Issue
Block a user