update email styles

This commit is contained in:
SowinskiBraeden committed 2026-06-27 12:47:49 -07:00
1 parent ffc33c967a
commit 2e1cc10dc7
3 files changed
+118 -13

No files matched your search

+1 -1
View File
@@ -9,7 +9,7 @@ DATA_PATH = BASE_DIR / "data" / "entries.csv"
DEFAULT_DATABASE_URL = f"sqlite:///{BASE_DIR / 'data' / 'boker-dev.sqlite3'}" DEFAULT_DATABASE_URL = f"sqlite:///{BASE_DIR / 'data' / 'boker-dev.sqlite3'}"
ELIGIBLE_MIN_SESSIONS = 3 ELIGIBLE_MIN_SESSIONS = 3
APP_VERSION = "2.5.14" APP_VERSION = "2.5.16"
def load_local_env(env_path: Path) -> None: def load_local_env(env_path: Path) -> None:
+107 -11
View File
@@ -1,6 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from __future__ import annotations from __future__ import annotations
from html import escape
import socket import socket
from flask import current_app from flask import current_app
@@ -26,15 +27,95 @@ def _send_message(msg: Message) -> None:
socket.setdefaulttimeout(previous_timeout) socket.setdefaulttimeout(previous_timeout)
def _html_email(title: str, intro: str, cta_label: str | None = None, cta_url: str | None = None, code: str | None = None, note: str | None = None) -> str:
app_url = current_app.config.get("APP_BASE_URL", "https://myboker.org").rstrip("/")
cta_html = ""
if cta_label and cta_url:
cta_html = f"""
<tr>
<td style="padding:8px 32px 24px;">
<a href="{escape(cta_url)}" style="display:inline-block;background:#9b8cf0;color:#12101e;text-decoration:none;font:700 14px Arial,sans-serif;padding:12px 18px;border-radius:6px;">{escape(cta_label)}</a>
</td>
</tr>
<tr>
<td style="padding:0 32px 24px;color:#7c8294;font:400 12px/1.6 Arial,sans-serif;">
If the button does not work, copy and paste this link:<br>
<a href="{escape(cta_url)}" style="color:#b8aff8;word-break:break-all;">{escape(cta_url)}</a>
</td>
</tr>
"""
code_html = ""
if code:
code_html = f"""
<tr>
<td style="padding:4px 32px 24px;">
<div style="display:inline-block;letter-spacing:6px;font:800 34px/1.1 Arial,sans-serif;color:#f5f7fb;background:#151925;border:1px solid #32394a;border-radius:8px;padding:14px 18px;">{escape(code)}</div>
</td>
</tr>
"""
note_html = ""
if note:
note_html = f"""
<tr>
<td style="padding:0 32px 28px;color:#a3a8b8;font:400 14px/1.6 Arial,sans-serif;">{escape(note)}</td>
</tr>
"""
return f"""<!doctype html>
<html>
<body style="margin:0;padding:0;background:#0f172a;">
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="background:#0f172a;margin:0;padding:32px 12px;">
<tr>
<td align="center">
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="max-width:560px;background:#202129;border:1px solid #313442;border-radius:10px;overflow:hidden;">
<tr>
<td style="padding:28px 32px 10px;">
<div style="font:800 18px Arial,sans-serif;color:#f5f7fb;">myboker<span style="color:#9b8cf0;">.org</span></div>
</td>
</tr>
<tr>
<td style="padding:18px 32px 8px;">
<h1 style="margin:0;color:#f5f7fb;font:800 24px/1.25 Arial,sans-serif;">{escape(title)}</h1>
</td>
</tr>
<tr>
<td style="padding:0 32px 22px;color:#c6cad6;font:400 15px/1.6 Arial,sans-serif;">{escape(intro)}</td>
</tr>
{code_html}
{cta_html}
{note_html}
<tr>
<td style="padding:18px 32px 26px;border-top:1px solid #32394a;color:#7c8294;font:400 12px/1.6 Arial,sans-serif;">
This email was sent by <a href="{escape(app_url)}" style="color:#b8aff8;text-decoration:none;">myboker.org</a>. If you did not request this, you can safely ignore it.
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>"""
def send_password_reset(to_email: str, reset_url: str) -> None: def send_password_reset(to_email: str, reset_url: str) -> None:
body = (
f"You requested a password reset for your myboker.org account.\n\n"
f"Reset your password:\n\n"
f"{reset_url}\n\n"
f"This link expires in 1 hour. If you did not request this, you can ignore this email."
)
msg = Message( msg = Message(
subject="Reset your myboker.org password", subject="Reset your myboker.org password",
recipients=[to_email], recipients=[to_email],
body=( body=body,
f"You requested a password reset for your myboker.org account.\n\n" html=_html_email(
f"Click the link below to set a new password:\n\n" "Reset your password",
f"{reset_url}\n\n" "We received a request to reset the password for your myboker.org account.",
f"This link expires in 1 hour. If you did not request this, you can ignore this email." cta_label="Reset password",
cta_url=reset_url,
note="This link expires in 1 hour.",
), ),
sender=current_app.config.get("MAIL_DEFAULT_SENDER"), sender=current_app.config.get("MAIL_DEFAULT_SENDER"),
) )
@@ -42,13 +123,20 @@ def send_password_reset(to_email: str, reset_url: str) -> None:
def send_email_verification_code(to_email: str, code: str) -> None: def send_email_verification_code(to_email: str, code: str) -> None:
msg = Message(
subject="Your myboker.org verification code",
recipients=[to_email],
body = ( body = (
"Verify your myboker.org account with this code:\n\n" "Verify your myboker.org account with this code:\n\n"
f"{code}\n\n" f"{code}\n\n"
"This code expires in 15 minutes. If you did not create an account, you can ignore this email." "This code expires in 15 minutes. If you did not create an account, you can ignore this email."
)
msg = Message(
subject="Your myboker.org verification code",
recipients=[to_email],
body=body,
html=_html_email(
"Verify your email",
"Enter this verification code to finish creating your myboker.org account.",
code=code,
note="This code expires in 15 minutes. Do not share it with anyone.",
), ),
sender=current_app.config.get("MAIL_DEFAULT_SENDER"), sender=current_app.config.get("MAIL_DEFAULT_SENDER"),
) )
@@ -57,14 +145,22 @@ def send_email_verification_code(to_email: str, code: str) -> None:
def send_league_invite(to_email: str, league_name: str, invite_url: str, invited_by_email: str, role: str) -> None: def send_league_invite(to_email: str, league_name: str, invite_url: str, invited_by_email: str, role: str) -> None:
role_label = role.strip().lower() if role else "member" role_label = role.strip().lower() if role else "member"
msg = Message(
subject=f"You've been invited to join {league_name} on myboker.org",
recipients=[to_email],
body = ( body = (
f"{invited_by_email} has invited you to join {league_name} as a {role_label} on myboker.org.\n\n" f"{invited_by_email} has invited you to join {league_name} as a {role_label} on myboker.org.\n\n"
f"Accept your invitation:\n\n" f"Accept your invitation:\n\n"
f"{invite_url}\n\n" f"{invite_url}\n\n"
f"This link expires in 7 days. You'll need a myboker.org account with this email address to accept." f"This link expires in 7 days. You'll need a myboker.org account with this email address to accept."
)
msg = Message(
subject=f"You've been invited to join {league_name} on myboker.org",
recipients=[to_email],
body=body,
html=_html_email(
"You have a league invite",
f"{invited_by_email} invited you to join {league_name} as a {role_label}.",
cta_label="Accept invite",
cta_url=invite_url,
note="This invitation link expires in 7 days.",
), ),
sender=current_app.config.get("MAIL_DEFAULT_SENDER"), sender=current_app.config.get("MAIL_DEFAULT_SENDER"),
) )
+10 -1
View File
@@ -117,6 +117,7 @@ def register():
email = normalize_email(form["email"]) email = normalize_email(form["email"])
password = request.form.get("password", "") password = request.form.get("password", "")
confirm_password = request.form.get("confirm_password", "") confirm_password = request.form.get("confirm_password", "")
existing_user = find_user_by_email(email)
if not email or "@" not in email: if not email or "@" not in email:
flash("Enter a valid email address.", "error") flash("Enter a valid email address.", "error")
@@ -124,8 +125,16 @@ def register():
flash("Password must be at least 8 characters.", "error") flash("Password must be at least 8 characters.", "error")
elif password != confirm_password: elif password != confirm_password:
flash("Passwords do not match.", "error") flash("Passwords do not match.", "error")
elif find_user_by_email(email): elif existing_user and existing_user.disabled_at is None:
flash("An account already exists for that email.", "error") flash("An account already exists for that email.", "error")
else:
if existing_user:
user = existing_user
user.password_hash = hash_password(password)
user.disabled_at = None
user.email_verified_at = None
user.email_verification_code_hash = None
user.email_verification_sent_at = None
else: else:
user = create_user(email, password) user = create_user(email, password)
db.session.flush() db.session.flush()