Fix email template rendering for Outlook-safe branded content
This commit is contained in:
@@ -1 +1 @@
|
|||||||
0303261702
|
0303261719
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -139,17 +139,17 @@ TEMPLATE_PRESENTATION: Dict[str, Dict[str, str]] = {
|
|||||||
|
|
||||||
def _build_email_stat_card(label: str, value: str, detail: str = "") -> str:
|
def _build_email_stat_card(label: str, value: str, detail: str = "") -> str:
|
||||||
detail_html = (
|
detail_html = (
|
||||||
f"<div style=\"margin-top:8px; font-size:13px; line-height:1.6; color:#9aa3b8; word-break:break-word;\">"
|
f"<div style=\"margin-top:8px; font-size:13px; line-height:1.6; color:#5c687d; word-break:break-word;\">"
|
||||||
f"{html.escape(detail)}</div>"
|
f"{html.escape(detail)}</div>"
|
||||||
if detail
|
if detail
|
||||||
else ""
|
else ""
|
||||||
)
|
)
|
||||||
return (
|
return (
|
||||||
"<div style=\"padding:16px; background:#151c2d; border:1px solid rgba(255,255,255,0.08); "
|
"<div style=\"padding:16px; background:#f8fafc; border:1px solid #d9e2ef; "
|
||||||
"border-radius:16px; color:#e9ecf5;\">"
|
"border-radius:16px; color:#132033;\">"
|
||||||
f"<div style=\"font-size:11px; letter-spacing:0.12em; text-transform:uppercase; color:#9aa3b8; "
|
f"<div style=\"font-size:11px; letter-spacing:0.12em; text-transform:uppercase; color:#6b778c; "
|
||||||
f"margin-bottom:8px;\">{html.escape(label)}</div>"
|
f"margin-bottom:8px;\">{html.escape(label)}</div>"
|
||||||
f"<div style=\"font-size:20px; font-weight:800; line-height:1.45; word-break:break-word;\">"
|
f"<div style=\"font-size:20px; font-weight:800; line-height:1.45; word-break:break-word; color:#132033;\">"
|
||||||
f"{html.escape(value)}</div>"
|
f"{html.escape(value)}</div>"
|
||||||
f"{detail_html}"
|
f"{detail_html}"
|
||||||
"</div>"
|
"</div>"
|
||||||
@@ -184,7 +184,7 @@ def _build_email_list(items: list[str], *, ordered: bool = False) -> str:
|
|||||||
f"<li style=\"margin:0 0 8px;\">{html.escape(item)}</li>" for item in items if item
|
f"<li style=\"margin:0 0 8px;\">{html.escape(item)}</li>" for item in items if item
|
||||||
)
|
)
|
||||||
return (
|
return (
|
||||||
f"<{tag} style=\"margin:0; {marker} color:#dbe5ff; line-height:1.8; font-size:14px;\">"
|
f"<{tag} style=\"margin:0; {marker} color:#132033; line-height:1.8; font-size:14px;\">"
|
||||||
f"{rendered_items}"
|
f"{rendered_items}"
|
||||||
f"</{tag}>"
|
f"</{tag}>"
|
||||||
)
|
)
|
||||||
@@ -193,40 +193,40 @@ def _build_email_list(items: list[str], *, ordered: bool = False) -> str:
|
|||||||
def _build_email_panel(title: str, body_html: str, *, variant: str = "neutral") -> str:
|
def _build_email_panel(title: str, body_html: str, *, variant: str = "neutral") -> str:
|
||||||
styles = {
|
styles = {
|
||||||
"neutral": {
|
"neutral": {
|
||||||
"background": "#101726",
|
"background": "#f8fafc",
|
||||||
"border": "rgba(255,255,255,0.08)",
|
"border": "#d9e2ef",
|
||||||
"eyebrow": "#9aa3b8",
|
"eyebrow": "#6b778c",
|
||||||
"text": "#dbe5ff",
|
"text": "#132033",
|
||||||
},
|
},
|
||||||
"brand": {
|
"brand": {
|
||||||
"background": "#101726",
|
"background": "#eef4ff",
|
||||||
"border": "rgba(59,130,246,0.22)",
|
"border": "#bfd2ff",
|
||||||
"eyebrow": "#9dbfff",
|
"eyebrow": "#2754b6",
|
||||||
"text": "#dbe5ff",
|
"text": "#132033",
|
||||||
},
|
},
|
||||||
"success": {
|
"success": {
|
||||||
"background": "#122016",
|
"background": "#edf9f0",
|
||||||
"border": "rgba(34,197,94,0.24)",
|
"border": "#bfe4c6",
|
||||||
"eyebrow": "#9de7b5",
|
"eyebrow": "#1f7a3f",
|
||||||
"text": "#d9f9e4",
|
"text": "#132033",
|
||||||
},
|
},
|
||||||
"warning": {
|
"warning": {
|
||||||
"background": "#241814",
|
"background": "#fff5ea",
|
||||||
"border": "rgba(251,146,60,0.34)",
|
"border": "#ffd5a8",
|
||||||
"eyebrow": "#fbbd7b",
|
"eyebrow": "#c46a10",
|
||||||
"text": "#ffe0ba",
|
"text": "#132033",
|
||||||
},
|
},
|
||||||
"danger": {
|
"danger": {
|
||||||
"background": "#251418",
|
"background": "#fff0f0",
|
||||||
"border": "rgba(239,68,68,0.32)",
|
"border": "#f3c1c1",
|
||||||
"eyebrow": "#ff9b9b",
|
"eyebrow": "#bb2d2d",
|
||||||
"text": "#ffd0d0",
|
"text": "#132033",
|
||||||
},
|
},
|
||||||
}.get(variant, {
|
}.get(variant, {
|
||||||
"background": "#101726",
|
"background": "#f8fafc",
|
||||||
"border": "rgba(255,255,255,0.08)",
|
"border": "#d9e2ef",
|
||||||
"eyebrow": "#9aa3b8",
|
"eyebrow": "#6b778c",
|
||||||
"text": "#dbe5ff",
|
"text": "#132033",
|
||||||
})
|
})
|
||||||
return (
|
return (
|
||||||
f"<div style=\"margin:0 0 18px; padding:18px; background:{styles['background']}; "
|
f"<div style=\"margin:0 0 18px; padding:18px; background:{styles['background']}; "
|
||||||
@@ -255,7 +255,7 @@ DEFAULT_TEMPLATES: Dict[str, Dict[str, str]] = {
|
|||||||
"Build: {{build_number}}\n"
|
"Build: {{build_number}}\n"
|
||||||
),
|
),
|
||||||
"body_html": (
|
"body_html": (
|
||||||
"<div style=\"margin:0 0 20px; color:#e9ecf5; font-size:15px; line-height:1.7;\">"
|
"<div style=\"margin:0 0 20px; color:#132033; font-size:15px; line-height:1.7;\">"
|
||||||
"A new invitation has been prepared for <strong>{{recipient_email}}</strong>. Use the details below to sign up."
|
"A new invitation has been prepared for <strong>{{recipient_email}}</strong>. Use the details below to sign up."
|
||||||
"</div>"
|
"</div>"
|
||||||
+ _build_email_stat_grid(
|
+ _build_email_stat_grid(
|
||||||
@@ -305,7 +305,7 @@ DEFAULT_TEMPLATES: Dict[str, Dict[str, str]] = {
|
|||||||
"{{message}}\n"
|
"{{message}}\n"
|
||||||
),
|
),
|
||||||
"body_html": (
|
"body_html": (
|
||||||
"<div style=\"margin:0 0 18px; color:#e9ecf5; font-size:15px; line-height:1.7;\">"
|
"<div style=\"margin:0 0 18px; color:#132033; font-size:15px; line-height:1.7;\">"
|
||||||
"Your account is live and ready to use. Everything below mirrors the current site behavior."
|
"Your account is live and ready to use. Everything below mirrors the current site behavior."
|
||||||
"</div>"
|
"</div>"
|
||||||
+ _build_email_stat_grid(
|
+ _build_email_stat_grid(
|
||||||
@@ -345,7 +345,7 @@ DEFAULT_TEMPLATES: Dict[str, Dict[str, str]] = {
|
|||||||
"If you need help, contact the admin.\n"
|
"If you need help, contact the admin.\n"
|
||||||
),
|
),
|
||||||
"body_html": (
|
"body_html": (
|
||||||
"<div style=\"margin:0 0 18px; color:#e9ecf5; font-size:15px; line-height:1.7;\">"
|
"<div style=\"margin:0 0 18px; color:#132033; font-size:15px; line-height:1.7;\">"
|
||||||
"Please review this account notice carefully. This message was sent by an administrator."
|
"Please review this account notice carefully. This message was sent by an administrator."
|
||||||
"</div>"
|
"</div>"
|
||||||
+ _build_email_stat_grid(
|
+ _build_email_stat_grid(
|
||||||
@@ -388,7 +388,7 @@ DEFAULT_TEMPLATES: Dict[str, Dict[str, str]] = {
|
|||||||
"{{message}}\n"
|
"{{message}}\n"
|
||||||
),
|
),
|
||||||
"body_html": (
|
"body_html": (
|
||||||
"<div style=\"margin:0 0 18px; color:#e9ecf5; font-size:15px; line-height:1.7;\">"
|
"<div style=\"margin:0 0 18px; color:#132033; font-size:15px; line-height:1.7;\">"
|
||||||
"Your account access has changed. Review the details below."
|
"Your account access has changed. Review the details below."
|
||||||
"</div>"
|
"</div>"
|
||||||
+ _build_email_stat_grid(
|
+ _build_email_stat_grid(
|
||||||
@@ -507,13 +507,14 @@ def _looks_like_full_html_document(value: str) -> bool:
|
|||||||
|
|
||||||
|
|
||||||
def _build_email_action_button(label: str, url: str, *, primary: bool) -> str:
|
def _build_email_action_button(label: str, url: str, *, primary: bool) -> str:
|
||||||
background = "linear-gradient(135deg, #ff6b2b 0%, #1c6bff 100%)" if primary else "#151c2d"
|
background = "linear-gradient(135deg, #ff6b2b 0%, #1c6bff 100%)" if primary else "#ffffff"
|
||||||
border = "1px solid rgba(59, 130, 246, 0.32)" if primary else "1px solid rgba(255, 255, 255, 0.12)"
|
fallback = "#1c6bff" if primary else "#ffffff"
|
||||||
color = "#ffffff"
|
border = "1px solid rgba(28, 107, 255, 0.28)" if primary else "1px solid #d5deed"
|
||||||
|
color = "#ffffff" if primary else "#132033"
|
||||||
return (
|
return (
|
||||||
f"<a href=\"{html.escape(url)}\" "
|
f"<a href=\"{html.escape(url)}\" "
|
||||||
f"style=\"display:inline-block; padding:12px 20px; margin:0 12px 12px 0; border-radius:999px; "
|
f"style=\"display:inline-block; padding:12px 20px; margin:0 12px 12px 0; border-radius:999px; "
|
||||||
f"background:{background}; border:{border}; color:{color}; text-decoration:none; font-size:14px; "
|
f"background-color:{fallback}; background:{background}; border:{border}; color:{color}; text-decoration:none; font-size:14px; "
|
||||||
f"font-weight:800; letter-spacing:0.01em;\">{html.escape(label)}</a>"
|
f"font-weight:800; letter-spacing:0.01em;\">{html.escape(label)}</a>"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -560,39 +561,39 @@ def _wrap_email_html(
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
"<!doctype html>"
|
"<!doctype html>"
|
||||||
"<html><body style=\"margin:0; padding:0; background:#05070d;\">"
|
"<html><body style=\"margin:0; padding:0; background:#eef2f7;\" bgcolor=\"#eef2f7\">"
|
||||||
"<div style=\"display:none; max-height:0; overflow:hidden; opacity:0;\">"
|
"<div style=\"display:none; max-height:0; overflow:hidden; opacity:0;\">"
|
||||||
f"{html.escape(title)} - {html.escape(subtitle)}"
|
f"{html.escape(title)} - {html.escape(subtitle)}"
|
||||||
"</div>"
|
"</div>"
|
||||||
"<table role=\"presentation\" width=\"100%\" cellspacing=\"0\" cellpadding=\"0\" "
|
"<table role=\"presentation\" width=\"100%\" cellspacing=\"0\" cellpadding=\"0\" "
|
||||||
"style=\"width:100%; border-collapse:collapse; background:radial-gradient(circle at top, rgba(17,33,74,0.9) 0%, rgba(8,12,22,1) 55%, #05070d 100%);\">"
|
"style=\"width:100%; border-collapse:collapse; background:#eef2f7;\" bgcolor=\"#eef2f7\">"
|
||||||
"<tr><td style=\"padding:32px 18px;\">"
|
"<tr><td style=\"padding:32px 18px;\" bgcolor=\"#eef2f7\">"
|
||||||
"<table role=\"presentation\" width=\"100%\" cellspacing=\"0\" cellpadding=\"0\" "
|
"<table role=\"presentation\" width=\"100%\" cellspacing=\"0\" cellpadding=\"0\" "
|
||||||
"style=\"max-width:680px; margin:0 auto; border-collapse:collapse;\">"
|
"style=\"max-width:680px; margin:0 auto; border-collapse:collapse;\">"
|
||||||
"<tr><td style=\"padding:0 0 18px;\">"
|
"<tr><td style=\"padding:0 0 18px;\">"
|
||||||
f"<div style=\"padding:24px 28px; background:#0b0f18; border:1px solid rgba(255,255,255,0.08); border-radius:28px; box-shadow:0 24px 60px rgba(0,0,0,0.42);\">"
|
f"<div style=\"padding:24px 28px; background:#ffffff; border:1px solid #d5deed; border-radius:28px; box-shadow:0 18px 48px rgba(15,23,42,0.08);\">"
|
||||||
"<table role=\"presentation\" width=\"100%\" cellspacing=\"0\" cellpadding=\"0\" style=\"border-collapse:collapse;\">"
|
"<table role=\"presentation\" width=\"100%\" cellspacing=\"0\" cellpadding=\"0\" style=\"border-collapse:collapse;\">"
|
||||||
"<tr>"
|
"<tr>"
|
||||||
f"<td style=\"vertical-align:middle; width:64px; padding:0 18px 0 0;\">{logo_block}</td>"
|
f"<td style=\"vertical-align:middle; width:64px; padding:0 18px 0 0;\">{logo_block}</td>"
|
||||||
"<td style=\"vertical-align:middle;\">"
|
"<td style=\"vertical-align:middle;\">"
|
||||||
f"<div style=\"font-size:11px; letter-spacing:0.18em; text-transform:uppercase; color:#9aa3b8; margin-bottom:6px;\">{html.escape(app_name)}</div>"
|
f"<div style=\"font-size:11px; letter-spacing:0.18em; text-transform:uppercase; color:#6b778c; margin-bottom:6px;\">{html.escape(app_name)}</div>"
|
||||||
f"<div style=\"font-size:30px; line-height:1.1; font-weight:900; color:#e9ecf5; margin:0 0 6px;\">{html.escape(title)}</div>"
|
f"<div style=\"font-size:30px; line-height:1.1; font-weight:900; color:#132033; margin:0 0 6px;\">{html.escape(title)}</div>"
|
||||||
f"<div style=\"font-size:15px; line-height:1.6; color:#9aa3b8;\">{html.escape(subtitle or EMAIL_TAGLINE)}</div>"
|
f"<div style=\"font-size:15px; line-height:1.6; color:#5c687d;\">{html.escape(subtitle or EMAIL_TAGLINE)}</div>"
|
||||||
"</td>"
|
"</td>"
|
||||||
"</tr>"
|
"</tr>"
|
||||||
"</table>"
|
"</table>"
|
||||||
f"<div style=\"height:6px; margin:22px 0 22px; border-radius:999px; background:linear-gradient(90deg, {styles['accent_a']} 0%, {styles['accent_b']} 100%);\"></div>"
|
f"<div style=\"height:6px; margin:22px 0 22px; border-radius:999px; background-color:{styles['accent_b']}; background:linear-gradient(90deg, {styles['accent_a']} 0%, {styles['accent_b']} 100%);\"></div>"
|
||||||
f"<div style=\"display:inline-block; padding:7px 12px; margin:0 0 16px; background:{styles['chip_bg']}; "
|
f"<div style=\"display:inline-block; padding:7px 12px; margin:0 0 16px; background:{styles['chip_bg']}; "
|
||||||
f"border:1px solid {styles['chip_border']}; border-radius:999px; color:{styles['chip_text']}; "
|
f"border:1px solid {styles['chip_border']}; border-radius:999px; color:{styles['chip_text']}; "
|
||||||
"font-size:11px; font-weight:800; letter-spacing:0.14em; text-transform:uppercase;\">"
|
"font-size:11px; font-weight:800; letter-spacing:0.14em; text-transform:uppercase;\">"
|
||||||
f"{html.escape(EMAIL_TAGLINE)}</div>"
|
f"{html.escape(EMAIL_TAGLINE)}</div>"
|
||||||
f"<div style=\"color:#e9ecf5;\">{body_html}</div>"
|
f"<div style=\"color:#132033;\">{body_html}</div>"
|
||||||
f"<div style=\"margin:24px 0 0;\">{actions_html}</div>"
|
f"<div style=\"margin:24px 0 0;\">{actions_html}</div>"
|
||||||
"<div style=\"margin:28px 0 0; padding:18px 0 0; border-top:1px solid rgba(255,255,255,0.08);\">"
|
"<div style=\"margin:28px 0 0; padding:18px 0 0; border-top:1px solid #e2e8f0;\">"
|
||||||
"<table role=\"presentation\" width=\"100%\" cellspacing=\"0\" cellpadding=\"0\" style=\"border-collapse:collapse;\">"
|
"<table role=\"presentation\" width=\"100%\" cellspacing=\"0\" cellpadding=\"0\" style=\"border-collapse:collapse;\">"
|
||||||
"<tr>"
|
"<tr>"
|
||||||
f"<td style=\"font-size:12px; line-height:1.7; color:#9aa3b8;\">{html.escape(footer)}</td>"
|
f"<td style=\"font-size:12px; line-height:1.7; color:#6b778c;\">{html.escape(footer)}</td>"
|
||||||
f"<td style=\"font-size:12px; line-height:1.7; color:#9aa3b8; text-align:right;\">Build {html.escape(build_number)}</td>"
|
f"<td style=\"font-size:12px; line-height:1.7; color:#6b778c; text-align:right;\">Build {html.escape(build_number)}</td>"
|
||||||
"</tr>"
|
"</tr>"
|
||||||
"</table>"
|
"</table>"
|
||||||
"</div>"
|
"</div>"
|
||||||
@@ -1014,7 +1015,7 @@ async def send_test_email(recipient_email: Optional[str] = None) -> Dict[str, st
|
|||||||
subtitle="This confirms Magent can generate and hand off branded mail.",
|
subtitle="This confirms Magent can generate and hand off branded mail.",
|
||||||
tone="brand",
|
tone="brand",
|
||||||
body_html=(
|
body_html=(
|
||||||
"<div style=\"margin:0 0 18px; color:#e9ecf5; font-size:15px; line-height:1.7;\">"
|
"<div style=\"margin:0 0 18px; color:#132033; font-size:15px; line-height:1.7;\">"
|
||||||
"This is a live test email from Magent. If this renders correctly, the HTML template shell and SMTP handoff are both working."
|
"This is a live test email from Magent. If this renders correctly, the HTML template shell and SMTP handoff are both working."
|
||||||
"</div>"
|
"</div>"
|
||||||
+ _build_email_stat_grid(
|
+ _build_email_stat_grid(
|
||||||
@@ -1110,7 +1111,7 @@ async def send_password_reset_email(
|
|||||||
subtitle=f"This will update the credentials used for {provider_label}.",
|
subtitle=f"This will update the credentials used for {provider_label}.",
|
||||||
tone="brand",
|
tone="brand",
|
||||||
body_html=(
|
body_html=(
|
||||||
f"<div style=\"margin:0 0 18px; color:#e9ecf5; font-size:15px; line-height:1.7;\">"
|
f"<div style=\"margin:0 0 18px; color:#132033; font-size:15px; line-height:1.7;\">"
|
||||||
f"A password reset was requested for <strong>{html.escape(username)}</strong>."
|
f"A password reset was requested for <strong>{html.escape(username)}</strong>."
|
||||||
"</div>"
|
"</div>"
|
||||||
+ _build_email_stat_grid(
|
+ _build_email_stat_grid(
|
||||||
|
|||||||
@@ -2296,14 +2296,6 @@ export default function SettingsPage({ section }: SettingsPageProps) {
|
|||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
) : null}
|
) : null}
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="settings-action-button"
|
|
||||||
onClick={() => void saveSettingGroup(sectionGroup)}
|
|
||||||
disabled={sectionSaving[sectionGroup.key] || sectionTesting[sectionGroup.key]}
|
|
||||||
>
|
|
||||||
{sectionSaving[sectionGroup.key] ? 'Saving...' : 'Save section'}
|
|
||||||
</button>
|
|
||||||
{getSectionTestLabel(sectionGroup.key) ? (
|
{getSectionTestLabel(sectionGroup.key) ? (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -2316,6 +2308,14 @@ export default function SettingsPage({ section }: SettingsPageProps) {
|
|||||||
: getSectionTestLabel(sectionGroup.key)}
|
: getSectionTestLabel(sectionGroup.key)}
|
||||||
</button>
|
</button>
|
||||||
) : null}
|
) : null}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="settings-action-button"
|
||||||
|
onClick={() => void saveSettingGroup(sectionGroup)}
|
||||||
|
disabled={sectionSaving[sectionGroup.key] || sectionTesting[sectionGroup.key]}
|
||||||
|
>
|
||||||
|
{sectionSaving[sectionGroup.key] ? 'Saving...' : 'Save section'}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
))}
|
))}
|
||||||
|
|||||||
4
frontend/package-lock.json
generated
4
frontend/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "magent-frontend",
|
"name": "magent-frontend",
|
||||||
"version": "0303261702",
|
"version": "0303261719",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "magent-frontend",
|
"name": "magent-frontend",
|
||||||
"version": "0303261702",
|
"version": "0303261719",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"next": "16.1.6",
|
"next": "16.1.6",
|
||||||
"react": "19.2.4",
|
"react": "19.2.4",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "magent-frontend",
|
"name": "magent-frontend",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0303261702",
|
"version": "0303261719",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
|
|||||||
Reference in New Issue
Block a user