""" 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'
' f'' 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'
' ) 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'
' ) 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'
' f'' f'
' f'
' 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'
' f'' f'
' f'
' 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')