Skip to content

feat(accounts): add initial Account Provider, Gate and Field components - #579

Merged
mzaintariq merged 7 commits into
masterfrom
feature/accounts-components-foundation
Sep 25, 2026
Merged

mzaintariq merged 7 commits into
masterfrom
feature/accounts-components-foundation

Conversation

@mzaintariq

Copy link
Copy Markdown
Collaborator

Overview

This PR implements the first thin vertical slice of the Accounts component work defined in #563.

The goal of this slice is to establish the foundational Accounts component architecture using the same composable/headless approach as Checkout.

Instead of introducing a single Account Dashboard component, this adds the core building blocks that future Account experiences will compose around:

  • EP Account Provider
  • EP Account Gate
  • EP Account Field

What this adds

EP Account Provider

Introduces the $ctx.account boundary for Account components.

Current context shape:

  • accountMember
  • selectedAccount
  • accountRoster
  • lapsedAccount
  • derived state

The Provider owns the mapping boundary so future identity/session changes can be absorbed without requiring consuming components to change.

EP Account Gate

Adds conditional rendering based on account state:

  • anonymous
  • authenticated
  • selected
  • lapsed

EP Account Field

Adds composable access to Account context values.

Currently exposes stable account values:

  • selected account id/name
  • lapsed account id/name
  • member id
  • account state

Member profile fields are intentionally not included until there is a supported runtime source for them.

Studio / Design-time support

Adds previewState support following the Checkout Provider pattern.

Preview states allow Studio users to compose and validate Account experiences without requiring a real authenticated session.

Runtime pages continue to rely on the real account/session integration once connected.

What is intentionally not included

This PR does not implement:

  • login/logout flows
  • account switching UI
  • identity/session integration
  • member profile APIs
  • orders
  • addresses
  • password flows

Those will be implemented as follow-up slices on top of this foundation.

Documentation

Added:

  • README documentation for Accounts components
  • CHANGELOG entry

Testing

Added tests covering:

  • Account Provider context resolution
  • preview states
  • Account Gate behaviour
  • Account Field rendering
  • Provider → Gate → Field composition

Test results:

150 test suites passed
2378 tests passed

@mzaintariq mzaintariq self-assigned this Sep 24, 2026
@mzaintariq

Copy link
Copy Markdown
Collaborator Author

Updated EPAccountGate to behave as a pure conditional component rather than a layout wrapper.

Since Gate only controls whether children render and does not render its own UI, it now returns children directly instead of introducing a DOM wrapper. This avoids layout/HTML composition issues when Gate is nested inside other components.

As a result, the Gate instance itself is no longer styleable in Studio. This is intentional — Gate is treated as a control-flow primitive rather than a visual component, so styling should be applied to the rendered children instead.

Also updated the component registration metadata with styleSections: false so Studio does not expose unused style controls for Gate.

Additionally, updated invalid previewState fallback handling so unknown canvas preview values fall back to the selected mock state (matching the existing auto preview floor) instead of anonymous. Runtime behaviour remains unchanged.

Left broader cleanup items (state derivation, shared preview resolver extraction, Field path resolver) unchanged since they are separate package-level concerns.

@field123 field123 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The names in $ctx.account match the session data on master. Some changes must happen before merge, because of ADR-0004 ("registered surfaces are append-only"). This package is published as a hostless bundle. The publish-hostless step rejects a release that removes or renames a registered component, prop, option or $ctx key. After we publish this PR, we can add to that surface, but we cannot change or remove what is there.

Must fix

  1. The Provider must read the real session. Outside Studio, resolveAccountContext always returns anonymous (EPAccountProvider.tsx:46-48). On a published page, an anonymous Gate shows the login prompt to a signed-in member. The authenticated and selected Gates never render. The session data is already on master (#534, #568, and useEpIdentity().getSession() from #569), so this slice can use it.
  2. "authenticated" has two meanings. state: "authenticated" means a member with no selected account. when="authenticated" means any member (EPAccountGate.tsx:72). Give the state a different name, for example memberOnly.
  3. AccountRef.name must be optional. The session types on master have name? (EpAccountSummary, envelope.ts).

accountRoster

The key belongs in the Provider, because #563 asks for roster state before the switcher. But the roster is not in the session. It comes from /ep/account/roster as { accounts, total }. It is paged, so total is not the length of accounts. A plain array cannot hold total or a loading state, and ADR-0004 does not let us change the shape later. Use the { accounts, total } shape, or remove the key until the switcher slice. A new key is an addition, so you can add it later at no cost.

Smaller items

  • When previewState is auto, Checkout shows live data and uses a mock only when no data exists. Here auto always shows the selected mock (EPAccountProvider.tsx:49-50). Match Checkout.
  • With no Provider, the Gate and Field render nothing in Studio. EPCartField shows mock data in the editor. Do the same.
  • Under auto, a when="anonymous" Gate is hidden in the canvas, so nobody can edit its content.
  • Remove the unused className prop on the Gate.
  • Remove the comments and Studio descriptions that describe future work. They will become incorrect.
  • Remove the "published contract" test that runs regexes over constant mock data. It cannot fail.
  • The Field state option shows the raw value (for example selected) as page text.
  • Move the CHANGELOG entry. Master released the old "Unreleased" section as 0.7.0.

Drafted with AI assistance. Verified before posting.

@mzaintariq

Copy link
Copy Markdown
Collaborator Author

I've updated the slice based on the feedback and verified the flows locally.

Verified behaviour:

  • Studio previewState="auto" uses the real logged-in session when available.
  • Explicit preview states continue to use fixtures for design-time composition.
  • Published/runtime pages ignore preview state and use the real identity/session state (verified locally).

The contract updates are also in place:

  • $ctx.account now maps from identity/session data (member, selected account, roster, lapsed state) without exposing credentials.
  • Derived state is now anonymous | memberOnly | selected | lapsed to avoid ambiguity with authenticated.
  • AccountRef.name is optional and accountRoster now follows the roster API shape { accounts, total }.
  • Removed the smaller surface issues around Gate className, raw state Field exposure, stale docs/comments, and updated README/CHANGELOG.

…client and hold Gates while loading

The Provider fetched a hardcoded /api/ep, so a store that mounts the auth
handler elsewhere read every shopper as anonymous. It now uses
useEpIdentity(), which takes basePath from ShopperContext, and discards a
read made with a previous basePath.

$ctx.account was anonymous until the session read settled, so an anonymous
Gate showed signed-out content to a signed-in member on first render.
isLoading is now published, and Gates render nothing while it is true.

The CHANGELOG entry moves out of the section master released as 0.7.0.
@mzaintariq
mzaintariq merged commit 7f8cb6c into master Sep 25, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants