@vtex/faststore 0.0.2 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/README.md +11 -5
  3. package/dist/catalog.json +556 -265
  4. package/dist/components.d.ts +3 -1
  5. package/dist/components.js +5 -3
  6. package/dist/content-types.json +16 -0
  7. package/dist/core/dist/actions/registry.d.ts +1 -1
  8. package/dist/core/dist/actions/registry.js +1 -1
  9. package/dist/core/dist/actions/registry.js.map +1 -1
  10. package/dist/docs/cart.md +2 -2
  11. package/dist/docs/my-account-profile.md +208 -0
  12. package/dist/docs/session.md +12 -6
  13. package/dist/docs/sign-in-redirect.md +137 -0
  14. package/dist/matter/access-code-sign-in/access-code-sign-in.d.ts +2 -2
  15. package/dist/matter/access-code-sign-in/access-code-sign-in.js +5 -2
  16. package/dist/matter/access-code-sign-in/access-code-sign-in.js.map +1 -1
  17. package/dist/matter/buy-button/buy-button.d.ts +1 -1
  18. package/dist/matter/buy-button/buy-button.js +3 -1
  19. package/dist/matter/buy-button/buy-button.js.map +1 -1
  20. package/dist/matter/cart/cart.d.ts +6 -6
  21. package/dist/matter/cart/cart.js +13 -6
  22. package/dist/matter/cart/cart.js.map +1 -1
  23. package/dist/matter/cart-item/cart-item.d.ts +2 -2
  24. package/dist/matter/cart-item/cart-item.js +5 -2
  25. package/dist/matter/cart-item/cart-item.js.map +1 -1
  26. package/dist/matter/client-preferences/client-preferences.js +3 -1
  27. package/dist/matter/client-preferences/client-preferences.js.map +1 -1
  28. package/dist/matter/client-profile/client-profile.d.ts +1 -1
  29. package/dist/matter/client-profile/client-profile.js +3 -1
  30. package/dist/matter/client-profile/client-profile.js.map +1 -1
  31. package/dist/matter/client-profile-form/client-profile-form.d.ts +1 -1
  32. package/dist/matter/client-profile-form/client-profile-form.js +3 -1
  33. package/dist/matter/client-profile-form/client-profile-form.js.map +1 -1
  34. package/dist/matter/delivery-options/delivery-options-accordion.d.ts +1 -1
  35. package/dist/matter/delivery-options/delivery-options-accordion.js +3 -1
  36. package/dist/matter/delivery-options/delivery-options-accordion.js.map +1 -1
  37. package/dist/matter/delivery-options/delivery-options-controller.d.ts +1 -1
  38. package/dist/matter/delivery-options/delivery-options-controller.js +3 -1
  39. package/dist/matter/delivery-options/delivery-options-controller.js.map +1 -1
  40. package/dist/matter/delivery-options/delivery-options.d.ts +1 -1
  41. package/dist/matter/delivery-options/delivery-options.js +3 -1
  42. package/dist/matter/delivery-options/delivery-options.js.map +1 -1
  43. package/dist/matter/email-identification/email-identification.d.ts +1 -1
  44. package/dist/matter/email-identification/email-identification.js +3 -1
  45. package/dist/matter/email-identification/email-identification.js.map +1 -1
  46. package/dist/matter/identity-summary/identity-summary.d.ts +1 -1
  47. package/dist/matter/identity-summary/identity-summary.js +3 -1
  48. package/dist/matter/identity-summary/identity-summary.js.map +1 -1
  49. package/dist/matter/my-account-profile/my-account-profile.d.ts +291 -0
  50. package/dist/matter/my-account-profile/my-account-profile.js +856 -0
  51. package/dist/matter/my-account-profile/my-account-profile.js.map +1 -0
  52. package/dist/matter/product-list/product-list.d.ts +1 -1
  53. package/dist/matter/product-list/product-list.js +3 -1
  54. package/dist/matter/product-list/product-list.js.map +1 -1
  55. package/dist/matter/region/region.d.ts +1 -1
  56. package/dist/matter/region/region.js +5 -2
  57. package/dist/matter/region/region.js.map +1 -1
  58. package/dist/matter/session/session.js +1 -1
  59. package/dist/matter/session/session.js.map +1 -1
  60. package/dist/matter/shipping-address/shipping-address.d.ts +1 -1
  61. package/dist/matter/shipping-address/shipping-address.js +4 -2
  62. package/dist/matter/shipping-address/shipping-address.js.map +1 -1
  63. package/dist/matter/sign-in-form/sign-in-form.d.ts +1 -1
  64. package/dist/matter/sign-in-form/sign-in-form.js +3 -1
  65. package/dist/matter/sign-in-form/sign-in-form.js.map +1 -1
  66. package/dist/matter/sign-in-redirect/sign-in-redirect.d.ts +45 -0
  67. package/dist/matter/sign-in-redirect/sign-in-redirect.js +127 -0
  68. package/dist/matter/sign-in-redirect/sign-in-redirect.js.map +1 -0
  69. package/dist/sdk/actions/actions-override-context.js +37 -0
  70. package/dist/sdk/actions/actions-override-context.js.map +1 -0
  71. package/dist/sdk/actions/cart-action-provider.d.ts +8 -6
  72. package/dist/sdk/actions/cart-action-provider.js +10 -8
  73. package/dist/sdk/actions/cart-action-provider.js.map +1 -1
  74. package/dist/sdk/actions/identity-action-provider.d.ts +2 -2
  75. package/dist/sdk/actions/identity-action-provider.js +4 -4
  76. package/dist/sdk/actions/identity-action-provider.js.map +1 -1
  77. package/dist/sdk/actions/inject-actions.d.ts +1 -5
  78. package/dist/sdk/actions/inject-actions.js +23 -21
  79. package/dist/sdk/actions/inject-actions.js.map +1 -1
  80. package/dist/sdk/actions/region-action-provider.d.ts +4 -3
  81. package/dist/sdk/actions/region-action-provider.js +6 -5
  82. package/dist/sdk/actions/region-action-provider.js.map +1 -1
  83. package/dist/sdk/actions/session-action-provider.d.ts +4 -3
  84. package/dist/sdk/actions/session-action-provider.js +6 -5
  85. package/dist/sdk/actions/session-action-provider.js.map +1 -1
  86. package/dist/sdk/actions/shipping-action-provider.d.ts +4 -3
  87. package/dist/sdk/actions/shipping-action-provider.js +6 -5
  88. package/dist/sdk/actions/shipping-action-provider.js.map +1 -1
  89. package/dist/sdk/cart/use-cart-actions.js.map +1 -1
  90. package/dist/sdk/client/create-client.js +24 -2
  91. package/dist/sdk/client/create-client.js.map +1 -1
  92. package/dist/sdk/content/create-content-runtime.js +1 -1
  93. package/dist/utils/safe-href.js +74 -4
  94. package/dist/utils/safe-href.js.map +1 -1
  95. package/package.json +1 -18
@@ -0,0 +1,208 @@
1
+ # MyAccountProfile
2
+
3
+ **Category:** identity
4
+ **Purpose:** The data root, frame, and field Controls for the My Account > Profile page — the Root owns the shopper's saved profile, the draft over it, the view↔edit toggle, and the single batched Save; the field Controls (name, phone, tax ID, birth date, gender — SFS-3288/3289/3290) register with the Root instead of it enumerating them, so which fields the page shows is a composition decision, in both states, from one act of composing.
5
+
6
+ ---
7
+
8
+ ## Visual cues (how to identify in an image)
9
+
10
+ - A My Account > Profile page showing saved shopper data (name, email, phone, tax ID, birth date, gender) with a single "Edit" action.
11
+ - Pressing Edit swaps the same fields into inputs, with "Save" and "Cancel" replacing "Edit".
12
+ - Email always renders read-only, in both states — never an input.
13
+ - Save success or failure surfaces as a toast (SFS-3366), not an inline line or banner near the fields.
14
+
15
+ Use `MyAccountProfile` (not a hand-assembled combination of ad-hoc state) whenever the image shows this whole view/edit/save profile block, composing its own field Controls (`FirstNameControl`, `LastNameControl`, `PhoneControl`, `TaxIdControl`, `BirthDateControl`, `GenderControl`) for whichever fields the page shows.
16
+
17
+ ---
18
+
19
+ ## When to use
20
+
21
+ - The My Account > Profile page's frame: the view↔edit toggle, the batched Save, Cancel, the read-only email, and the Toast-based save feedback.
22
+
23
+ ## When NOT to use
24
+
25
+ - Rendering an individual profile field's presentation or input directly on the Root — that is each field Control's job. This Root owns no field's formatting or masking; the Controls delegate that to the `Field`, `PhoneField`, `DocumentField`, `Input`, and `Select` particles.
26
+ - The signed-out prompt — compose the existing `SignInForm` alongside a `SessionGuard` in the store topology; this Root renders nothing for that audience.
27
+ - Editing the email — it is the login identifier and is read-only by construction; there is no edit-state rendering for it.
28
+
29
+ ---
30
+
31
+ ## Composition
32
+
33
+ `MyAccountProfile` is a **compound**: `Root` owns the data + mechanics; the frame parts and every field Control are composed freely (any order, any subset). A field the schema author does not compose is absent from both the view state and the edit state, and cannot reach the write payload — there is no separate flag to keep it out.
34
+
35
+ ```
36
+ ToastProvider (required ancestor; SFS-3366 — hosts Save's success/error toasts)
37
+ └── SessionRoot (required ancestor; must resolve to loggedIn)
38
+ └── MyAccountProfile.Root (data-fs-my-account-profile; own fragment/data root; owns draft + Save)
39
+ ├── MyAccountProfile.Email (read-only, both states)
40
+ ├── MyAccountProfile.FirstNameControl / .LastNameControl (required; Field particle)
41
+ ├── MyAccountProfile.PhoneControl (optional; compose up to twice via the `field` prop)
42
+ ├── MyAccountProfile.TaxIdControl (optional; self-gates on activeDocumentType)
43
+ ├── MyAccountProfile.BirthDateControl (optional; Field + Input type="date")
44
+ ├── MyAccountProfile.GenderControl (optional; Select over a configurable options prop)
45
+ ├── MyAccountProfile.EditButton (view state only)
46
+ ├── MyAccountProfile.SaveButton (edit state only)
47
+ └── MyAccountProfile.CancelButton (edit state only)
48
+ ```
49
+
50
+ A field Control is absent from the composition means absent from both states and never in the write payload — there is no separate `show*`/`visible` prop anywhere in this compound (compose-or-omit).
51
+
52
+ `Root` is self-contained: it declares the colocated fragment over `customerProfile` — the identical field set SFS-3286's `customerProfileUpdate` mutation returns — and self-gates on the session's `loggedIn` state. Every frame part and field Control `requiresContext: 'my-account-profile'`, except `MyAccountProfileSkeleton`: it renders directly from the Root, before any Provider exists, so it declares no context requirement (mirrors `ProductShelfSkeleton`). Save calls `useToast()` for its success/error feedback (Decision 10), so `Root` also requires a `ToastProvider` ancestor.
53
+
54
+ ---
55
+
56
+ ## Behavior
57
+
58
+ - **Field registration, not enumeration**: a Control calls `registerField({ key, validate?, normalize? })` in an effect on mount and unregisters on cleanup. The Root validates, batches, and focuses only over the live registry — it never names a field itself (Decision 1).
59
+ - **Dirty is value comparison, not a touched flag**: `dirty(key) === draft[key] !== savedValues[key]`. Retyping a field back to its saved value makes Save a no-op for it, for free.
60
+ - **Absent and empty are different instructions**: a dirty field cleared to `''` erases upstream; a field the shopper never touched is omitted from the payload entirely.
61
+ - **Save order**: compute dirtiness first → nothing dirty raises the saved toast without dispatching → otherwise validate only the dirty registered fields' current draft values (Decision 9) → any error blocks the dispatch and focuses the first invalid field in rendered DOM order → otherwise normalize dirty values and dispatch one `$actions.identity.customerProfileUpdate` → a `userError` raises an error toast and keeps the edit state with the draft intact → success returns to the view state and raises a success toast (Decision 10). A field the shopper never touched is neither validated nor sent.
62
+ - **Edit always opens from the latest saved values**: pressing Edit re-seeds the draft from `savedValues` before switching modes, so a value the server normalized on a previous save (not this task's concern, but a sibling Control's) is what the shopper sees, not a stale local draft.
63
+ - **Background re-delivery**: when `$data` delivers new values while editing, a field re-seeds only if its draft still equals the previously saved value; a dirty field is never clobbered.
64
+ - **Self-gate**: renders nothing outside `state === 'loggedIn'`, even if the topology's `SessionGuard` is missing. Renders `MyAccountProfileSkeleton` while `$data` is unresolved and the shopper is logged in — never an empty summary.
65
+ - **PII**: no profile value appears in any log line. A write that throws (rather than resolving `userErrors`) logs only the error's name; the message shown to the shopper is the `saveFailedMessage` schema prop.
66
+
67
+ ---
68
+
69
+ ## Props
70
+
71
+ **`MyAccountProfile.Root`** — `documentType` (string, e.g. `'CPF'`; the store-configured fallback used when the saved `documentType` is not a registered type), `savedMessage` (string, default `'Your profile has been saved.'`; the toast title on a successful Save), `saveFailedMessage` (string, default `'Unable to save your profile. Please try again.'`; the toast title on a failed Save). Declares `actions: { identity: ['customerProfileUpdate'] }`, `requiresContext: 'session'`, `providesContext: 'my-account-profile'`, `fetchStrategy: 'reactive'`.
72
+
73
+ **`.EditButton` / `.SaveButton` / `.CancelButton`** — `label` (sensible default) or composed `children`.
74
+
75
+ **`.Email` / `.Skeleton`** — no props beyond `className`.
76
+
77
+ **`.FirstNameControl` / `.LastNameControl`** — `label` (default `'First name'` / `'Last name'`), `requiredMessage` (shown when Save is blocked on an empty value).
78
+
79
+ **`.PhoneControl`** — `field` (`'homePhone' | 'mobilePhone'`, default `'mobilePhone'`; compose the Control twice to expose both numbers), `country` (`'US' | 'BR'`, default `'US'`), `label`, `invalidMessage`.
80
+
81
+ **`.TaxIdControl`** — `label` (default `'National tax ID'`), `invalidMessage`, `lockedReason` (the inline copy shown beside the disabled input when a CPF is already saved).
82
+
83
+ **`.BirthDateControl`** — `label` (default `'Birth date'`).
84
+
85
+ **`.GenderControl`** — `label` (default `'Gender'`), `placeholder`, `clearLabel` (default `'Clear'`; the button shown once a draft value is set, since the Select itself offers no way back to empty), `options` (`{ value, label }[]`, defaults to a sensible set: female / male / other / prefer not to say).
86
+
87
+ ---
88
+
89
+ ## Field Controls anatomy (SFS-3288/3289/3290)
90
+
91
+ Each Control is a thin binding over an existing presentational particle — it adds no validation or formatting logic of its own beyond wiring the particle into the Root's registration contract:
92
+
93
+ - **Registration**: in a mount effect, a Control calls `registerField({ key, validate?, normalize? })` and returns the unregister function from the effect. `firstName`/`lastName` register a validator that rejects an empty draft value; every other Control either registers a format-only validator (a present value must be well-formed; empty is always valid) or no validator at all.
94
+ - **Rendering**: `mode === 'view'` renders `savedValues[key]` as plain text (empty, never a placeholder, when the key was never saved); `mode === 'edit'` renders the bound particle over `draft[key]`, writing through `setField(key, value)`.
95
+ - **Normalization**: `PhoneControl` registers `normalize = (v) => v ? toE164(v, country) : v`; `BirthDateControl` registers the native `yyyy-mm-dd` ↔ stored-format identity map. Normalization runs once, on Save, only for a field the shopper actually changed.
96
+ - **The locked tax ID**: when a saved CPF exists, `TaxIdControl`'s edit branch renders a **disabled** input carrying the saved (masked) value, a `data-fs-my-account-profile-tax-id-readonly` hook, and `lockedReason` — never plain text, never omitted. It registers no validator in this branch and is never dirty. With no saved document, it renders an editable, optional `DocumentField` instead.
97
+ - **Self-gate**: `TaxIdControl` renders nothing when the Root's `activeDocumentType` is undefined or unsupported (V0 supports CPF only).
98
+ - **One phone Control, two bindings**: `PhoneControl`'s `field` prop selects which draft key an instance binds; composing it twice (once per `field` value) exposes both `homePhone` and `mobilePhone` as independent, independently-erasable fields.
99
+
100
+ ---
101
+
102
+ ## Context contract (`useMyAccountProfile()`)
103
+
104
+ Every field Control consumes this hook — the seam SFS-3287 designs and SFS-3288/3289/3290 consumes as-is:
105
+
106
+ | Field | Shape | Notes |
107
+ |---|---|---|
108
+ | `mode` | `'view' \| 'edit'` | A Control renders its own branch off this; there is no `show*` prop anywhere. |
109
+ | `savedValues` / `draft` | `Record<key, string>` | Every editable key is always a string; a `null` upstream value is `''`. |
110
+ | `fieldErrors` | `Partial<Record<key, string>>` | Set on a blocked Save; cleared on the field's own next change or on Cancel. |
111
+ | `email` | `string` | The read-only login identifier. |
112
+ | `activeDocumentType` | `string \| undefined` | The saved `documentType` when registered, else the Root's `documentType` prop. |
113
+ | `pending` | `boolean` | Set while the Save dispatch is in flight; parts self-gate and disable off it. Save success/failure feedback surfaces as a toast (Decision 10), not through context. |
114
+ | `registerField(registration)` | `() => unregister` | Call in an effect; the returned function unregisters on cleanup. |
115
+ | `setField(key, value)` | — | Writes one draft value; clears that field's error. |
116
+ | `edit()` / `cancel()` | — | Mode transitions; `cancel()` restores `draft` from `savedValues`. |
117
+
118
+ The `data-fs-my-account-profile-field="<key>"` attribute on each Control's own wrapper element is part of this contract — it is the seam `focusFirstInvalid` queries by rendered DOM order on a blocked Save.
119
+
120
+ ---
121
+
122
+ ## Canonical schema example
123
+
124
+ ```json
125
+ {
126
+ "toast-provider": { "type": "ToastProvider", "children": ["session"] },
127
+ "session": { "type": "SessionRoot", "children": ["profile-guard"] },
128
+ "profile-guard": {
129
+ "type": "SessionGuard",
130
+ "props": { "when": ["loggedIn"] },
131
+ "children": ["profile-root"]
132
+ },
133
+ "profile-root": {
134
+ "type": "MyAccountProfileRoot",
135
+ "props": { "documentType": "CPF" },
136
+ "children": [
137
+ "profile-email",
138
+ "profile-first-name",
139
+ "profile-last-name",
140
+ "profile-mobile-phone",
141
+ "profile-tax-id",
142
+ "profile-edit",
143
+ "profile-save",
144
+ "profile-cancel"
145
+ ]
146
+ },
147
+ "profile-email": { "type": "MyAccountProfileEmail", "children": [] },
148
+ "profile-edit": { "type": "MyAccountProfileEditButton", "children": [] },
149
+ "profile-save": { "type": "MyAccountProfileSaveButton", "children": [] },
150
+ "profile-cancel": { "type": "MyAccountProfileCancelButton", "children": [] },
151
+ "profile-first-name": { "type": "MyAccountProfileFirstNameControl", "children": [] },
152
+ "profile-last-name": { "type": "MyAccountProfileLastNameControl", "children": [] },
153
+ "profile-mobile-phone": {
154
+ "type": "MyAccountProfilePhoneControl",
155
+ "props": { "field": "mobilePhone", "country": "BR" },
156
+ "children": []
157
+ },
158
+ "profile-tax-id": { "type": "MyAccountProfileTaxIdControl", "children": [] }
159
+ }
160
+ ```
161
+
162
+ Add each field Control id to `profile-root`'s `children` array for the fields the store exposes. A sibling `SignInForm`, gated by a `SessionGuard` with `when: ["anonymous", "identified"]`, composes next to `profile-guard` for the signed-out audience.
163
+
164
+ ---
165
+
166
+ ## Common mistakes
167
+
168
+ - **Rendering `MyAccountProfile.Root` outside `SessionRoot`.** `useSession()` throws; the page must host a `SessionRoot`.
169
+ - **Rendering `MyAccountProfile.Root` outside a `ToastProvider`.** Save calls `useToast()` (Decision 10); without a `ToastProvider` ancestor that call throws.
170
+ - **Expecting the email to be editable.** It is always read-only; there is no edit-state rendering for it anywhere in this compound.
171
+ - **Composing a field Control without an unmount cleanup on its `registerField` effect.** A Control that never unregisters leaks a stale validator into the registry after it stops rendering — every shipped Control returns the unregister function from its effect; keep that pattern in a custom Control.
172
+ - **Reading `draft` in the view state, or `savedValues` in the edit state.** Each Control must branch on `mode` itself (Decision 2); the Root does not hide this for you.
173
+ - **Rendering a frame part or field Control outside `MyAccountProfileRoot`.** Every part except `Skeleton` `requiresContext: 'my-account-profile'` and throws otherwise.
174
+ - **Giving a required Control's native input a `required` HTML attribute.** Requiredness is expressed only through the registered `validate` function (Decision 2 of SFS-3288/3289/3290); a native `required` attribute makes the browser block the form's `submit` event before the Root's own validation and focus logic ever runs.
175
+ - **Hiding the `TaxIdControl` in the edit state when a CPF is already saved.** It must render a disabled, locked input instead — a field disappearing between view and edit reads as a bug (product-confirmed, 2026-09-03).
176
+ - **Adding a `showPhone`/`visible` boolean prop to a Control.** Presence is composition; a boolean visibility prop contradicts compose-or-omit and is out of scope for every Control in this group.
177
+
178
+ ---
179
+
180
+ ## Styling
181
+
182
+ | Component | What you style | Plausible classes |
183
+ |---|---|---|
184
+ | `MyAccountProfile.Root` | The `data-fs-my-account-profile` root `<form>`; carries `-mode`/`-pending` | `flex flex-col gap-4` |
185
+ | Every field Control's wrapper | `data-fs-my-account-profile-field="<key>"` — the Root's focus seam, on every Control | `flex flex-col gap-1` |
186
+ | `.Email` | `[data-fs-my-account-profile-email]` | `text-sm text-neutral-600` |
187
+ | Save feedback | Styled through the `[[toast]]` compound (`Toast.Root`/`Toast.Title`/`Toast.Description`), not through this Root's own `data-fs-*` hooks. | — |
188
+ | `.EditButton` / `.SaveButton` / `.CancelButton` | `[data-fs-my-account-profile-edit\|save\|cancel]` | `rounded-md bg-blue-600 px-4 py-2 font-semibold text-white` |
189
+ | `.Skeleton` | `[data-fs-my-account-profile-skeleton]`, lines via `[data-fs-my-account-profile-skeleton-line]` | `animate-pulse` |
190
+ | `.FirstNameControl` / `.LastNameControl` | `[data-fs-my-account-profile-first-name]` / `[data-fs-my-account-profile-last-name]` | `flex flex-col gap-1` |
191
+ | `.PhoneControl` | `[data-fs-my-account-profile-phone="<field>"]` | `flex flex-col gap-1` |
192
+ | `.TaxIdControl` | `[data-fs-my-account-profile-tax-id]`; the locked input also carries `[data-fs-my-account-profile-tax-id-readonly]` | `flex flex-col gap-1` / disabled-input treatment |
193
+ | `.BirthDateControl` | `[data-fs-my-account-profile-birth-date]` | `flex flex-col gap-1` |
194
+ | `.GenderControl` | `[data-fs-my-account-profile-gender]`; its clear button carries `[data-fs-my-account-profile-gender-clear]` | `flex flex-col gap-1` |
195
+
196
+ ---
197
+
198
+ ## Known gaps
199
+
200
+ - **The server's generic "invalid value" error carries no field key.** A blocked save whose failure came back from the schema, rather than from a registered `validate`, renders in the one error region with nothing to highlight and nowhere to move focus. Improving this needs an API-side change, tracked outside this task.
201
+
202
+ ## Related
203
+
204
+ - [[session]] — provides the identity tri-state and the required `SessionRoot` ancestor.
205
+ - [[client-profile-form]] — the closest precedent for the draft-plus-dirty-plus-batched-save mechanics and the raw-fragment/hand-typed-`$data` pattern this Root follows (Decision 8).
206
+ - [[identity-edit]] — the closest precedent for the mode-lives-in-state, mode-is-which-component toggle model this Root follows (Decision 2); its context is scoped to checkout and is not reused here.
207
+ - [[sign-in-form]] — composed alongside this Root's `SessionGuard` sibling for the signed-out audience; not this task's file.
208
+ - [[toast]] — required `ToastProvider` ancestor; Save's success/error feedback surfaces through it (Decision 10, SFS-3366).
@@ -31,14 +31,21 @@ Exactly one `SessionRoot` per rendered tree. A layout-hosted provider already co
31
31
  - **For profile field values** (name, email, document, phone) — those are separate components, not derived from session state.
32
32
  - As a generic layout wrapper — that is `Container`.
33
33
 
34
- ## Layout hosting and the server-side fetch
34
+ ## Layout hosting vs. page hosting
35
35
 
36
- Hosting `SessionRoot` in a layout is safe, and safer than page hosting. The two scopes fetch differently:
36
+ Layout and page scope still fetch differently, even though both now see the shopper's cookie
37
+ ([`specs/ssr-cookie-forwarding-fetch.md`](../../../../../specs/ssr-cookie-forwarding-fetch.md)):
37
38
 
38
- - **Layout scope** emits `LAYOUT_QUERY` inside a client component with no SSR seed — no `serverVariables`, no `initialData`. The read happens in the browser, where the shopper's cookie is present.
39
- - **Page scope** emits a server-side `fetchPageData` and seeds the reactive root with `initialData`. That server fetch carries no user cookie, so the seed is an anonymous session — and the root then reads `cache-first` against the cache it just seeded, so that anonymous session is what the page keeps. It does not correct itself.
39
+ - **Layout scope** emits `LAYOUT_QUERY` inside a client component with no SSR seed — no `serverVariables`, no
40
+ `initialData`. The read happens once in the browser and is reused from cache across client-side navigation.
41
+ - **Page scope** emits a server-side `fetchPageData` and seeds the reactive root with `initialData`, resolved
42
+ fresh on every request that page serves.
40
43
 
41
- So the cookie hazard belongs to page hosting, not layout hosting. Cost is per layout, not per element: `LAYOUT_QUERY` is composed from the component types used in the layout, with no notion of tree position, so the provider's depth does not change what is fetched.
44
+ The reason to prefer layout hosting for anything shell-wide (a header entry point, any identity-conditional
45
+ content outside the page slot) is not fetch correctness anymore — it is that a page-scoped provider only covers
46
+ that one page, so a need that spans every page would otherwise mean re-authoring the same provider per page
47
+ (ADR-0004). Cost is per layout, not per element: `LAYOUT_QUERY` is composed from the component types used in the
48
+ layout, with no notion of tree position, so the provider's depth does not change what is fetched.
42
49
 
43
50
  ---
44
51
 
@@ -107,7 +114,6 @@ Pages served by this layout add no `SessionRoot` of their own; their session con
107
114
  ## Common mistakes
108
115
 
109
116
  - **Inventing `Session` (without `Root`).** The provider is `SessionRoot`.
110
- - **Avoiding the shared layout out of a cookie concern.** Layout scope is client-only and has the cookie; it is page scope that fetches server-side without one.
111
117
  - **Keeping a page-level `SessionRoot` when the layout already hosts one.** That nests two reactive roots — the inner one re-fetches and shadows the outer context.
112
118
  - **Placing `SessionRoot` beside the `Slot` instead of above it.** A sibling provider still serves the consumers in its own branch, so the header keeps working while every consumer under the `Slot` fails; it must be an ancestor of both the header subtree and the `Slot`.
113
119
  - **Placing `SessionGuard` outside a `SessionRoot`.** It reads `useSession()` and throws at render time; the schema compiler also rejects it for missing the `'session'` context ancestor.
