Merge the identity page's App/Website preview tabs and make Email functional
App and Website were always shown together already, so giving them separate tabs implied a toggle that didn't exist -- merged into one "App & Website" tab. The Email tab now shows a real, colour-bound sample (plain header with a secondary-coloured crest badge, a primary-coloured button) matching the actual branded emails this app already sends -- audited every mail-sending call site (claims.py, the new invoicing service, and platform billing's club-facing reminders) and the first two already use the branded shell; billing's is intentionally unbranded since it's RosterChief invoicing the club, not the club invoicing its own members. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
This commit is contained in:
@@ -1804,9 +1804,10 @@
|
||||
background: var(--color-paper);
|
||||
}
|
||||
|
||||
/* Live preview tabs -- App is the only one with real content behind it
|
||||
(Website/Email aren't built), so these are static labels, not a real
|
||||
tab control. */
|
||||
/* Live preview tabs -- a real (if minimal) tab control: click toggles which
|
||||
.preview-panel is shown (club_settings.html's own inline script). App and
|
||||
Website share one tab since the mobile app isn't built yet and the two
|
||||
mocks were already always shown together underneath it. */
|
||||
.preview-tab {
|
||||
padding: 0.3125rem 0.625rem;
|
||||
font-family: var(--font-display);
|
||||
@@ -1815,6 +1816,7 @@
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
color: var(--color-muted);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.preview-tab-active {
|
||||
@@ -2045,4 +2047,58 @@
|
||||
.preview-brand-dot-neutral {
|
||||
background: var(--color-dim);
|
||||
}
|
||||
|
||||
/* The Email tab -- a generic sample, not any one real message. Shaped like
|
||||
templates/email/_base.html's actual output (plain white header with a
|
||||
coloured crest badge, a primary-coloured button in the body) rather than
|
||||
the phone/website mocks' solid colour bar: that's genuinely how every
|
||||
branded email (club/templates/club/email/*.html, members/.../claim_approved.html)
|
||||
looks, so this preview is accurate, not another approximation. */
|
||||
.preview-email {
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--color-line);
|
||||
border-radius: 0.625rem;
|
||||
}
|
||||
|
||||
.preview-email-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.625rem 0.75rem;
|
||||
background: #fff;
|
||||
border-bottom: 1px solid var(--color-line);
|
||||
}
|
||||
|
||||
.preview-email-crest {
|
||||
transition: background-color 0.15s ease, color 0.15s ease;
|
||||
}
|
||||
|
||||
.preview-email-name {
|
||||
font-family: var(--font-display);
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
color: var(--color-ink);
|
||||
}
|
||||
|
||||
.preview-email-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
padding: 0.875rem 0.75rem;
|
||||
}
|
||||
|
||||
.preview-email-button {
|
||||
align-self: flex-start;
|
||||
border-radius: 0.25rem;
|
||||
padding: 0.3125rem 0.75rem;
|
||||
margin: 0.125rem 0;
|
||||
font-family: var(--font-display);
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
transition: background-color 0.15s ease, color 0.15s ease;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,14 +9,16 @@
|
||||
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 is scoped to surfaces that are
|
||||
(or will genuinely be) this app rather than D9's aspirational App/
|
||||
Website/Email tabs: the sidebar mock is what primary_color/secondary_color
|
||||
affect today (base.html's --tenant-* custom properties); the phone mock
|
||||
is the same colours applied ahead of the mobile app being built. 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.
|
||||
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
|
||||
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.
|
||||
{% endcomment %}
|
||||
|
||||
{% block panel_title %}{% trans "Club identity" %}{% endblock panel_title %}
|
||||
@@ -118,13 +120,13 @@
|
||||
<div class="flex items-center justify-between gap-2">
|
||||
<div class="card-title">{% lucide "eye" size=16 %} {% trans "Live preview" %}</div>
|
||||
<div class="flex overflow-hidden rounded-md border border-line">
|
||||
<span class="preview-tab preview-tab-active">{% trans "App" %}</span>
|
||||
<span class="preview-tab">{% trans "Website" %}</span>
|
||||
<span class="preview-tab">{% trans "Email" %}</span>
|
||||
<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 flex flex-wrap items-start gap-4">
|
||||
<div class="mt-3">
|
||||
<div id="preview-panel-app" class="flex flex-wrap items-start gap-4" data-preview-panel="app">
|
||||
<div class="preview-phone-lg">
|
||||
<div id="preview-phone-header" class="preview-phone-header">
|
||||
<div class="preview-phone-header-row">
|
||||
@@ -171,11 +173,26 @@
|
||||
<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 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>
|
||||
@@ -240,7 +257,16 @@
|
||||
document.getElementById("preview-website-join").style.background = secondary;
|
||||
document.getElementById("preview-website-join").style.color = secondaryContent;
|
||||
|
||||
["preview-dot-1", "preview-dot-2", "preview-dot-3"].forEach((id) => {
|
||||
// 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;
|
||||
});
|
||||
|
||||
@@ -267,6 +293,16 @@
|
||||
}
|
||||
|
||||
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) => {
|
||||
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));
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
{% endblock extra_body %}
|
||||
|
||||
@@ -4686,6 +4686,19 @@ class ClubSettingsPreviewTests(ManagementTestBase):
|
||||
response = self.club_get("club_settings")
|
||||
self.assertContains(response, 'name="website"')
|
||||
|
||||
def test_app_and_website_share_one_tab(self):
|
||||
response = self.club_get("club_settings")
|
||||
|
||||
self.assertContains(response, "App & Website")
|
||||
self.assertContains(response, 'data-preview-tab="app"')
|
||||
self.assertNotContains(response, 'data-preview-tab="website"')
|
||||
|
||||
def test_the_email_tab_renders_and_starts_hidden(self):
|
||||
response = self.club_get("club_settings")
|
||||
|
||||
self.assertContains(response, 'data-preview-tab="email"')
|
||||
self.assertContains(response, 'id="preview-panel-email" class="preview-email hidden"')
|
||||
|
||||
def test_saving_still_updates_the_club(self):
|
||||
response = self.club_post(
|
||||
"club_settings",
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user