Account portal — DES-54 handoff notes
Mobile prototype. Pairs with the desktop pass 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.
| Concept | Portal string | Surface |
|---|---|---|
| Cadence summary | every {count} weeks | List row, hero, detail |
| Price and cadence | $478 every 8 weeks | List row left meta |
| Imminent renewal | in 3 days / today / tomorrow | List row right column |
| Distant renewal | in 23 days (muted) | List row right column |
| Reassessment due | Reassess Jun 14 (coral) | List row right column |
| Hero next renewal | Wed, Jun 18 (Lora) | Coming up next card |
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.
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)
- Mobile header — sticky, back chevron, ellie. wordmark, hamburger menu.
- Section label — "MY ELLIE JOURNEY" caps,
tracking-ultra-wide. - Reassessment callout (conditional) —
bg-primary-100, surfaces only when at least one subscription is inpending_reassessment. Above the hero by design so action-needed beats the upcoming-renewal signal. - Coming up next hero — soonest upcoming renewal across all active subscriptions. Lora date in
text-secondary-dark, day-count subtitle, product + amount footer separated by a divider. The single thing this page tells the patient at a glance. - Section label — "SUBSCRIPTIONS".
- Subscriptions list card — flat rows sorted by
nextCharge.dateascending. Each row: leading medication icon (driven bySubscription.form); name + price/cycle on the left; renewal countdown + status pill on the right. See Cadence handling rules for emphasis thresholds. - About explainer card — defines Renewal vs Refill. The canonical definition lives here.
- Bottom tab bar — Home / Orders / Wallet / Account.
bg-primary.
Detail page (/account/subscription/[id])
- Back link — text-only "‹ My Ellie Journey".
- Title row — product name + status pill, right-aligned.
- Category subtitle —
category · tier. - Reassessment callout (conditional) — same atom as the list page.
- Hero — Lora renewal date, day-count subtitle, charge breakdown ("$478.00 charged to Visa ending 4242").
- SUBSCRIPTION rows — Cadence, Cycle price, Cycle progress, Next shipment, Prescription valid. All driven from
orderCycle,orderPrice,getCycleProgress,nextShipment,prescriptionValidThrough. - Cycle progress thin track — visual companion to the "Cycle progress" row above.
- PROVIDER block — passive info card. Clinician name + specialty + license, no drill-in. Patient-clinician messaging lives on its own surface, owned by a separate ticket; not exposed from this page.
- HISTORY rows — Renewal history (charges) and Shipment tracking (shipments). Expand inline; reverse-chronological.
- PAYMENT METHOD row — card brand + last 4. Drill-in opens
change_payment_method_modalin production. - About explainer card — same Renewal/Refill definition as the list page.
- MANAGE rows — drill-in actions, each opening a production modal:
- Adjust cadence → cadence-change modal
- Skip next shipment →
delay_shipment_modal - Pause / Resume → pause confirmation modal
- Cancel →
cancel_subscription_widgetDefault actions render intext-secondary-dark, destructive intext-primary. The modals themselves are out of scope for DES-54.
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 row's price/cycle line. The "Subscription order details" panel mirrors the portal'sSUBSCRIPTIONsection labels (Cadence, Cycle price, Prescription valid). - 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."
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 file | Production touchpoint(s) |
|---|---|
subscription_list.tsx | upcoming_renewals.tsx + order_list_item.tsx |
subscription_detail.tsx | order_details.tsx plus related modals: cancel_subscription_widget, change_payment_method_modal, delay_shipment_modal, edit_nickname_modal |
no_subscriptions.tsx | account_client.tsx (empty branch) |
account_header.tsx | account_header.tsx |
account_nav.tsx | account_nav.tsx |
status-pill.tsx | New shared atom. Place in src/components/ for reuse by reassessment_alert and failed_payment_alert. |
page.tsx | account/page.tsx + account_client.tsx |
lib/subscriptions.ts | Shared util location. OrderCycle, displayInterval, priceCycleLine, getCycleProgress are utility helpers; 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 tier render as the detail header subtitle (e.g.
Weight Loss · Tier 1). - 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,unitis always'week'. - Always pluralize via
${count === 1 ? 'week' : 'weeks'}. Centralized indisplayIntervalandpriceCycleLine. - The same row template renders 8-week, 10-week, and 12-week cadences equivalently. No layout branches by count.
- Right-column timing (
in N days) is computed fromnextCharge.dateregardless of cadence. Threshold for bold-vs-muted emphasis is 7 days. - Cycle progress reports in weeks:
Week 3 of 8,Week 6 of 10,Week 9 of 12.
Engineering touchpoints
Named entry points the ticket asked for, captured here so implementation maps cleanly:
-
upcoming_renewals.tsx— receives the prototype'sSubscriptionListpatterns: sort bynextCharge.dateascending, "Coming up next" hero pulls the soonest renewal that is not yet past (defensive filter ondaysUntil >= 0), pending reassessment callout above the hero. -
order_details.tsx— receives the prototype'sSubscriptionDetailpatterns: cadence + cycle price + cycle progress + next shipment + prescription valid as flat rows; hero block surfacing next renewal date in Lora; PROVIDER / HISTORY / PAYMENT METHOD / MANAGE sections. The HISTORY sections expand inline usinguseStateper section; both lists render reverse-chronological. -
Shared account utilities —
displayInterval,priceCycleLine,getCycleProgress,daysUntil,formatDate,parseDate,sortByNextChargeshould land in the shared util location. TypesOrderCycle,Subscription,SubscriptionStatus,ChargeRecord,ShipmentRecordalign with the API contract. -
Status pill — promote from prototype to shared atom; also needed by
reassessment_alertandfailed_payment_alert. -
Medication icon —
src/components/medication-icon.tsx. Renders a Lucide icon for the medication's delivery form inside a tinted circle on the leading edge of each list row. Driven by theSubscription.formandSubscription.categoryfields.Background tint varies by category to give the list visual rhythm:
- Teal family (
bg-secondary-100+text-secondary-dark): Longevity, HRT — the clinical / wellness register. - Coral family (
bg-primary-200+text-primary): Skincare, Weight Loss, Sexual Health — the transformation / beauty / intimate register.
Current Lucide mappings:
SprayCan(spray),Syringe(injection),Pill(oral),Droplets(topical, approximate). Adding a new form means extendingMedicationFormand adding a Lucide icon to theGLYPHSmap. Adding a new category means deciding which color family it joins in theTEAL_CATEGORIESlist.Important caveat: Lucide is not the EllieMD brand icon system. EllieMD's branded iconography uses floral/botanical motifs (
flower-success.svg,flower-warning.svgetc.). For medication-domain glyphs (syringe, pill, spray) Lucide is a defensible production choice since these are utility icons that wouldn't carry the brand floral motif. If branded medication icons exist in the EllieMD Figma library, swap to those. - Teal family (
-
New dependencies introduced —
lucide-react(icon set, ~2-3KB per icon, tree-shakeable),react-markdown+remark-gfm(used only by the dev/notesroute, not by user-facing pages). Strip the/notesroute and these last two deps if not migrating the dev tooling. -
Data shape note — the prototype assumes a
Subscriptioncarries bothcharges: ChargeRecord[]andshipments: ShipmentRecord[]arrays in addition to the convenience pointerslastCharge/nextCharge/lastShipment/nextShipment. The HISTORY sections render directly from those arrays in reverse-chronological order. If the production API only exposes the convenience pointers today, the arrays will need to be added (or the history view backed by separate endpoints) for the inline expansions to work.
Reading these notes
This document is also rendered inside the prototype itself at /notes, linked from the DOCS row of the dev toolbar at the top of every page. The toolbar gets stripped during production integration, but during review (and during initial integration work) the notes are one click away from any screen. The raw markdown lives at the repo root for direct viewing in source.
Cadence variants demonstrated
The dev toolbar Multiple active scenario seeds three subscriptions with different cadences so engineers and reviewers can see the layout doesn't assume a single shape:
- NAD+ Nasal Spray: 8-week cadence, $478 per cycle (Longevity)
- Semaglutide: 12-week cadence, $897 per cycle (Weight Loss, Tier 1)
- BPC-157/TB-500 Capsules: 10-week cadence, $398 per cycle (Longevity)