STATE
‹ Back to prototype

Account portal — DES-54 handoff notes

Desktop prototype. Pairs with the mobile prototype tracked separately.

Vocabulary alignment

The portal renders the strings below directly from orderCycle, orderPrice, and nextCharge. PDP and checkout should use the same vocabulary so the patient sees one consistent way of describing cadence and renewal across the journey.

ConceptPortal stringSurface
Cadence summaryevery {count} weeksList card meta, detail table
Cycle price$478.00 per cycle · every 8 weeksList card
Cycle price (detail)Cycle Price: $478.00Subscription Details table
Imminent renewal (date hero)MAY 9 / in 3 daysList card right column
Distant renewalin 23 days (muted)Date hero countdown
Reassessment dueReassessment / MAY 12 / in 6 days (coral)List card right column
Coming Up heroRenews May 9 · in 3 days (Lora)Detail hero band
Cycle Price hero$478.00 / CYCLE PRICEDetail hero right column
Order ID formatOrder #ELL-2845History rows, order detail

Renewal and Refill are kept as distinct concepts and defined explicitly in the explainer card on both list and detail screens:

  • Renewal: Your subscription cycle is billed (the recurring charge to your card).
  • Refill: A new bottle ships from the pharmacy after your provider reviews the renewal, usually 2–3 days after the charge clears.

PDP educational copy should preserve this distinction rather than collapsing the two into "your monthly order."

No "monthly" assumption anywhere. All cadence text flows through orderCycle.count and pluralizes correctly across any week count. The only unit in the engineering contract is 'week' (per Source of Truth); composite cycles like MOTS-C carry their on/off rhythm in the optional description field rather than as a different unit.

Deprecated terms. monthSupply and "monthly price" are deprecated everywhere in the patient portal. Replace with orderCycle + orderPrice and the Cycle Price / Cadence labels above.

Page hierarchy

Where each piece of subscription information sits on the page, top to bottom. PDP and checkout teams should mirror this where applicable so the patient sees the same vocabulary in the same relative position across surfaces.

List page (/account)

  1. Title row — "Your treatments" + treatment count, no Lora display type
  2. Reassessment callout (conditional)bg-primary-100, surfaces only when at least one subscription is in pending_reassessment. Above the cards by design so action-needed beats the upcoming-renewal signal.
  3. Treatment cards — one card per subscription, sorted by nextCharge.date ascending. Each card uses a 3-column grid: leading MedicationIcon (size="lg"), info column with name/category/price-cycle line, and a centered date hero on the right (RENEWS · MAY · 9 · in 3 days, or coral REASSESSMENT variant for pending).
  4. About explainer card — defines Renewal vs Refill. The canonical definition lives here.

Detail page (/account/subscription/[id])

  1. Back link — text-only "‹ My Ellie Journey".
  2. Header row — leading MedicationIcon (size="lg") + product name + status pill + meta line (category · formulation). No Cancel link at the top.
  3. Reassessment callout (conditional) — same atom as the list page.
  4. Coming Up hero band — Lora "Renews May 9 · in 3 days", body line about charge + ship dates, big Lora $478.00 numeral on the right with CYCLE PRICE caps label. Solid warm cream background #fdf8f1 with 4px secondary teal left accent.
  5. Subscription Details table — 4-column desktop grid (Cadence / Cycle Price / Next Charge / Next Shipment) with border-b-2 border-black double-rule header. Cycle progress demoted into a warm-cream footer band inside the same card.
  6. Provider card — clinician name + specialty + reassessment status + Message link. No avatar (kept minimal).
  7. Coming Up table (conditional, when active) — scheduled future events only (renewal + refill), 4 columns (Date / Type pill / Description / Amount).
  8. History table — past events, 5 columns (Date / Type / Description / Amount / Status). Refill rows link to Order detail at /account/orders/[orderId].
  9. Payment Method — VISA chip + ending in 4242 + Update payment link.
  10. About Refills & Renewals — same explainer atom as the list page.
  11. Manage — 3-card grid (Adjust cadence / Skip next shipment / Pause subscription). Inline icon variant: small icon next to label, description below.
  12. Cancel zone — quiet footer below Manage, hairline-separated, muted text: "If you need to stop your treatment entirely, you can cancel this subscription." Demoted from the top so the page does not feel like it is offering cancellation as a primary action.

Order detail page (/account/orders/[orderId])

Per-shipment receipt. Linked from History rows on the subscription detail. Mobile equivalent is a future ticket; this surface is desktop-only for DES-54.

  1. Back link — "‹ My Ellie Journey".
  2. Header — Order ID + status pill + Reorder / Download receipt actions.
  3. Subtitle — charge date + link back to parent subscription.
  4. Fulfillment progress — Charged → Reviewed → Shipped → Delivered with tracking number and View tracking link.
  5. Items table — Product / Qty / Cycle Price (with "per 8-week cycle" sub-label) / Total. Cycle Price column is the deliverable: cycle price appears next to line items so patients see the per-cycle context for the charge.
  6. Totals — Subtotal / Shipping / Total charged.
  7. Three info cards — Payment / Shipping address / From your subscription (with Manage subscription link back).

Cross-surface alignment

  • PDP — Cadence and price formatting matches the portal exactly. Pre-purchase copy ("you'll be charged $X every Y weeks") uses the same every {count} {unit} shape that the portal renders post-purchase. PDP does not own the canonical Renewal vs Refill definition; if it educates, it should paraphrase or link.
  • Checkout — The order-line cadence summary uses the same $X every N {unit} format the portal uses on the list card's price/cycle line. The "Subscription order details" panel mirrors the portal's SUBSCRIPTION DETAILS section labels (Cadence, Cycle Price, Next Charge, Next Shipment).
  • Hierarchy parity — Across all three surfaces, cadence and price appear together (never as separate disconnected facts), and they appear before renewal-timing information. The patient learns "what does this cost and how often" before "when's the next charge."

