From 536d5c8f402a45f396df6f522d9b97e16ae830b2 Mon Sep 17 00:00:00 2001 From: Deeman Date: Wed, 25 Feb 2026 12:12:09 +0100 Subject: [PATCH] =?UTF-8?q?feat(emails):=20subtask=205=20=E2=80=94=20compo?= =?UTF-8?q?se=20preview=20(admin=5Fcompose=20template=20+=20HTMX=20endpoin?= =?UTF-8?q?t)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add emails/admin_compose.html: branded wrapper for ad-hoc compose body - Update email_compose.html: two-column layout with HTMX live preview pane (hx-post, hx-trigger=input delay:500ms, hx-target=#preview-pane) - Add partials/email_preview_frame.html: sandboxed iframe partial - Add POST /admin/emails/compose/preview route (no CSRF — read-only render) - Update email_compose POST handler to use render_email_template() instead of importing _email_wrap from worker Co-Authored-By: Claude Sonnet 4.6 --- web/src/padelnomics/admin/routes.py | 43 ++++++- .../admin/templates/admin/email_compose.html | 106 ++++++++++++------ .../admin/partials/email_preview_frame.html | 8 ++ .../templates/emails/admin_compose.html | 5 + 4 files changed, 126 insertions(+), 36 deletions(-) create mode 100644 web/src/padelnomics/admin/templates/admin/partials/email_preview_frame.html create mode 100644 web/src/padelnomics/templates/emails/admin_compose.html diff --git a/web/src/padelnomics/admin/routes.py b/web/src/padelnomics/admin/routes.py index 32bf36d..b60d27f 100644 --- a/web/src/padelnomics/admin/routes.py +++ b/web/src/padelnomics/admin/routes.py @@ -33,6 +33,7 @@ from ..core import ( utcnow, utcnow_iso, ) +from ..email_templates import EMAIL_TEMPLATE_REGISTRY, render_email_template logger = logging.getLogger(__name__) @@ -1398,10 +1399,16 @@ async def email_compose(): email_addresses=EMAIL_ADDRESSES, ) - html = f"

{body.replace(chr(10), '
')}

" + body_html = f"

{body.replace(chr(10), '
')}

" if wrap: - from ..worker import _email_wrap - html = _email_wrap(html) + html = render_email_template( + "emails/admin_compose.html", + lang="en", + body_html=body_html, + preheader="", + ) + else: + html = body_html result = await send_email( to=to, subject=subject, html=html, @@ -1424,6 +1431,36 @@ async def email_compose(): ) +@bp.route("/emails/compose/preview", methods=["POST"]) +@role_required("admin") +async def compose_preview(): + """HTMX endpoint: render live preview for compose textarea (no CSRF — read-only).""" + form = await request.form + body = form.get("body", "").strip() + wrap = form.get("wrap", "") == "1" + + body_html = f"

{body.replace(chr(10), '
')}

" if body else "" + + if wrap and body_html: + try: + rendered_html = render_email_template( + "emails/admin_compose.html", + lang="en", + body_html=body_html, + preheader="", + ) + except Exception: + logger.exception("compose_preview: template render failed") + rendered_html = body_html + else: + rendered_html = body_html + + return await render_template( + "admin/partials/email_preview_frame.html", + rendered_html=rendered_html, + ) + + # --- Audiences --- @bp.route("/emails/audiences") diff --git a/web/src/padelnomics/admin/templates/admin/email_compose.html b/web/src/padelnomics/admin/templates/admin/email_compose.html index dfd5a7a..1ab8846 100644 --- a/web/src/padelnomics/admin/templates/admin/email_compose.html +++ b/web/src/padelnomics/admin/templates/admin/email_compose.html @@ -2,51 +2,91 @@ {% set admin_page = "compose" %} {% block title %}Compose Email - Admin - {{ config.APP_NAME }}{% endblock %} +{% block admin_head %} + +{% endblock %} + {% block admin_content %}
← Sent Log

Compose Email

-
-
- +
+ {# ── Left: form ────────────────────────────────────── #} +
+
+ + -
- - -
+
+ + +
-
- - -
+
+ + +
-
- - -
+
+ + +
-
- - -
+
+ + +
-
- -
+
+ +
-
- - Cancel +
+ + Cancel +
+
- +
+ + {# ── Right: live preview panel ─────────────────────── #} +
+
Live preview
+
+

Start typing to see a preview…

+
+
{% endblock %} diff --git a/web/src/padelnomics/admin/templates/admin/partials/email_preview_frame.html b/web/src/padelnomics/admin/templates/admin/partials/email_preview_frame.html new file mode 100644 index 0000000..5943fe3 --- /dev/null +++ b/web/src/padelnomics/admin/templates/admin/partials/email_preview_frame.html @@ -0,0 +1,8 @@ +{# HTMX partial: sandboxed iframe showing a rendered email preview. + Rendered by POST /admin/emails/compose/preview. #} + diff --git a/web/src/padelnomics/templates/emails/admin_compose.html b/web/src/padelnomics/templates/emails/admin_compose.html new file mode 100644 index 0000000..657ad07 --- /dev/null +++ b/web/src/padelnomics/templates/emails/admin_compose.html @@ -0,0 +1,5 @@ +{% extends "emails/_base.html" %} + +{% block body %} +{{ body_html | safe }} +{% endblock %}