"""
Account service s-expression page components.
Renders account dashboard, newsletters, fragment pages, login, and device
auth pages. Called from route handlers in place of ``render_template()``.
"""
from __future__ import annotations
from typing import Any
from shared.sexp.jinja_bridge import sexp
from shared.sexp.helpers import (
call_url, root_header_html, search_desktop_html,
search_mobile_html, full_page, oob_page,
)
# ---------------------------------------------------------------------------
# Header helpers
# ---------------------------------------------------------------------------
def _auth_nav_html(ctx: dict) -> str:
"""Auth section desktop nav items."""
html = sexp(
'(~nav-link :href h :label "newsletters" :select-colours sc)',
h=call_url(ctx, "account_url", "/newsletters/"),
sc=ctx.get("select_colours", ""),
)
account_nav_html = ctx.get("account_nav_html", "")
if account_nav_html:
html += account_nav_html
return html
def _auth_header_html(ctx: dict, *, oob: bool = False) -> str:
"""Build the account section header row."""
return sexp(
'(~menu-row :id "auth-row" :level 1 :colour "sky"'
' :link-href lh :link-label "account" :icon "fa-solid fa-user"'
' :nav-html nh :child-id "auth-header-child" :oob oob)',
lh=call_url(ctx, "account_url", "/"),
nh=_auth_nav_html(ctx),
oob=oob,
)
def _auth_nav_mobile_html(ctx: dict) -> str:
"""Mobile nav menu for auth section."""
html = sexp(
'(~nav-link :href h :label "newsletters" :select-colours sc)',
h=call_url(ctx, "account_url", "/newsletters/"),
sc=ctx.get("select_colours", ""),
)
account_nav_html = ctx.get("account_nav_html", "")
if account_nav_html:
html += account_nav_html
return html
# ---------------------------------------------------------------------------
# Account dashboard (GET /)
# ---------------------------------------------------------------------------
def _account_main_panel_html(ctx: dict) -> str:
"""Account info panel with user details and logout."""
from quart import g
from shared.browser.app.csrf import generate_csrf_token
user = getattr(g, "user", None)
error = ctx.get("error", "")
parts = ['
',
'
']
if error:
parts.append(
f'
{error}
'
)
# Account header with logout
parts.append('
')
parts.append('
Account ')
if user:
parts.append(f'
{user.email}
')
if user.name:
parts.append(f'
{user.name}
')
parts.append('
')
parts.append(
f'
'
)
parts.append('
')
# Labels
if user and hasattr(user, "labels") and user.labels:
parts.append('
Labels ')
parts.append('
')
for label in user.labels:
parts.append(
f''
f'{label.name} '
)
parts.append('
')
parts.append('
')
return "".join(parts)
# ---------------------------------------------------------------------------
# Newsletters (GET /newsletters/)
# ---------------------------------------------------------------------------
def _newsletter_toggle_html(un: Any, account_url_fn: Any, csrf_token: str) -> str:
"""Render a single newsletter toggle switch."""
nid = un.newsletter_id
toggle_url = account_url_fn(f"/newsletter/{nid}/toggle/")
if un.subscribed:
bg = "bg-emerald-500"
translate = "translate-x-6"
checked = "true"
else:
bg = "bg-stone-300"
translate = "translate-x-1"
checked = "false"
return (
f''
f''
f' '
f'
'
)
def _newsletters_panel_html(ctx: dict, newsletter_list: list) -> str:
"""Newsletters management panel."""
from shared.browser.app.csrf import generate_csrf_token
account_url_fn = ctx.get("account_url")
csrf = generate_csrf_token()
parts = ['',
'
',
'
Newsletters ']
if newsletter_list:
parts.append('
')
for item in newsletter_list:
nl = item["newsletter"]
un = item.get("un")
parts.append('
')
parts.append(f'
{nl.name}
')
if nl.description:
parts.append(f'
{nl.description}
')
parts.append('
')
if un:
parts.append(_newsletter_toggle_html(un, account_url_fn, csrf))
else:
# No subscription yet — show off toggle
toggle_url = account_url_fn(f"/newsletter/{nl.id}/toggle/")
parts.append(
f'
'
f''
f' '
f'
'
)
parts.append('
')
parts.append('
')
else:
parts.append('
No newsletters available.
')
parts.append('
')
return "".join(parts)
# ---------------------------------------------------------------------------
# Auth pages (login, device, check_email)
# ---------------------------------------------------------------------------
def _login_page_content(ctx: dict) -> str:
"""Login form content."""
from shared.browser.app.csrf import generate_csrf_token
from quart import url_for
error = ctx.get("error", "")
email = ctx.get("email", "")
parts = ['',
'
Sign in ']
if error:
parts.append(
f'
{error}
'
)
action = url_for("auth.start_login")
parts.append(
f'
'
)
parts.append('
')
return "".join(parts)
def _device_page_content(ctx: dict) -> str:
"""Device authorization form content."""
from shared.browser.app.csrf import generate_csrf_token
from quart import url_for
error = ctx.get("error", "")
code = ctx.get("code", "")
parts = ['',
'
Authorize device ',
'
Enter the code shown in your terminal to sign in.
']
if error:
parts.append(
f'
{error}
'
)
action = url_for("auth.device_submit")
parts.append(
f'
'
)
parts.append('
')
return "".join(parts)
def _device_approved_content() -> str:
"""Device approved success content."""
return (
''
'
Device authorized '
'
You can close this window and return to your terminal.
'
'
'
)
# ---------------------------------------------------------------------------
# Public API: Account dashboard
# ---------------------------------------------------------------------------
async def render_account_page(ctx: dict) -> str:
"""Full page: account dashboard."""
main = _account_main_panel_html(ctx)
hdr = root_header_html(ctx)
hdr += sexp(
'(div :id "root-header-child" :class "flex flex-col w-full items-center" (raw! a))',
a=_auth_header_html(ctx),
)
return full_page(ctx, header_rows_html=hdr,
content_html=main,
menu_html=_auth_nav_mobile_html(ctx))
async def render_account_oob(ctx: dict) -> str:
"""OOB response for account dashboard."""
main = _account_main_panel_html(ctx)
oobs = (
_auth_header_html(ctx, oob=True)
+ root_header_html(ctx, oob=True)
)
return oob_page(ctx, oobs_html=oobs,
content_html=main,
menu_html=_auth_nav_mobile_html(ctx))
# ---------------------------------------------------------------------------
# Public API: Newsletters
# ---------------------------------------------------------------------------
async def render_newsletters_page(ctx: dict, newsletter_list: list) -> str:
"""Full page: newsletters."""
main = _newsletters_panel_html(ctx, newsletter_list)
hdr = root_header_html(ctx)
hdr += sexp(
'(div :id "root-header-child" :class "flex flex-col w-full items-center" (raw! a))',
a=_auth_header_html(ctx),
)
return full_page(ctx, header_rows_html=hdr,
content_html=main,
menu_html=_auth_nav_mobile_html(ctx))
async def render_newsletters_oob(ctx: dict, newsletter_list: list) -> str:
"""OOB response for newsletters."""
main = _newsletters_panel_html(ctx, newsletter_list)
oobs = (
_auth_header_html(ctx, oob=True)
+ root_header_html(ctx, oob=True)
)
return oob_page(ctx, oobs_html=oobs,
content_html=main,
menu_html=_auth_nav_mobile_html(ctx))
# ---------------------------------------------------------------------------
# Public API: Fragment pages
# ---------------------------------------------------------------------------
async def render_fragment_page(ctx: dict, page_fragment_html: str) -> str:
"""Full page: fragment-provided content."""
hdr = root_header_html(ctx)
hdr += sexp(
'(div :id "root-header-child" :class "flex flex-col w-full items-center" (raw! a))',
a=_auth_header_html(ctx),
)
return full_page(ctx, header_rows_html=hdr,
content_html=page_fragment_html,
menu_html=_auth_nav_mobile_html(ctx))
async def render_fragment_oob(ctx: dict, page_fragment_html: str) -> str:
"""OOB response for fragment pages."""
oobs = (
_auth_header_html(ctx, oob=True)
+ root_header_html(ctx, oob=True)
)
return oob_page(ctx, oobs_html=oobs,
content_html=page_fragment_html,
menu_html=_auth_nav_mobile_html(ctx))
# ---------------------------------------------------------------------------
# Public API: Auth pages (login, device)
# ---------------------------------------------------------------------------
async def render_login_page(ctx: dict) -> str:
"""Full page: login form."""
hdr = root_header_html(ctx)
return full_page(ctx, header_rows_html=hdr,
content_html=_login_page_content(ctx),
meta_html='Login \u2014 Rose Ash ')
async def render_device_page(ctx: dict) -> str:
"""Full page: device authorization form."""
hdr = root_header_html(ctx)
return full_page(ctx, header_rows_html=hdr,
content_html=_device_page_content(ctx),
meta_html='Authorize Device \u2014 Rose Ash ')
async def render_device_approved_page(ctx: dict) -> str:
"""Full page: device approved."""
hdr = root_header_html(ctx)
return full_page(ctx, header_rows_html=hdr,
content_html=_device_approved_content(),
meta_html='Device Authorized \u2014 Rose Ash ')