Mobile / desktop divergence

Mobile and desktop are intentionally not pixel-equivalent. Each viewport adopts the design register that fits it:

ConcernMobileDesktop
Detail heroCentered Lora date, calm, bg-secondary-50 mintTwo-column horizontal: text left, $478 hero numeral right, warm cream bg
Subscription blockFlat divide-y rows (iOS Settings register)4-col columnar table with double-rule header
HistoryTwo expandable disclosures: Renewal history + Shipment tracking (split by data type)Two visible tables: Coming Up + History (split by tense) with order detail links
ManageStacked iOS-style action rows, Cancel as last destructive in same list3-card grid (icon + label + description), Cancel demoted to quiet footer
Order detailNot present (future ticket)Full surface at /account/orders/[orderId]

What stays consistent across both viewports:

  • Identical engineering contract (Subscription, OrderCycle, etc.)
  • Identical vocabulary (Cycle / Cycle price / Cadence / Renewal / Refill / Supply / Order)
  • Identical Refill vs Renewal explainer copy
  • Identical category-tinted MedicationIcon glyph (size scales by viewport)
  • Identical canonical names (patient: Emma, clinician: Hollie Hall, FNP-C)
  • Identical engineering touchpoint annotations (upcoming_renewals, activity_log, order_details)

When PDP / checkout adopt this vocabulary, they should treat both viewport patterns as valid expressions of the same underlying contract. Don't flatten one into the other.

Prototype to production component map

The prototype uses descriptive names that don't match the production filenames the ticket points engineering at. Mapping below.

Prototype fileProduction touchpoint(s)
care_subscription_list.tsxupcoming_renewals.tsx + order_list_item.tsx
care_subscription_detail.tsxorder_details.tsx plus related modals: cancel_subscription_widget, change_payment_method_modal, delay_shipment_modal, edit_nickname_modal
order_detail.tsxNew surface — production order_receipt.tsx or equivalent. Mobile equivalent is a future ticket.
no_subscriptions.tsxaccount_client.tsx (empty branch)
medication-icon.tsxNew shared atom. Lucide-based, parameterized with size variants. Place in src/components/ for reuse across list, detail, order detail. See file header for caveat about Lucide vs branded icons.
status-pill.tsxNew shared atom. Place in src/components/ for reuse by reassessment_alert and failed_payment_alert.
lib/subscriptions.tsShared util location. OrderCycle, displayInterval, priceCycleLine, getCycleProgress, Order, getOrderById are utility/type exports; the data fetch lives on the production hooks layer.

Category-specific template notes

The default account template applies to all five ProductCategory values: Longevity, Weight Loss, Skincare, Sexual Health, HRT. Variation today is data-only:

  • Category and formulation render as the detail header subtitle (e.g. Weight Loss · GLP-1 injection).
  • MedicationIcon background tint: Longevity / HRT use the secondary teal family; Skincare / Weight Loss / Sexual Health use the primary coral family.
  • Tier surfaces inline with product name (e.g. Semaglutide (Tier 1)). No category-specific layout.
  • Provider clinician differs by program but uses the same atom.
  • Reassessment cadence is driven by prescriptionValidThrough, not category-specific layout.

No category currently warrants a layout deviation. If one emerges later (e.g. HRT lab ordering, Longevity supplement tracking), document the exception here.

Cadence handling rules

  • All cadence text is driven from OrderCycle = { count, unit: 'week', description? }. Per the Source of Truth engineering contract, unit is always 'week'.
  • Always pluralize via ${count === 1 ? 'week' : 'weeks'}. Centralized in displayInterval and priceCycleLine.
  • The same row template renders 8-week, 10-week, and 12-week cadences equivalently. No layout branches by count.
  • Date hero countdown text uses thresholds: today / tomorrow / in N days. Coral when state is pending_reassessment; teal when within 14 days; muted black-300 when further out.
  • Cycle progress reports in weeks: Week 3 of 8, Week 6 of 10, Week 9 of 12. Helper field is getCycleProgress().unitInCycle (named generically to allow non-week units in the future without renaming the type).

Engineering touchpoints

Each surface in the prototype maps to a named engineering touchpoint, called out in JSX comments adjacent to the relevant section:

TouchpointWhere it livesNotes
upcoming_renewals"Coming Up" Section in care_subscription_detail.tsxScheduled future events table
activity_log"History" Section in care_subscription_detail.tsxPast events table with order detail cross-links
order_detailsorder_detail.tsx + /account/orders/[orderId] routePer-shipment receipt with cycle price next to line items

Shared utilities exposed by @/lib/subscriptions:

  • displayInterval(orderCycle) → "Every 8 weeks" / "Every 12 weeks"
  • priceCycleLine(price, orderCycle) → "$478 every 8 weeks"
  • formatDate(iso, opts?) → "May 9" / "May 9, 2026"
  • daysUntil(iso) → 3 / -2
  • getCycleProgress(s) → { unitInCycle, dayInCycle, totalDays, percent }
  • monthsActiveSince(iso) → 8
  • sortByNextCharge(subs) → Subscription[] sorted by nextCharge.date asc
  • getOrderById(id) → Order | undefined
  • getOrdersForSubscription(subscriptionId) → Order[] sorted newest first