@@ -0,0 +1,137 @@
1
+ # SignInRedirect
2
+
3
+ **Category:** identity
4
+ **Purpose:** The whole of the `/login` route — hands the document over to the account's platform login, carrying the shopper's way back.
5
+
6
+ ---
7
+
8
+ ## Visual cues (how to identify in an image)
9
+
10
+ **None, deliberately.** This component has no visual identity and must never be selected from a screenshot. It is a route's behavior rather than a block on a page: what it draws is a waiting message that exists only for the moment before the browser leaves. A design showing a sign-in screen is showing the *platform's* login, which this stack does not render — nothing in that image maps to this component.
11
+
12
+ The image is the source of truth for layout, and this component is not in any layout.
13
+
14
+ ## When to use
15
+
16
+ - On the `/login` page, as its only element, when the store's account lives on the VTEX platform's login rather than on the storefront.
17
+
18
+ ## When NOT to use
19
+
20
+ - For a credential form the shopper fills in on the storefront → this component collects nothing; the shopper types on the platform, after the document has already left.
21
+ - For the header control that sends a shopper to sign in → use [[account-entry-point]], which produces one of the URLs that arrive here.
22
+ - For gating an account page behind a session → that is `AccountAccessGate`'s, which redirects *to* this route.
23
+ - Anywhere a shopper is meant to read the page. Anything composed beside this component is navigated away from before it can be seen.
24
+
25
+ ---
26
+
27
+ ## Composition
28
+
29
+ `SignInRedirect` is a leaf element in the PageSchema and takes no children. It owns the whole page.
30
+
31
+ ```text
32
+ SignInRedirect
33
+ ```
34
+
35
+ It requires no `SessionRoot` and declares no context: it reads nothing of the shopper's. `/login` deliberately sits outside the account layout, so a shopper who is already signed in still makes the hop.
36
+
37
+ ## Components
38
+
39
+ ### SignInRedirect
40
+
41
+ | Prop | Type | Default | Notes |
42
+ |------|------|---------|-------|
43
+ | `platformLoginUrl` | string | — | The account's platform login. An absolute URL (`https://account.example.net/api/io/login`) or a site-relative path (`/api/io/login`). The storefront's own `/login` is refused, whichever way it is spelled. Required in practice: with no usable value the component renders nothing. |
44
+ | `loadingLabel` | string | — | Text shown while the handover is pending. Absent renders the element with no text. |
45
+
46
+ ### SignInRedirectLoading
47
+
48
+ The waiting look, exported separately so it can be styled and demonstrated on its own. It is rendered by `SignInRedirect` and is not authored in a schema.
49
+
50
+ Content model (`sign-in-redirect`) fields, edited in the CMS:
51
+
52
+ | Field | Type | Required | Notes |
53
+ |-------|------|----------|-------|
54
+ | `loadingLabel` | string | no | Cleared or absent keeps the authored prop |
55
+
56
+ **The address is schema-only and unreachable from the Admin.** The content model carries the waiting message and deliberately not `platformLoginUrl`. Schema props are developer-owned and land through review; a CMS entry is editable at runtime, and the runtime channel is the one that could misroute an auth flow. Because entry fields resolve one by one, the merchant gets the string they are entitled to without the address coming with it.
57
+
58
+ **Only the return parameter crosses.** The component reads exactly one parameter from the incoming URL, `returnUrl`, and composes the destination from the authored address. Every other parameter on the incoming URL is dropped rather than forwarded into an auth URL; every parameter already on the authored address survives, because the destination is built from it.
59
+
60
+ **The return is revalidated on arrival, not trusted.** `/login` is reachable by a hand-crafted link, so the value is treated as shopper-supplied whoever produced it. A return that leaves the site, is protocol-relative, or points back at `/login` itself is dropped — never rewritten into a working one — and a bounded warning names the reason. Refusing a self-referential return is what keeps a shopper who just authenticated from being handed to the platform a second time.
61
+
62
+ **The return's form follows the address's origin.** A path alone cannot say which origin it belongs to, and the store author is the one who decides whether the receiver shares the storefront's. A same-origin address gets the site-relative path exactly as it arrived; a cross-origin one would resolve that path against its own domain, so the value is absolutized first — always against the storefront's origin, never the address's.
63
+
64
+ **The handover replaces the history entry.** It is a full document load, so the client router cache stays out of it, but it takes no place in history: pressing back from the platform's login reaches the page the shopper came from rather than bouncing off this route again.
65
+
66
+ **A misauthored address renders nothing rather than spinning.** The component has one visual state and it means "a handover is coming". If the address is absent, blank, or refused, nothing is coming, so nothing renders and a warning says why. An unauthored *message* is a different case: the address still works, so the element renders as the page's content and styling hook, with only its text absent.
67
+
68
+ ---
69
+
70
+ ## Canonical schema example
71
+
72
+ ```json
73
+ {
74
+ "login-redirect": {
75
+ "type": "SignInRedirect",
76
+ "props": {
77
+ "platformLoginUrl": "https://account.example.net/api/io/login",
78
+ "loadingLabel": "Redirecionando para o login…"
79
+ },
80
+ "style": ["$sign-in-redirect"]
81
+ }
82
+ }
83
+ ```
84
+
85
+ Bound to a CMS entry, the element authors no `loadingLabel` at all — the entry supplies it.
86
+
87
+ ## Common mistakes
88
+
89
+ - **Composing it beside anything else.** It replaces the document on mount, so a header, footer, or explanatory copy authored on the same page is never seen.
90
+ - **Placing it under the account layout or behind `AccountAccessGate`.** The gate redirects *to* this route; putting the route behind the gate closes a loop the shopper cannot leave.
91
+ - **Authoring a `mailto:` or `tel:` address.** Both are valid authored links and neither resolves to a page; the component refuses them and renders nothing.
92
+ - **Expecting it to skip the hop for a signed-in shopper.** It reads no session by design, so every visitor is handed over.
93
+ - **Authoring `$content` in the schema.** `$content` is compiler-injected from the page's content entry; naming it in `props` fails the build.
94
+ - **Authoring `returnUrl` on `platformLoginUrl`.** The shopper's return overwrites it; author the address's other parameters instead.
95
+ - **Pointing `platformLoginUrl` at the storefront's own `/login`.** That is this route handed to itself, which would reload the page forever; the address is refused instead. Another host's `/login` is the platform's page and is fine.
96
+ - **Selecting it from a screenshot.** It has no visual identity — a sign-in screen in an image is the platform's page, not this one.
97
+
98
+ ---
99
+
100
+ ## Styling
101
+
102
+ | Component | What you style | Plausible classes |
103
+ |---|---|---|
104
+ | `SignInRedirect` | The one element the route renders, which is the waiting message. Style it as a centered full-page state rather than as an inline label | `flex min-h-[420px] items-center justify-center text-[14px] font-medium text-[#6B7280]` |
105
+
106
+ That element carries both hooks: `data-fs-sign-in-redirect` is the component's identity, and `data-fs-sign-in-redirect-loading` is the waiting look. They are the same element here, so either reaches it — use the identity for the page state and the loading hook when a rule has to survive the part being rendered on its own.
107
+
108
+ ```json
109
+ {
110
+ "login-redirect": {
111
+ "type": "SignInRedirect",
112
+ "props": {
113
+ "platformLoginUrl": "/api/io/login",
114
+ "loadingLabel": "Redirecionando para o login…"
115
+ },
116
+ "style": ["flex", "min-h-[420px]", "items-center", "justify-center", "text-[14px]", "text-[#6B7280]"]
117
+ }
118
+ }
119
+ ```
120
+
121
+ A spinner beside the text is a decoration the component does not render. Draw it from the element itself, once per store, as the `SignInRedirect` base swatch in `global.json → styles`. `before:block` is what makes the pseudo-element take a width and height — an inline one ignores both:
122
+
123
+ ```json
124
+ {
125
+ "SignInRedirect": {
126
+ "base": ["before:block", "before:h-8", "before:w-8", "before:animate-spin", "before:rounded-full", "before:border-2"]
127
+ }
128
+ }
129
+ ```
130
+
131
+ ---
132
+
133
+ ## Related
134
+
135
+ - [[account-entry-point]] — the header control that produces one of this route's inbound URLs, encoding the `returnUrl` this component revalidates. It is what a design showing a "Sign In" control actually calls for.
136
+ - [[navigation-link]] — an ordinary link the shopper chooses to follow, for any destination they read on arrival. This route is passed through, never read.
137
+ - `AccountAccessGate` — the other producer of inbound URLs, redirecting an unauthenticated visitor here. Not in the library yet; until it ships, the header control is this route's only sender.
@@ -89,7 +89,7 @@ export declare function AccessCodeEmailControl({ label, placeholder, invalidEmai
89
89
  * inline via a host-supplied, per-code message; the step never changes on error, so the shopper can
90
90
  * retry or abandon the method and continue as a guest.
91
91
  */
92
- export declare function AccessCodeRequestControl({ label, pendingLabel, sendLimitMessage, blockedMessage, networkErrorMessage, onRequested, $actions, className, ...props }: AccessCodeRequestControlProps): import("react/jsx-runtime").JSX.Element | null;
92
+ export declare function AccessCodeRequestControl({ label, pendingLabel, sendLimitMessage, blockedMessage, networkErrorMessage, onRequested, $actions: $actionsProp, className, ...props }: AccessCodeRequestControlProps): import("react/jsx-runtime").JSX.Element | null;
93
93
  /**
94
94
  * Renders only while the Root's stepper is at `step === 'validate'`. Composes `AccessCodeField` for
95
95
  * the code value and a submit control, disabled below `codeLength` (a UX courtesy, never a
@@ -100,7 +100,7 @@ export declare function AccessCodeRequestControl({ label, pendingLabel, sendLimi
100
100
  * no local "success" UI — `useSession()` flipping to `loggedIn` is what makes the whole Root
101
101
  * disappear.
102
102
  */
103
- export declare function AccessCodeValidateControl({ label, placeholder, submitLabel, codeLength, invalidCodeMessage, blockedMessage, missingAuthContextMessage, networkErrorMessage, onValidated, $actions, className, ...props }: AccessCodeValidateControlProps): import("react/jsx-runtime").JSX.Element | null;
103
+ export declare function AccessCodeValidateControl({ label, placeholder, submitLabel, codeLength, invalidCodeMessage, blockedMessage, missingAuthContextMessage, networkErrorMessage, onValidated, $actions: $actionsProp, className, ...props }: AccessCodeValidateControlProps): import("react/jsx-runtime").JSX.Element | null;
104
104
  export declare const AccessCodeSignIn: {
105
105
  Root: typeof AccessCodeSignInRoot;
106
106
  Email: typeof AccessCodeEmailControl;
@@ -3,6 +3,7 @@ import { defineComponent } from "../../core/dist/define-component.js";
3
3
  import "../../core/dist/index.js";
4
4
  import { AccessCodeField } from "../../particles/access-code-field/access-code-field.js";
5
5
  import { EmailField, isValidEmail } from "../../particles/email-field/email-field.js";
6
+ import { useActionsOverride } from "../../sdk/actions/actions-override-context.js";
6
7
  import { useDataOverride } from "../../sdk/data/data-override-context.js";
7
8
  import { useSession } from "../session/session.js";
8
9
  import { createContext, useCallback, useContext, useEffect, useMemo, useState } from "react";
@@ -250,7 +251,8 @@ function AccessCodeEmailControl({ label, placeholder, invalidEmailMessage, class
250
251
  * inline via a host-supplied, per-code message; the step never changes on error, so the shopper can
251
252
  * retry or abandon the method and continue as a guest.
252
253
  */
253
- function AccessCodeRequestControl({ label, pendingLabel, sendLimitMessage, blockedMessage, networkErrorMessage, onRequested, $actions, className, ...props }) {
254
+ function AccessCodeRequestControl({ label, pendingLabel, sendLimitMessage, blockedMessage, networkErrorMessage, onRequested, $actions: $actionsProp, className, ...props }) {
255
+ const $actions = useActionsOverride($actionsProp);
254
256
  const { step, email, startValidation } = useAccessCodeSignIn();
255
257
  const [pending, setPending] = useState(false);
256
258
  const [error, setError] = useState(null);
@@ -311,7 +313,8 @@ function AccessCodeRequestControl({ label, pendingLabel, sendLimitMessage, block
311
313
  * no local "success" UI — `useSession()` flipping to `loggedIn` is what makes the whole Root
312
314
  * disappear.
313
315
  */
314
- function AccessCodeValidateControl({ label, placeholder, submitLabel, codeLength = 6, invalidCodeMessage, blockedMessage, missingAuthContextMessage, networkErrorMessage, onValidated, $actions, className, ...props }) {
316
+ function AccessCodeValidateControl({ label, placeholder, submitLabel, codeLength = 6, invalidCodeMessage, blockedMessage, missingAuthContextMessage, networkErrorMessage, onValidated, $actions: $actionsProp, className, ...props }) {
317
+ const $actions = useActionsOverride($actionsProp);
315
318
  const { step, email, authenticationToken } = useAccessCodeSignIn();
316
319
  const [code, setCode] = useState("");
317
320
  const [pending, setPending] = useState(false);
@@ -1 +1 @@
1
- {"version":3,"file":"access-code-sign-in.js","names":[],"sources":["../../../src/matter/access-code-sign-in/access-code-sign-in.tsx"],"sourcesContent":["'use client';\n\nimport { defineComponent } from '@vtex/fs-core';\nimport type { ComponentPropsWithRef, FormEvent } from 'react';\nimport { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';\nimport { AccessCodeField } from '../../particles/access-code-field/access-code-field.js';\nimport { EmailField, isValidEmail } from '../../particles/email-field/email-field.js';\nimport type { ComponentActions } from '../../sdk/actions/types.js';\nimport { useDataOverride } from '../../sdk/data/data-override-context.js';\nimport type { SessionUserError } from '../../sdk/session/auth-operations.js';\nimport { useSession } from '../session/session.js';\n\n// ---------------------------------------------------------------------------\n// GraphQL Fragment (colocated, ADR-0011) — clientProfile.email seeds the draft email as a\n// convenience pre-fill for an already-identified shopper; sessionAuthProviders.accessKeyAuthentication\n// is the account gate. Authored as a raw string, mirroring EmailIdentificationControl/ShippingSimulator.\n// ---------------------------------------------------------------------------\n\nconst fragment = /* GraphQL */ `\n fragment AccessCodeSignInData on Query {\n clientProfile {\n __typename\n id\n email\n }\n sessionAuthProviders {\n accessKeyAuthentication\n }\n }\n`;\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Colocated fragment slice forwarded by the reactive runtime — `null` until both fields resolve. */\nexport type AccessCodeSignInSlice = {\n clientProfile: { email: string | null } | null;\n sessionAuthProviders: { accessKeyAuthentication: boolean } | null;\n};\n\nexport type AccessCodeSignInStep = 'request' | 'validate';\n\nexport type AccessCodeSignInContextValue = Readonly<{\n /** The shopper's locally-typed draft email — never written to the cart; only ever an argument to\n * requestAccessCode/validateAccessCode. Pre-fills from `clientProfile.email` when known, but a\n * shopper edit is never persisted anywhere by this family. */\n email: string;\n /** Updates the draft email; marks it as shopper-touched so pre-fill no longer overwrites it. */\n setEmail: (email: string) => void;\n step: AccessCodeSignInStep;\n /** Opaque token round-tripped from requestAccessCode into validateAccessCode. */\n authenticationToken: string | null;\n /** Called by AccessCodeRequestControl on a successful requestAccessCode. */\n startValidation: (authenticationToken: string) => void;\n}>;\n\nexport type AccessCodeSignInRootProps = ComponentPropsWithRef<'div'> & {\n $data?: AccessCodeSignInSlice | null;\n};\n\nexport type AccessCodeEmailControlProps = ComponentPropsWithRef<'div'> & {\n label?: string;\n placeholder?: string;\n invalidEmailMessage?: string;\n};\n\nexport type AccessCodeRequestControlProps = ComponentPropsWithRef<'div'> & {\n label?: string;\n pendingLabel?: string;\n sendLimitMessage?: string;\n blockedMessage?: string;\n networkErrorMessage?: string;\n onRequested?: (authenticationToken: string) => void;\n $actions?: ComponentActions;\n};\n\nexport type AccessCodeValidateControlProps = ComponentPropsWithRef<'div'> & {\n label?: string;\n placeholder?: string;\n submitLabel?: string;\n codeLength?: number;\n invalidCodeMessage?: string;\n blockedMessage?: string;\n missingAuthContextMessage?: string;\n networkErrorMessage?: string;\n onValidated?: () => void;\n $actions?: ComponentActions;\n};\n\n// ---------------------------------------------------------------------------\n// Context\n// ---------------------------------------------------------------------------\n\nconst ACCESS_CODE_SIGN_IN_CONTEXT_UNSET = Symbol('AccessCodeSignInContextUnset');\n\nconst AccessCodeSignInContext = createContext<AccessCodeSignInContextValue | typeof ACCESS_CODE_SIGN_IN_CONTEXT_UNSET>(\n ACCESS_CODE_SIGN_IN_CONTEXT_UNSET,\n);\n\n/**\n * Read the stepper state (email, step, authenticationToken) from the nearest `AccessCodeSignInRoot`.\n * Throws when called outside that subtree — a misplaced control is a composition bug that should\n * surface immediately, mirroring `useShippingSimulator()`/`useSession()`.\n */\nfunction useAccessCodeSignIn(): AccessCodeSignInContextValue {\n const ctx = useContext(AccessCodeSignInContext);\n if (ctx === ACCESS_CODE_SIGN_IN_CONTEXT_UNSET) {\n throw new Error('AccessCodeSignIn components must be used inside AccessCodeSignInRoot.');\n }\n return ctx;\n}\n\n// ---------------------------------------------------------------------------\n// Error-code → message mapping — every failure surfaces inline, at the control that produced it;\n// an unmapped code falls back to the server-supplied message rather than being silent.\n// ---------------------------------------------------------------------------\n\nfunction requestErrorMessage(\n error: SessionUserError,\n props: Pick<AccessCodeRequestControlProps, 'sendLimitMessage' | 'blockedMessage' | 'networkErrorMessage'>,\n): string {\n switch (error.code) {\n case 'ACCESS_CODE_SEND_LIMIT':\n return props.sendLimitMessage ?? error.message;\n case 'BLOCKED':\n return props.blockedMessage ?? error.message;\n case 'NETWORK_ERROR':\n return props.networkErrorMessage ?? error.message;\n default:\n return error.message;\n }\n}\n\nfunction validateErrorMessage(\n error: SessionUserError,\n props: Pick<\n AccessCodeValidateControlProps,\n 'invalidCodeMessage' | 'blockedMessage' | 'missingAuthContextMessage' | 'networkErrorMessage'\n >,\n): string {\n switch (error.code) {\n case 'INVALID_ACCESS_CODE':\n return props.invalidCodeMessage ?? error.message;\n case 'BLOCKED':\n return props.blockedMessage ?? error.message;\n case 'MISSING_AUTH_CONTEXT':\n return props.missingAuthContextMessage ?? error.message;\n case 'NETWORK_ERROR':\n return props.networkErrorMessage ?? error.message;\n default:\n return error.message;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Definitions\n// ---------------------------------------------------------------------------\n\nexport const AccessCodeSignInRootDefinition = defineComponent<AccessCodeSignInRootProps>({\n name: 'AccessCodeSignInRoot',\n importPath: '@vtex/faststore/components',\n component: AccessCodeSignInRoot,\n fragment,\n fetchStrategy: 'reactive',\n requiresContext: 'session',\n editor: { label: 'Access Code Sign-In', category: 'identity' },\n schema: {},\n guideline:\n 'Self-gating root of the anonymous-or-identified, email + access-code sign-in: renders nothing once loggedIn or when the account disables accessKeyAuthentication, but is available from the anonymous state onward. Owns a locally-typed draft email (never written to the cart) and the request/validate stepper. Must be inside SessionRoot. See access-code-sign-in.md.',\n});\n\nexport const AccessCodeEmailControlDefinition = defineComponent<AccessCodeEmailControlProps>({\n name: 'AccessCodeEmailControl',\n importPath: '@vtex/faststore/components',\n component: AccessCodeEmailControl,\n editor: { label: 'Access Code Email', category: 'identity' },\n schema: {\n label: { type: 'string', label: 'Label' },\n placeholder: { type: 'string', label: 'Placeholder' },\n invalidEmailMessage: { type: 'string', label: 'Invalid email message' },\n },\n guideline:\n 'The draft email input for the access-code flow: visible only at the request step, pre-fills from clientProfile.email when known but never persists an edit anywhere — it is purely the argument requestAccessCode/validateAccessCode dispatch with. Must be inside AccessCodeSignInRoot. See access-code-sign-in.md.',\n});\n\nexport const AccessCodeRequestControlDefinition = defineComponent<AccessCodeRequestControlProps>({\n name: 'AccessCodeRequestControl',\n importPath: '@vtex/faststore/components',\n component: AccessCodeRequestControl,\n actions: { session: ['requestAccessCode'] },\n editor: { label: 'Access Code Request', category: 'identity' },\n schema: {\n label: { type: 'string', label: 'Label' },\n sendLimitMessage: { type: 'string', label: 'Send-limit message' },\n blockedMessage: { type: 'string', label: 'Blocked message' },\n networkErrorMessage: { type: 'string', label: 'Network error message' },\n },\n guideline:\n 'Send-code step: disabled until the Root-owned draft email is a valid, non-empty address; dispatches $actions.session.requestAccessCode with it and advances the stepper on success. Failures render inline, never as a blocking error. Must be inside AccessCodeSignInRoot. See access-code-sign-in.md.',\n});\n\nexport const AccessCodeValidateControlDefinition = defineComponent<AccessCodeValidateControlProps>({\n name: 'AccessCodeValidateControl',\n importPath: '@vtex/faststore/components',\n component: AccessCodeValidateControl,\n actions: { session: ['validateAccessCode'] },\n editor: { label: 'Access Code Validate', category: 'identity' },\n schema: {\n label: { type: 'string', label: 'Label' },\n placeholder: { type: 'string', label: 'Placeholder' },\n submitLabel: { type: 'string', label: 'Submit label' },\n codeLength: { type: 'number', default: 6, label: 'Code length' },\n invalidCodeMessage: { type: 'string', label: 'Invalid code message' },\n blockedMessage: { type: 'string', label: 'Blocked message' },\n networkErrorMessage: { type: 'string', label: 'Network error message' },\n },\n guideline:\n 'Code-entry step: composes AccessCodeField and dispatches $actions.session.validateAccessCode with the Root-owned email and authenticationToken; a successful validate flips the session reactively and hides the Root. Must be inside AccessCodeSignInRoot. See access-code-sign-in.md.',\n});\n\n// ---------------------------------------------------------------------------\n// Implementations\n// ---------------------------------------------------------------------------\n\n/**\n * Data-owning root of the access-code sign-in flow. Self-gates on two signals:\n * `useSession().state !== 'loggedIn'` (available from anonymous through identified; hides once\n * signed in) and `sessionAuthProviders.accessKeyAuthentication === true`. An unresolved fragment\n * read (`$data` still `null`) is treated as \"provider not enabled\" — the flow stays hidden rather\n * than flashing. Owns the `step`/`authenticationToken` stepper plus a locally-typed draft `email`\n * as local state, broadcasting all of it through context to its children. The draft email pre-fills\n * from `clientProfile.email` once (for an already-identified shopper) but a shopper edit is never\n * persisted anywhere by this family — it is purely the requestAccessCode/validateAccessCode\n * argument; the server sets clientProfileData.email as a side effect of a successful validate.\n * Reacts automatically once `useSession()` flips to `loggedIn` — no manual unmount call anywhere in\n * this family.\n */\nexport function AccessCodeSignInRoot({ $data: $dataProp, className, children, ...props }: AccessCodeSignInRootProps) {\n const $data = useDataOverride($dataProp);\n const { state } = useSession();\n\n const [step, setStep] = useState<AccessCodeSignInStep>('request');\n const [authenticationToken, setAuthenticationToken] = useState<string | null>(null);\n\n const startValidation = useCallback((token: string) => {\n setAuthenticationToken(token);\n setStep('validate');\n }, []);\n\n const savedEmail = $data?.clientProfile?.email ?? '';\n const [email, setEmailState] = useState('');\n const [touched, setTouched] = useState(false);\n\n // Pre-fill from the resolving/known profile email, but only while still at the request step and\n // until the shopper edits the draft — mirrors ClientProfileForm's untouched-field re-seed, never\n // clobbering an in-progress edit. Freezing the re-seed once the flow leaves 'request' keeps a\n // mid-flow profile-cache change from diverging the email between requestAccessCode and\n // validateAccessCode (the server rejects a validate whose email differs from the request).\n useEffect(() => {\n if (step === 'request' && !touched) setEmailState(savedEmail);\n }, [savedEmail, touched, step]);\n\n const setEmail = useCallback((next: string) => {\n setTouched(true);\n setEmailState(next);\n }, []);\n\n const providerEnabled = $data?.sessionAuthProviders?.accessKeyAuthentication ?? false;\n\n const contextValue = useMemo<AccessCodeSignInContextValue>(\n () => ({ email, setEmail, step, authenticationToken, startValidation }),\n [email, setEmail, step, authenticationToken, startValidation],\n );\n\n const gated = state !== 'loggedIn' && providerEnabled;\n if (!gated) return null;\n\n return (\n <AccessCodeSignInContext value={contextValue}>\n <div {...props} className={className} data-fs-access-code-sign-in-root=\"\">\n {children}\n </div>\n </AccessCodeSignInContext>\n );\n}\n\n/**\n * The draft email input for the access-code flow. Renders only at the request step. Bound to the\n * Root's local `email`/`setEmail` — never a fragment, never a mutation of its own; typing here\n * never touches the cart. Format validation is `EmailField`'s own blur-triggered check (never\n * per-keystroke); `invalidEmailMessage` is only its fallback text, mirroring how\n * `EmailIdentificationControl` defers to the same particle for the same reason.\n */\nexport function AccessCodeEmailControl({\n label,\n placeholder,\n invalidEmailMessage,\n className,\n ...props\n}: AccessCodeEmailControlProps) {\n const { step, email, setEmail } = useAccessCodeSignIn();\n\n if (step !== 'request') return null;\n\n return (\n <div {...props} className={className} data-fs-access-code-email-control=\"\">\n <EmailField\n label={label}\n placeholder={placeholder}\n value={email}\n onValueChange={setEmail}\n errorMessage={invalidEmailMessage}\n />\n </div>\n );\n}\n\n/**\n * Renders only while the Root's stepper is at `step === 'request'`. Reads the draft email from\n * context and dispatches `$actions.session.requestAccessCode({ email })` once it is a valid,\n * non-empty address (no account-existence pre-check beyond that format gate). On a result with a\n * non-null token and no `userErrors`, advances the Root's stepper. Every `userErrors` entry renders\n * inline via a host-supplied, per-code message; the step never changes on error, so the shopper can\n * retry or abandon the method and continue as a guest.\n */\nexport function AccessCodeRequestControl({\n label,\n pendingLabel,\n sendLimitMessage,\n blockedMessage,\n networkErrorMessage,\n onRequested,\n $actions,\n className,\n ...props\n}: AccessCodeRequestControlProps) {\n const { step, email, startValidation } = useAccessCodeSignIn();\n const [pending, setPending] = useState(false);\n const [error, setError] = useState<SessionUserError | null>(null);\n\n if (step !== 'request') return null;\n\n const canSubmit = !pending && isValidEmail(email);\n\n async function handleClick() {\n if (!canSubmit) return;\n setPending(true);\n setError(null);\n\n try {\n const result = (await $actions?.session?.requestAccessCode?.({ email })) ?? {\n authenticationToken: null,\n userErrors: [],\n };\n\n if (result.authenticationToken && result.userErrors.length === 0) {\n startValidation(result.authenticationToken);\n onRequested?.(result.authenticationToken);\n return;\n }\n\n const [firstError] = result.userErrors;\n if (firstError) setError(firstError);\n } finally {\n setPending(false);\n }\n }\n\n const message = error ? requestErrorMessage(error, { sendLimitMessage, blockedMessage, networkErrorMessage }) : null;\n\n return (\n <div\n {...props}\n className={className}\n data-fs-access-code-request-control=\"\"\n {...(pending ? { 'data-fs-access-code-request-pending': '' } : {})}\n >\n <button\n type=\"button\"\n onClick={handleClick}\n disabled={!canSubmit}\n aria-busy={pending || undefined}\n data-fs-access-code-request-submit=\"\"\n data-fs-button=\"\"\n >\n {pending ? (pendingLabel ?? label) : label}\n </button>\n {message ? (\n <div role=\"alert\" data-fs-access-code-request-error=\"\">\n {message}\n </div>\n ) : null}\n </div>\n );\n}\n\n/**\n * Renders only while the Root's stepper is at `step === 'validate'`. Composes `AccessCodeField` for\n * the code value and a submit control, disabled below `codeLength` (a UX courtesy, never a\n * validity claim). Dispatches `$actions.session.validateAccessCode` with the email and\n * `authenticationToken` read from context. On a non-empty `userErrors` result, sets the field's\n * `invalid`/`errorMessage` from a host-supplied per-code message and retains the code/token so the\n * shopper can retry without re-requesting. On success it calls the optional `onValidated` and renders\n * no local \"success\" UI — `useSession()` flipping to `loggedIn` is what makes the whole Root\n * disappear.\n */\nexport function AccessCodeValidateControl({\n label,\n placeholder,\n submitLabel,\n codeLength = 6,\n invalidCodeMessage,\n blockedMessage,\n missingAuthContextMessage,\n networkErrorMessage,\n onValidated,\n $actions,\n className,\n ...props\n}: AccessCodeValidateControlProps) {\n const { step, email, authenticationToken } = useAccessCodeSignIn();\n const [code, setCode] = useState('');\n const [pending, setPending] = useState(false);\n const [error, setError] = useState<SessionUserError | null>(null);\n\n if (step !== 'validate') return null;\n\n const canSubmit = !pending && code.length >= codeLength && authenticationToken != null;\n\n async function handleSubmit(event: FormEvent<HTMLFormElement>) {\n event.preventDefault();\n if (!canSubmit || authenticationToken == null) return;\n\n setPending(true);\n setError(null);\n\n try {\n const errors =\n (await $actions?.session?.validateAccessCode?.({ email, accessCode: code, authenticationToken })) ?? [];\n\n const [firstError] = errors;\n if (firstError) {\n setError(firstError);\n return;\n }\n } finally {\n setPending(false);\n }\n\n onValidated?.();\n }\n\n function handleCodeChange(next: string) {\n setCode(next);\n if (error) setError(null);\n }\n\n const errorMessage = error\n ? validateErrorMessage(error, {\n invalidCodeMessage,\n blockedMessage,\n missingAuthContextMessage,\n networkErrorMessage,\n })\n : undefined;\n\n return (\n <div\n {...props}\n className={className}\n data-fs-access-code-validate-control=\"\"\n {...(pending ? { 'data-fs-access-code-validate-pending': '' } : {})}\n >\n <form onSubmit={handleSubmit}>\n <AccessCodeField\n label={label}\n placeholder={placeholder}\n value={code}\n maxLength={codeLength}\n onValueChange={handleCodeChange}\n disabled={pending}\n invalid={error != null}\n errorMessage={errorMessage}\n />\n <button\n type=\"submit\"\n disabled={!canSubmit}\n aria-busy={pending || undefined}\n data-fs-access-code-validate-submit=\"\"\n data-fs-button=\"\"\n >\n {submitLabel}\n </button>\n </form>\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Namespace export\n// ---------------------------------------------------------------------------\n\nexport const AccessCodeSignIn = {\n Root: AccessCodeSignInRoot,\n Email: AccessCodeEmailControl,\n Request: AccessCodeRequestControl,\n Validate: AccessCodeValidateControl,\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"access-code-sign-in.js","names":[],"sources":["../../../src/matter/access-code-sign-in/access-code-sign-in.tsx"],"sourcesContent":["'use client';\n\nimport { defineComponent } from '@vtex/fs-core';\nimport type { ComponentPropsWithRef, FormEvent } from 'react';\nimport { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';\nimport { AccessCodeField } from '../../particles/access-code-field/access-code-field.js';\nimport { EmailField, isValidEmail } from '../../particles/email-field/email-field.js';\nimport { useActionsOverride } from '../../sdk/actions/actions-override-context.js';\nimport type { ComponentActions } from '../../sdk/actions/types.js';\nimport { useDataOverride } from '../../sdk/data/data-override-context.js';\nimport type { SessionUserError } from '../../sdk/session/auth-operations.js';\nimport { useSession } from '../session/session.js';\n\n// ---------------------------------------------------------------------------\n// GraphQL Fragment (colocated, ADR-0011) — clientProfile.email seeds the draft email as a\n// convenience pre-fill for an already-identified shopper; sessionAuthProviders.accessKeyAuthentication\n// is the account gate. Authored as a raw string, mirroring EmailIdentificationControl/ShippingSimulator.\n// ---------------------------------------------------------------------------\n\nconst fragment = /* GraphQL */ `\n fragment AccessCodeSignInData on Query {\n clientProfile {\n __typename\n id\n email\n }\n sessionAuthProviders {\n accessKeyAuthentication\n }\n }\n`;\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Colocated fragment slice forwarded by the reactive runtime — `null` until both fields resolve. */\nexport type AccessCodeSignInSlice = {\n clientProfile: { email: string | null } | null;\n sessionAuthProviders: { accessKeyAuthentication: boolean } | null;\n};\n\nexport type AccessCodeSignInStep = 'request' | 'validate';\n\nexport type AccessCodeSignInContextValue = Readonly<{\n /** The shopper's locally-typed draft email — never written to the cart; only ever an argument to\n * requestAccessCode/validateAccessCode. Pre-fills from `clientProfile.email` when known, but a\n * shopper edit is never persisted anywhere by this family. */\n email: string;\n /** Updates the draft email; marks it as shopper-touched so pre-fill no longer overwrites it. */\n setEmail: (email: string) => void;\n step: AccessCodeSignInStep;\n /** Opaque token round-tripped from requestAccessCode into validateAccessCode. */\n authenticationToken: string | null;\n /** Called by AccessCodeRequestControl on a successful requestAccessCode. */\n startValidation: (authenticationToken: string) => void;\n}>;\n\nexport type AccessCodeSignInRootProps = ComponentPropsWithRef<'div'> & {\n $data?: AccessCodeSignInSlice | null;\n};\n\nexport type AccessCodeEmailControlProps = ComponentPropsWithRef<'div'> & {\n label?: string;\n placeholder?: string;\n invalidEmailMessage?: string;\n};\n\nexport type AccessCodeRequestControlProps = ComponentPropsWithRef<'div'> & {\n label?: string;\n pendingLabel?: string;\n sendLimitMessage?: string;\n blockedMessage?: string;\n networkErrorMessage?: string;\n onRequested?: (authenticationToken: string) => void;\n $actions?: ComponentActions;\n};\n\nexport type AccessCodeValidateControlProps = ComponentPropsWithRef<'div'> & {\n label?: string;\n placeholder?: string;\n submitLabel?: string;\n codeLength?: number;\n invalidCodeMessage?: string;\n blockedMessage?: string;\n missingAuthContextMessage?: string;\n networkErrorMessage?: string;\n onValidated?: () => void;\n $actions?: ComponentActions;\n};\n\n// ---------------------------------------------------------------------------\n// Context\n// ---------------------------------------------------------------------------\n\nconst ACCESS_CODE_SIGN_IN_CONTEXT_UNSET = Symbol('AccessCodeSignInContextUnset');\n\nconst AccessCodeSignInContext = createContext<AccessCodeSignInContextValue | typeof ACCESS_CODE_SIGN_IN_CONTEXT_UNSET>(\n ACCESS_CODE_SIGN_IN_CONTEXT_UNSET,\n);\n\n/**\n * Read the stepper state (email, step, authenticationToken) from the nearest `AccessCodeSignInRoot`.\n * Throws when called outside that subtree — a misplaced control is a composition bug that should\n * surface immediately, mirroring `useShippingSimulator()`/`useSession()`.\n */\nfunction useAccessCodeSignIn(): AccessCodeSignInContextValue {\n const ctx = useContext(AccessCodeSignInContext);\n if (ctx === ACCESS_CODE_SIGN_IN_CONTEXT_UNSET) {\n throw new Error('AccessCodeSignIn components must be used inside AccessCodeSignInRoot.');\n }\n return ctx;\n}\n\n// ---------------------------------------------------------------------------\n// Error-code → message mapping — every failure surfaces inline, at the control that produced it;\n// an unmapped code falls back to the server-supplied message rather than being silent.\n// ---------------------------------------------------------------------------\n\nfunction requestErrorMessage(\n error: SessionUserError,\n props: Pick<AccessCodeRequestControlProps, 'sendLimitMessage' | 'blockedMessage' | 'networkErrorMessage'>,\n): string {\n switch (error.code) {\n case 'ACCESS_CODE_SEND_LIMIT':\n return props.sendLimitMessage ?? error.message;\n case 'BLOCKED':\n return props.blockedMessage ?? error.message;\n case 'NETWORK_ERROR':\n return props.networkErrorMessage ?? error.message;\n default:\n return error.message;\n }\n}\n\nfunction validateErrorMessage(\n error: SessionUserError,\n props: Pick<\n AccessCodeValidateControlProps,\n 'invalidCodeMessage' | 'blockedMessage' | 'missingAuthContextMessage' | 'networkErrorMessage'\n >,\n): string {\n switch (error.code) {\n case 'INVALID_ACCESS_CODE':\n return props.invalidCodeMessage ?? error.message;\n case 'BLOCKED':\n return props.blockedMessage ?? error.message;\n case 'MISSING_AUTH_CONTEXT':\n return props.missingAuthContextMessage ?? error.message;\n case 'NETWORK_ERROR':\n return props.networkErrorMessage ?? error.message;\n default:\n return error.message;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Definitions\n// ---------------------------------------------------------------------------\n\nexport const AccessCodeSignInRootDefinition = defineComponent<AccessCodeSignInRootProps>({\n name: 'AccessCodeSignInRoot',\n importPath: '@vtex/faststore/components',\n component: AccessCodeSignInRoot,\n fragment,\n fetchStrategy: 'reactive',\n requiresContext: 'session',\n editor: { label: 'Access Code Sign-In', category: 'identity' },\n schema: {},\n guideline:\n 'Self-gating root of the anonymous-or-identified, email + access-code sign-in: renders nothing once loggedIn or when the account disables accessKeyAuthentication, but is available from the anonymous state onward. Owns a locally-typed draft email (never written to the cart) and the request/validate stepper. Must be inside SessionRoot. See access-code-sign-in.md.',\n});\n\nexport const AccessCodeEmailControlDefinition = defineComponent<AccessCodeEmailControlProps>({\n name: 'AccessCodeEmailControl',\n importPath: '@vtex/faststore/components',\n component: AccessCodeEmailControl,\n editor: { label: 'Access Code Email', category: 'identity' },\n schema: {\n label: { type: 'string', label: 'Label' },\n placeholder: { type: 'string', label: 'Placeholder' },\n invalidEmailMessage: { type: 'string', label: 'Invalid email message' },\n },\n guideline:\n 'The draft email input for the access-code flow: visible only at the request step, pre-fills from clientProfile.email when known but never persists an edit anywhere — it is purely the argument requestAccessCode/validateAccessCode dispatch with. Must be inside AccessCodeSignInRoot. See access-code-sign-in.md.',\n});\n\nexport const AccessCodeRequestControlDefinition = defineComponent<AccessCodeRequestControlProps>({\n name: 'AccessCodeRequestControl',\n importPath: '@vtex/faststore/components',\n component: AccessCodeRequestControl,\n actions: { session: ['requestAccessCode'] },\n editor: { label: 'Access Code Request', category: 'identity' },\n schema: {\n label: { type: 'string', label: 'Label' },\n sendLimitMessage: { type: 'string', label: 'Send-limit message' },\n blockedMessage: { type: 'string', label: 'Blocked message' },\n networkErrorMessage: { type: 'string', label: 'Network error message' },\n },\n guideline:\n 'Send-code step: disabled until the Root-owned draft email is a valid, non-empty address; dispatches $actions.session.requestAccessCode with it and advances the stepper on success. Failures render inline, never as a blocking error. Must be inside AccessCodeSignInRoot. See access-code-sign-in.md.',\n});\n\nexport const AccessCodeValidateControlDefinition = defineComponent<AccessCodeValidateControlProps>({\n name: 'AccessCodeValidateControl',\n importPath: '@vtex/faststore/components',\n component: AccessCodeValidateControl,\n actions: { session: ['validateAccessCode'] },\n editor: { label: 'Access Code Validate', category: 'identity' },\n schema: {\n label: { type: 'string', label: 'Label' },\n placeholder: { type: 'string', label: 'Placeholder' },\n submitLabel: { type: 'string', label: 'Submit label' },\n codeLength: { type: 'number', default: 6, label: 'Code length' },\n invalidCodeMessage: { type: 'string', label: 'Invalid code message' },\n blockedMessage: { type: 'string', label: 'Blocked message' },\n networkErrorMessage: { type: 'string', label: 'Network error message' },\n },\n guideline:\n 'Code-entry step: composes AccessCodeField and dispatches $actions.session.validateAccessCode with the Root-owned email and authenticationToken; a successful validate flips the session reactively and hides the Root. Must be inside AccessCodeSignInRoot. See access-code-sign-in.md.',\n});\n\n// ---------------------------------------------------------------------------\n// Implementations\n// ---------------------------------------------------------------------------\n\n/**\n * Data-owning root of the access-code sign-in flow. Self-gates on two signals:\n * `useSession().state !== 'loggedIn'` (available from anonymous through identified; hides once\n * signed in) and `sessionAuthProviders.accessKeyAuthentication === true`. An unresolved fragment\n * read (`$data` still `null`) is treated as \"provider not enabled\" — the flow stays hidden rather\n * than flashing. Owns the `step`/`authenticationToken` stepper plus a locally-typed draft `email`\n * as local state, broadcasting all of it through context to its children. The draft email pre-fills\n * from `clientProfile.email` once (for an already-identified shopper) but a shopper edit is never\n * persisted anywhere by this family — it is purely the requestAccessCode/validateAccessCode\n * argument; the server sets clientProfileData.email as a side effect of a successful validate.\n * Reacts automatically once `useSession()` flips to `loggedIn` — no manual unmount call anywhere in\n * this family.\n */\nexport function AccessCodeSignInRoot({ $data: $dataProp, className, children, ...props }: AccessCodeSignInRootProps) {\n const $data = useDataOverride($dataProp);\n const { state } = useSession();\n\n const [step, setStep] = useState<AccessCodeSignInStep>('request');\n const [authenticationToken, setAuthenticationToken] = useState<string | null>(null);\n\n const startValidation = useCallback((token: string) => {\n setAuthenticationToken(token);\n setStep('validate');\n }, []);\n\n const savedEmail = $data?.clientProfile?.email ?? '';\n const [email, setEmailState] = useState('');\n const [touched, setTouched] = useState(false);\n\n // Pre-fill from the resolving/known profile email, but only while still at the request step and\n // until the shopper edits the draft — mirrors ClientProfileForm's untouched-field re-seed, never\n // clobbering an in-progress edit. Freezing the re-seed once the flow leaves 'request' keeps a\n // mid-flow profile-cache change from diverging the email between requestAccessCode and\n // validateAccessCode (the server rejects a validate whose email differs from the request).\n useEffect(() => {\n if (step === 'request' && !touched) setEmailState(savedEmail);\n }, [savedEmail, touched, step]);\n\n const setEmail = useCallback((next: string) => {\n setTouched(true);\n setEmailState(next);\n }, []);\n\n const providerEnabled = $data?.sessionAuthProviders?.accessKeyAuthentication ?? false;\n\n const contextValue = useMemo<AccessCodeSignInContextValue>(\n () => ({ email, setEmail, step, authenticationToken, startValidation }),\n [email, setEmail, step, authenticationToken, startValidation],\n );\n\n const gated = state !== 'loggedIn' && providerEnabled;\n if (!gated) return null;\n\n return (\n <AccessCodeSignInContext value={contextValue}>\n <div {...props} className={className} data-fs-access-code-sign-in-root=\"\">\n {children}\n </div>\n </AccessCodeSignInContext>\n );\n}\n\n/**\n * The draft email input for the access-code flow. Renders only at the request step. Bound to the\n * Root's local `email`/`setEmail` — never a fragment, never a mutation of its own; typing here\n * never touches the cart. Format validation is `EmailField`'s own blur-triggered check (never\n * per-keystroke); `invalidEmailMessage` is only its fallback text, mirroring how\n * `EmailIdentificationControl` defers to the same particle for the same reason.\n */\nexport function AccessCodeEmailControl({\n label,\n placeholder,\n invalidEmailMessage,\n className,\n ...props\n}: AccessCodeEmailControlProps) {\n const { step, email, setEmail } = useAccessCodeSignIn();\n\n if (step !== 'request') return null;\n\n return (\n <div {...props} className={className} data-fs-access-code-email-control=\"\">\n <EmailField\n label={label}\n placeholder={placeholder}\n value={email}\n onValueChange={setEmail}\n errorMessage={invalidEmailMessage}\n />\n </div>\n );\n}\n\n/**\n * Renders only while the Root's stepper is at `step === 'request'`. Reads the draft email from\n * context and dispatches `$actions.session.requestAccessCode({ email })` once it is a valid,\n * non-empty address (no account-existence pre-check beyond that format gate). On a result with a\n * non-null token and no `userErrors`, advances the Root's stepper. Every `userErrors` entry renders\n * inline via a host-supplied, per-code message; the step never changes on error, so the shopper can\n * retry or abandon the method and continue as a guest.\n */\nexport function AccessCodeRequestControl({\n label,\n pendingLabel,\n sendLimitMessage,\n blockedMessage,\n networkErrorMessage,\n onRequested,\n $actions: $actionsProp,\n className,\n ...props\n}: AccessCodeRequestControlProps) {\n const $actions = useActionsOverride($actionsProp);\n const { step, email, startValidation } = useAccessCodeSignIn();\n const [pending, setPending] = useState(false);\n const [error, setError] = useState<SessionUserError | null>(null);\n\n if (step !== 'request') return null;\n\n const canSubmit = !pending && isValidEmail(email);\n\n async function handleClick() {\n if (!canSubmit) return;\n setPending(true);\n setError(null);\n\n try {\n const result = (await $actions?.session?.requestAccessCode?.({ email })) ?? {\n authenticationToken: null,\n userErrors: [],\n };\n\n if (result.authenticationToken && result.userErrors.length === 0) {\n startValidation(result.authenticationToken);\n onRequested?.(result.authenticationToken);\n return;\n }\n\n const [firstError] = result.userErrors;\n if (firstError) setError(firstError);\n } finally {\n setPending(false);\n }\n }\n\n const message = error ? requestErrorMessage(error, { sendLimitMessage, blockedMessage, networkErrorMessage }) : null;\n\n return (\n <div\n {...props}\n className={className}\n data-fs-access-code-request-control=\"\"\n {...(pending ? { 'data-fs-access-code-request-pending': '' } : {})}\n >\n <button\n type=\"button\"\n onClick={handleClick}\n disabled={!canSubmit}\n aria-busy={pending || undefined}\n data-fs-access-code-request-submit=\"\"\n data-fs-button=\"\"\n >\n {pending ? (pendingLabel ?? label) : label}\n </button>\n {message ? (\n <div role=\"alert\" data-fs-access-code-request-error=\"\">\n {message}\n </div>\n ) : null}\n </div>\n );\n}\n\n/**\n * Renders only while the Root's stepper is at `step === 'validate'`. Composes `AccessCodeField` for\n * the code value and a submit control, disabled below `codeLength` (a UX courtesy, never a\n * validity claim). Dispatches `$actions.session.validateAccessCode` with the email and\n * `authenticationToken` read from context. On a non-empty `userErrors` result, sets the field's\n * `invalid`/`errorMessage` from a host-supplied per-code message and retains the code/token so the\n * shopper can retry without re-requesting. On success it calls the optional `onValidated` and renders\n * no local \"success\" UI — `useSession()` flipping to `loggedIn` is what makes the whole Root\n * disappear.\n */\nexport function AccessCodeValidateControl({\n label,\n placeholder,\n submitLabel,\n codeLength = 6,\n invalidCodeMessage,\n blockedMessage,\n missingAuthContextMessage,\n networkErrorMessage,\n onValidated,\n $actions: $actionsProp,\n className,\n ...props\n}: AccessCodeValidateControlProps) {\n const $actions = useActionsOverride($actionsProp);\n const { step, email, authenticationToken } = useAccessCodeSignIn();\n const [code, setCode] = useState('');\n const [pending, setPending] = useState(false);\n const [error, setError] = useState<SessionUserError | null>(null);\n\n if (step !== 'validate') return null;\n\n const canSubmit = !pending && code.length >= codeLength && authenticationToken != null;\n\n async function handleSubmit(event: FormEvent<HTMLFormElement>) {\n event.preventDefault();\n if (!canSubmit || authenticationToken == null) return;\n\n setPending(true);\n setError(null);\n\n try {\n const errors =\n (await $actions?.session?.validateAccessCode?.({ email, accessCode: code, authenticationToken })) ?? [];\n\n const [firstError] = errors;\n if (firstError) {\n setError(firstError);\n return;\n }\n } finally {\n setPending(false);\n }\n\n onValidated?.();\n }\n\n function handleCodeChange(next: string) {\n setCode(next);\n if (error) setError(null);\n }\n\n const errorMessage = error\n ? validateErrorMessage(error, {\n invalidCodeMessage,\n blockedMessage,\n missingAuthContextMessage,\n networkErrorMessage,\n })\n : undefined;\n\n return (\n <div\n {...props}\n className={className}\n data-fs-access-code-validate-control=\"\"\n {...(pending ? { 'data-fs-access-code-validate-pending': '' } : {})}\n >\n <form onSubmit={handleSubmit}>\n <AccessCodeField\n label={label}\n placeholder={placeholder}\n value={code}\n maxLength={codeLength}\n onValueChange={handleCodeChange}\n disabled={pending}\n invalid={error != null}\n errorMessage={errorMessage}\n />\n <button\n type=\"submit\"\n disabled={!canSubmit}\n aria-busy={pending || undefined}\n data-fs-access-code-validate-submit=\"\"\n data-fs-button=\"\"\n >\n {submitLabel}\n </button>\n </form>\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Namespace export\n// ---------------------------------------------------------------------------\n\nexport const AccessCodeSignIn = {\n Root: AccessCodeSignInRoot,\n Email: AccessCodeEmailControl,\n Request: AccessCodeRequestControl,\n Validate: AccessCodeValidateControl,\n};\n"],"mappings":"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"}
@@ -84,7 +84,7 @@ export declare function BuyButtonRoot({ $data, selectedVariantId, children, ...p
84
84
  export declare namespace BuyButtonRoot {
85
85
  var Skeleton: typeof BuyButtonSkeleton;
86
86
  }
87
- export declare function BuyButtonButton({ availableLabel, unavailableLabel, className, children, onClick, $content, $actions, ...props }: BuyButtonButtonProps): import("react/jsx-runtime").JSX.Element | null;
87
+ export declare function BuyButtonButton({ availableLabel, unavailableLabel, className, children, onClick, $content, $actions: $actionsProp, ...props }: BuyButtonButtonProps): import("react/jsx-runtime").JSX.Element | null;
88
88
  export declare function BuyButtonSkeleton({ className, ...props }: ComponentPropsWithRef<'div'>): import("react/jsx-runtime").JSX.Element;
89
89
  export declare const BuyButton: {
90
90
  Root: typeof BuyButtonRoot;
@@ -3,6 +3,7 @@ import { defineComponent } from "../../core/dist/define-component.js";
3
3
  import { defineContentType } from "../../core/dist/define-content-type.js";
4
4
  import "../../core/dist/index.js";
5
5
  import { graphql } from "../../sdk/generated/gql.js";
6
+ import { useActionsOverride } from "../../sdk/actions/actions-override-context.js";
6
7
  import { useDataOverride } from "../../sdk/data/data-override-context.js";
7
8
  import { Button } from "../../particles/button/button.js";
8
9
  import { HotDataProvider } from "../../sdk/data/hot-data-provider.js";
@@ -206,7 +207,8 @@ function BuyButtonView({ contextValue, className, children, ...props }) {
206
207
  })
207
208
  });
208
209
  }
209
- function BuyButtonButton({ availableLabel = "Comprar", unavailableLabel = "Indisponivel", className, children, onClick, $content, $actions, ...props }) {
210
+ function BuyButtonButton({ availableLabel = "Comprar", unavailableLabel = "Indisponivel", className, children, onClick, $content, $actions: $actionsProp, ...props }) {
211
+ const $actions = useActionsOverride($actionsProp);
210
212
  const ctx = useBuyButton();
211
213
  const [addItemPending, setAddItemPending] = useState(false);
212
214
  const { quantity } = useProductQuantity();