@vtex/faststore 0.0.5 → 0.0.7
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.
- package/CHANGELOG.md +101 -0
- package/dist/actions.json +181 -0
- package/dist/catalog.json +1673 -484
- package/dist/cli/UnreachableCaseError-Crd168Vu.js +10 -0
- package/dist/cli/UnreachableCaseError-Crd168Vu.js.map +1 -0
- package/dist/cli/cacheControl-CEojH_TM.js +158 -0
- package/dist/cli/cacheControl-CEojH_TM.js.map +1 -0
- package/dist/cli/cachePolicy-m2vASMRW.js +26 -0
- package/dist/cli/cachePolicy-m2vASMRW.js.map +1 -0
- package/dist/cli/computeCoreSchemaHash-DPJZj-qJ.js +10 -0
- package/dist/cli/computeCoreSchemaHash-DPJZj-qJ.js.map +1 -0
- package/dist/cli/default-BCQHcwbL.js +253 -0
- package/dist/cli/default-BCQHcwbL.js.map +1 -0
- package/dist/cli/disableSuggestions-yO9UqiWn.js +21 -0
- package/dist/cli/disableSuggestions-yO9UqiWn.js.map +1 -0
- package/dist/cli/dist-C1JhdHfo.js +2001 -0
- package/dist/cli/dist-C1JhdHfo.js.map +1 -0
- package/dist/cli/graphql-DG9thCDG.js +14589 -0
- package/dist/cli/graphql-DG9thCDG.js.map +1 -0
- package/dist/cli/index.d.ts +350 -0
- package/dist/cli/index.js +35047 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/index.min-BsIHDfFK.js +613 -0
- package/dist/cli/index.min-BsIHDfFK.js.map +1 -0
- package/dist/cli/inlineTrace-uLgOJ3TH.js +52 -0
- package/dist/cli/inlineTrace-uLgOJ3TH.js.map +1 -0
- package/dist/cli/internalPlugin-DnuwN4_4.js +11 -0
- package/dist/cli/internalPlugin-DnuwN4_4.js.map +1 -0
- package/dist/cli/packageVersion-DQAnIEil.js +6 -0
- package/dist/cli/packageVersion-DQAnIEil.js.map +1 -0
- package/dist/cli/rolldown-runtime-BMI-E3GI.js +44 -0
- package/dist/cli/schemaIsSubgraph-DoGh3OYj.js +17 -0
- package/dist/cli/schemaIsSubgraph-DoGh3OYj.js.map +1 -0
- package/dist/cli/schemaReporting-K-me-3-v.js +199 -0
- package/dist/cli/schemaReporting-K-me-3-v.js.map +1 -0
- package/dist/cli/server-runtime.js +49859 -0
- package/dist/cli/server-runtime.js.map +1 -0
- package/dist/cli/traceTreeBuilder-BIcV2U7b.js +9118 -0
- package/dist/cli/traceTreeBuilder-BIcV2U7b.js.map +1 -0
- package/dist/cli/usageReporting-Bpl8vSZX.js +10201 -0
- package/dist/cli/usageReporting-Bpl8vSZX.js.map +1 -0
- package/dist/cli/v4-CBhFvC49.js +42 -0
- package/dist/cli/v4-CBhFvC49.js.map +1 -0
- package/dist/client.d.ts +2 -1
- package/dist/client.js +2 -1
- package/dist/components.d.ts +32 -9
- package/dist/components.js +35 -14
- package/dist/content-types.json +93 -0
- package/dist/core/dist/define-component.d.ts +37 -0
- package/dist/core/dist/index.d.ts +2 -2
- package/dist/docs/access-code-sign-in.md +1 -1
- package/dist/docs/account-entry-point.md +1 -1
- package/dist/docs/account-menu-identity.md +160 -0
- package/dist/docs/account-menu.md +106 -23
- package/dist/docs/address-summary.md +24 -3
- package/dist/docs/alert-dialog.md +204 -0
- package/dist/docs/avatar.md +103 -0
- package/dist/docs/billing-address.md +284 -0
- package/dist/docs/card-entry.md +250 -0
- package/dist/docs/cardholder-document.md +142 -0
- package/dist/docs/client-preferences-fieldset.md +148 -0
- package/dist/docs/client-preferences.md +2 -1
- package/dist/docs/client-profile-fieldset.md +184 -0
- package/dist/docs/client-profile.md +1 -1
- package/dist/docs/document-field.md +58 -4
- package/dist/docs/field.md +317 -0
- package/dist/docs/form.md +307 -0
- package/dist/docs/identity-edit.md +6 -6
- package/dist/docs/identity-summary.md +5 -5
- package/dist/docs/installments.md +248 -0
- package/dist/docs/my-account-profile.md +2 -1
- package/dist/docs/order-placed-payment-summary.md +138 -0
- package/dist/docs/payment-method-badges.md +232 -0
- package/dist/docs/payment-methods.md +1 -0
- package/dist/docs/payment.md +218 -0
- package/dist/docs/phone-field.md +58 -4
- package/dist/docs/place-order.md +157 -0
- package/dist/docs/product-shelf.md +20 -13
- package/dist/docs/save-card.md +186 -0
- package/dist/docs/saved-cards.md +218 -0
- package/dist/docs/select.md +465 -0
- package/dist/docs/shipping-address.md +110 -258
- package/dist/index.d.ts +5 -2
- package/dist/internal/client.d.ts +3 -1
- package/dist/internal/client.js +4 -2
- package/dist/internal/content.d.ts +2 -2
- package/dist/internal/server.d.ts +4 -1
- package/dist/internal/server.js +4 -1
- package/dist/matter/access-code-sign-in/access-code-sign-in.js +9 -8
- package/dist/matter/access-code-sign-in/access-code-sign-in.js.map +1 -1
- package/dist/matter/account-access-gate/account-access-gate.js.map +1 -1
- package/dist/matter/account-entry-point/account-entry-point.js +1 -1
- package/dist/matter/account-entry-point/account-entry-point.js.map +1 -1
- package/dist/matter/account-menu/account-menu.d.ts +70 -0
- package/dist/matter/account-menu/account-menu.js +201 -9
- package/dist/matter/account-menu/account-menu.js.map +1 -1
- package/dist/matter/account-menu/sign-out-failure.js +50 -0
- package/dist/matter/account-menu/sign-out-failure.js.map +1 -0
- package/dist/matter/account-menu-identity/account-menu-identity.d.ts +21 -0
- package/dist/matter/account-menu-identity/account-menu-identity.js +88 -0
- package/dist/matter/account-menu-identity/account-menu-identity.js.map +1 -0
- package/dist/matter/billing-address/billing-address.d.ts +165 -0
- package/dist/matter/billing-address/billing-address.js +408 -0
- package/dist/matter/billing-address/billing-address.js.map +1 -0
- package/dist/matter/card-entry/card-capture.d.ts +25 -0
- package/dist/matter/card-entry/card-capture.js +25 -0
- package/dist/matter/card-entry/card-capture.js.map +1 -0
- package/dist/matter/card-entry/card-entry.d.ts +120 -0
- package/dist/matter/card-entry/card-entry.js +833 -0
- package/dist/matter/card-entry/card-entry.js.map +1 -0
- package/dist/matter/cardholder-document/cardholder-document.d.ts +45 -0
- package/dist/matter/cardholder-document/cardholder-document.js +70 -0
- package/dist/matter/cardholder-document/cardholder-document.js.map +1 -0
- package/dist/matter/cart/cart.js +1 -1
- package/dist/matter/client-preferences/client-preferences-fieldset.d.ts +77 -0
- package/dist/matter/client-preferences/client-preferences-fieldset.js +299 -0
- package/dist/matter/client-preferences/client-preferences-fieldset.js.map +1 -0
- package/dist/matter/client-preferences/client-preferences.d.ts +3 -5
- package/dist/matter/client-preferences/client-preferences.js +6 -6
- package/dist/matter/client-preferences/client-preferences.js.map +1 -1
- package/dist/matter/client-profile/client-profile.d.ts +4 -6
- package/dist/matter/client-profile/client-profile.js +9 -6
- package/dist/matter/client-profile/client-profile.js.map +1 -1
- package/dist/matter/client-profile-form/client-profile-fieldset.d.ts +119 -0
- package/dist/matter/client-profile-form/client-profile-fieldset.js +488 -0
- package/dist/matter/client-profile-form/client-profile-fieldset.js.map +1 -0
- package/dist/matter/identity-edit/identity-edit.js +2 -2
- package/dist/matter/identity-edit/identity-edit.js.map +1 -1
- package/dist/matter/identity-summary/identity-summary.js +3 -2
- package/dist/matter/identity-summary/identity-summary.js.map +1 -1
- package/dist/matter/installments/installments-select.d.ts +42 -0
- package/dist/matter/installments/installments-select.js +167 -0
- package/dist/matter/installments/installments-select.js.map +1 -0
- package/dist/matter/installments/installments.d.ts +58 -0
- package/dist/matter/installments/installments.js +176 -0
- package/dist/matter/installments/installments.js.map +1 -0
- package/dist/matter/installments/use-installments-selection.d.ts +29 -0
- package/dist/matter/installments/use-installments-selection.js +95 -0
- package/dist/matter/installments/use-installments-selection.js.map +1 -0
- package/dist/matter/my-account-profile/my-account-profile.d.ts +3 -0
- package/dist/matter/my-account-profile/my-account-profile.js +6 -5
- package/dist/matter/my-account-profile/my-account-profile.js.map +1 -1
- package/dist/matter/order-placed-payment-summary/order-placed-payment-summary.d.ts +63 -0
- package/dist/matter/order-placed-payment-summary/order-placed-payment-summary.js +305 -0
- package/dist/matter/order-placed-payment-summary/order-placed-payment-summary.js.map +1 -0
- package/dist/matter/payment/payment-card-capture.d.ts +19 -0
- package/dist/matter/payment/payment-card-capture.js +129 -0
- package/dist/matter/payment/payment-card-capture.js.map +1 -0
- package/dist/matter/payment/payment-controller.d.ts +24 -0
- package/dist/matter/payment/payment-controller.js +108 -0
- package/dist/matter/payment/payment-controller.js.map +1 -0
- package/dist/matter/payment/payment-method-badges.d.ts +57 -0
- package/dist/matter/payment/payment-method-badges.js +192 -0
- package/dist/matter/payment/payment-method-badges.js.map +1 -0
- package/dist/matter/payment/payment.d.ts +89 -0
- package/dist/matter/payment/payment.js +264 -0
- package/dist/matter/payment/payment.js.map +1 -0
- package/dist/matter/payment/use-payment-method-selection.d.ts +302 -0
- package/dist/matter/payment/use-payment-method-selection.js +458 -0
- package/dist/matter/payment/use-payment-method-selection.js.map +1 -0
- package/dist/matter/payment/use-payment-submit-gate.d.ts +111 -0
- package/dist/matter/payment/use-payment-submit-gate.js +144 -0
- package/dist/matter/payment/use-payment-submit-gate.js.map +1 -0
- package/dist/matter/place-order/place-order.d.ts +74 -0
- package/dist/matter/place-order/place-order.js +379 -0
- package/dist/matter/place-order/place-order.js.map +1 -0
- package/dist/matter/product-list/product-list.d.ts +1 -0
- package/dist/matter/product-list/product-list.js.map +1 -1
- package/dist/matter/product-shelf/product-shelf.d.ts +62 -31
- package/dist/matter/product-shelf/product-shelf.js +159 -57
- package/dist/matter/product-shelf/product-shelf.js.map +1 -1
- package/dist/matter/region/region.js +1 -1
- package/dist/matter/region/region.js.map +1 -1
- package/dist/matter/save-card/save-card.d.ts +49 -0
- package/dist/matter/save-card/save-card.js +188 -0
- package/dist/matter/save-card/save-card.js.map +1 -0
- package/dist/matter/saved-cards/saved-cards.d.ts +79 -0
- package/dist/matter/saved-cards/saved-cards.js +388 -0
- package/dist/matter/saved-cards/saved-cards.js.map +1 -0
- package/dist/matter/shipping-address/country-descriptors.js +20 -10
- package/dist/matter/shipping-address/country-descriptors.js.map +1 -1
- package/dist/matter/shipping-address/shipping-address.d.ts +113 -174
- package/dist/matter/shipping-address/shipping-address.js +480 -791
- package/dist/matter/shipping-address/shipping-address.js.map +1 -1
- package/dist/matter/variant-selector/variant-selector.js +1 -1
- package/dist/particles/address-summary/address-summary.d.ts +24 -0
- package/dist/particles/address-summary/address-summary.js +83 -2
- package/dist/particles/address-summary/address-summary.js.map +1 -1
- package/dist/particles/alert-dialog/alert-dialog.d.ts +68 -0
- package/dist/particles/alert-dialog/alert-dialog.js +187 -0
- package/dist/particles/alert-dialog/alert-dialog.js.map +1 -0
- package/dist/particles/avatar/avatar.d.ts +17 -0
- package/dist/particles/avatar/avatar.js +65 -0
- package/dist/particles/avatar/avatar.js.map +1 -0
- package/dist/particles/checkbox/checkbox.d.ts +7 -3
- package/dist/particles/checkbox/checkbox.js +4 -1
- package/dist/particles/checkbox/checkbox.js.map +1 -1
- package/dist/particles/document-field/document-field.d.ts +48 -5
- package/dist/particles/document-field/document-field.js +189 -32
- package/dist/particles/document-field/document-field.js.map +1 -1
- package/dist/particles/field/bound-field-registry.js +52 -0
- package/dist/particles/field/bound-field-registry.js.map +1 -0
- package/dist/particles/field/field.d.ts +116 -0
- package/dist/particles/field/field.js +497 -6
- package/dist/particles/field/field.js.map +1 -1
- package/dist/particles/fieldset/fieldset.js +2 -2
- package/dist/particles/fieldset/fieldset.js.map +1 -1
- package/dist/particles/form/form.d.ts +104 -0
- package/dist/particles/form/form.js +485 -0
- package/dist/particles/form/form.js.map +1 -0
- package/dist/particles/phone-field/phone-field.d.ts +38 -10
- package/dist/particles/phone-field/phone-field.js +183 -39
- package/dist/particles/phone-field/phone-field.js.map +1 -1
- package/dist/particles/postal-code-field/postal-code-field.js +2 -2
- package/dist/particles/postal-code-field/postal-code-field.js.map +1 -1
- package/dist/particles/select/field-select.d.ts +43 -0
- package/dist/particles/select/field-select.js +150 -0
- package/dist/particles/select/field-select.js.map +1 -0
- package/dist/particles/select/select.d.ts +2 -0
- package/dist/particles/select/select.js.map +1 -1
- package/dist/particles/shared/compose-refs.js +21 -0
- package/dist/particles/shared/compose-refs.js.map +1 -0
- package/dist/schema.graphql +766 -28
- package/dist/sdk/actions/analytics-action-provider.d.ts +15 -0
- package/dist/sdk/actions/analytics-action-provider.js +19 -0
- package/dist/sdk/actions/analytics-action-provider.js.map +1 -0
- package/dist/sdk/actions/index.d.ts +3 -1
- package/dist/sdk/actions/index.js +3 -1
- package/dist/sdk/actions/payment-action-provider.d.ts +22 -0
- package/dist/sdk/actions/payment-action-provider.js +26 -0
- package/dist/sdk/actions/payment-action-provider.js.map +1 -0
- package/dist/sdk/actions/types.d.ts +6 -2
- package/dist/sdk/analytics/analytics-destination.d.ts +43 -0
- package/dist/sdk/analytics/analytics-destination.js +90 -0
- package/dist/sdk/analytics/analytics-destination.js.map +1 -0
- package/dist/sdk/analytics/use-analytics-actions.d.ts +57 -0
- package/dist/sdk/analytics/use-analytics-actions.js +109 -0
- package/dist/sdk/analytics/use-analytics-actions.js.map +1 -0
- package/dist/sdk/cart/use-cart-actions.js +10 -9
- package/dist/sdk/cart/use-cart-actions.js.map +1 -1
- package/dist/sdk/cart/use-cart-validate-on-open.js +2 -1
- package/dist/sdk/cart/use-cart-validate-on-open.js.map +1 -1
- package/dist/sdk/content/create-content-runtime.d.ts +22 -1
- package/dist/sdk/content/create-content-runtime.js +89 -1
- package/dist/sdk/content/create-content-runtime.js.map +1 -1
- package/dist/sdk/data/empty-content-value.js +16 -0
- package/dist/sdk/data/empty-content-value.js.map +1 -0
- package/dist/sdk/data/entity-resolution-context.js +31 -0
- package/dist/sdk/data/entity-resolution-context.js.map +1 -0
- package/dist/sdk/data/entity-subscriber.js +18 -8
- package/dist/sdk/data/entity-subscriber.js.map +1 -1
- package/dist/sdk/data/hot-data-provider.js +2 -1
- package/dist/sdk/data/hot-data-provider.js.map +1 -1
- package/dist/sdk/data/index.js +2 -1
- package/dist/sdk/data/page-reactive-data-root.d.ts +36 -0
- package/dist/sdk/data/page-reactive-data-root.js +4 -2
- package/dist/sdk/data/page-reactive-data-root.js.map +1 -1
- package/dist/sdk/data/reactive-data-provider.js +7 -3
- package/dist/sdk/data/reactive-data-provider.js.map +1 -1
- package/dist/sdk/data/reactive-list-item-data-provider.js +12 -4
- package/dist/sdk/data/reactive-list-item-data-provider.js.map +1 -1
- package/dist/sdk/data/use-reactive-data-settled.js +39 -0
- package/dist/sdk/data/use-reactive-data-settled.js.map +1 -0
- package/dist/sdk/data/use-reactive-entity.js +5 -1
- package/dist/sdk/data/use-reactive-entity.js.map +1 -1
- package/dist/sdk/data/variables-from-content.d.ts +43 -0
- package/dist/sdk/data/variables-from-content.js +57 -0
- package/dist/sdk/data/variables-from-content.js.map +1 -0
- package/dist/sdk/generated/fragment-types.js +2 -0
- package/dist/sdk/generated/fragment-types.js.map +1 -1
- package/dist/sdk/generated/gql.js +2 -1
- package/dist/sdk/generated/gql.js.map +1 -1
- package/dist/sdk/generated/graphql.js +85 -1
- package/dist/sdk/generated/graphql.js.map +1 -1
- package/dist/sdk/i18n/messages/en-US.js +3 -0
- package/dist/sdk/i18n/messages/en-US.json +3 -0
- package/dist/sdk/i18n/messages/en-US.json.d.ts +3 -0
- package/dist/sdk/i18n/messages/es-ES.js +3 -0
- package/dist/sdk/i18n/messages/es-ES.json +3 -0
- package/dist/sdk/i18n/messages/pt-BR.js +3 -0
- package/dist/sdk/i18n/messages/pt-BR.json +3 -0
- package/dist/sdk/identity/use-identity-actions.js +8 -6
- package/dist/sdk/identity/use-identity-actions.js.map +1 -1
- package/dist/sdk/payment/gateway-origin.js +35 -0
- package/dist/sdk/payment/gateway-origin.js.map +1 -0
- package/dist/sdk/payment/gateway.d.ts +85 -0
- package/dist/sdk/payment/gateway.js +183 -0
- package/dist/sdk/payment/gateway.js.map +1 -0
- package/dist/sdk/payment/operations.d.ts +52 -0
- package/dist/sdk/payment/operations.js +223 -0
- package/dist/sdk/payment/operations.js.map +1 -0
- package/dist/sdk/payment/security-code-mask.js +71 -0
- package/dist/sdk/payment/security-code-mask.js.map +1 -0
- package/dist/sdk/payment/submission-registry.d.ts +33 -0
- package/dist/sdk/payment/submission-registry.js +58 -0
- package/dist/sdk/payment/submission-registry.js.map +1 -0
- package/dist/sdk/payment/use-payment-actions.d.ts +78 -0
- package/dist/sdk/payment/use-payment-actions.js +276 -0
- package/dist/sdk/payment/use-payment-actions.js.map +1 -0
- package/dist/sdk/payment/use-place-order.d.ts +65 -0
- package/dist/sdk/payment/use-place-order.js +640 -0
- package/dist/sdk/payment/use-place-order.js.map +1 -0
- package/dist/sdk/region/use-region-actions.js +2 -1
- package/dist/sdk/region/use-region-actions.js.map +1 -1
- package/dist/sdk/session/read-server-session.d.ts +21 -0
- package/dist/sdk/session/read-server-session.js +61 -0
- package/dist/sdk/session/read-server-session.js.map +1 -0
- package/dist/sdk/session/use-session-actions.js +5 -4
- package/dist/sdk/session/use-session-actions.js.map +1 -1
- package/dist/sdk/session/use-session-revalidation.js +2 -1
- package/dist/sdk/session/use-session-revalidation.js.map +1 -1
- package/dist/sdk/shipping/use-shipping-actions.js +4 -3
- package/dist/sdk/shipping/use-shipping-actions.js.map +1 -1
- package/dist/utils/loggable-error-name.js +49 -0
- package/dist/utils/loggable-error-name.js.map +1 -0
- package/dist/utils/resolve-authored-copy.js +18 -0
- package/dist/utils/resolve-authored-copy.js.map +1 -0
- package/dist/utils/safe-href.d.ts +11 -0
- package/dist/utils/substitute-template.js +33 -0
- package/dist/utils/substitute-template.js.map +1 -0
- package/package.json +14 -2
- package/src/styles/tailwind.css +34 -0
- package/dist/docs/client-profile-form.md +0 -192
- package/dist/matter/client-profile-form/client-profile-form.d.ts +0 -126
- package/dist/matter/client-profile-form/client-profile-form.js +0 -662
- package/dist/matter/client-profile-form/client-profile-form.js.map +0 -1
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# PlaceOrder
|
|
2
|
+
|
|
3
|
+
**Category:** commerce
|
|
4
|
+
**Purpose:** The checkout's place-order CTA. Runs the three-call payment sequence (start transaction, the holder's direct-to-gateway submission, gateway callback), blocks interaction while it runs, and renders one inline outcome — an error message or a success confirmation — once it settles.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Visual cues (how to identify in an image)
|
|
9
|
+
|
|
10
|
+
A PlaceOrder block is present when you see:
|
|
11
|
+
|
|
12
|
+
- A **primary CTA button** at the bottom of a payment/checkout section with a label like "Place order", "Finalizar compra", "Pay now", or similar.
|
|
13
|
+
- The button may appear **greyed out with a spinner or busy indicator** and a label like "Placing order…" — that is still the same `PlaceOrder`, mid-sequence.
|
|
14
|
+
- A **confirmation line or banner below the button** reading something like "Order placed successfully" once the button has been pressed and the order succeeded — this is `PlaceOrder`'s own inline success state, not a separate order-confirmation page.
|
|
15
|
+
- An **inline error line below the button** (e.g. "Enter your payment details before placing the order.") is also part of this same block, not a separate error component.
|
|
16
|
+
|
|
17
|
+
The image is the source of truth for layout.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## When to use
|
|
22
|
+
|
|
23
|
+
- The final CTA in a credit-card checkout/payment section, once a method (and, for a new card, its fields) has been entered.
|
|
24
|
+
|
|
25
|
+
## When NOT to use
|
|
26
|
+
|
|
27
|
+
- Product-page purchase CTA (add to cart) — use [[buy-button]] instead. `PlaceOrder` is checkout-only and drives the payment sequence, not a cart write.
|
|
28
|
+
- Do **not** nest it inside the card-entry form. It must be a **sibling** of card entry and saved cards under the same payment root — nesting it inside the card form would need a second CTA for the saved-card path, which is exactly the mistake this component's placement avoids.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Composition
|
|
33
|
+
|
|
34
|
+
`PlaceOrder` is a single, self-contained component — no Root/leaf split. It must be placed as a child of `PaymentSectionRoot` or `PaymentSectionController`, as a **sibling** of `CardEntry` / `SavedCards`, never nested inside either:
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
PaymentSectionRoot (or PaymentSectionController)
|
|
38
|
+
├── PaymentMethodList
|
|
39
|
+
├── CardEntry (new-card path)
|
|
40
|
+
├── SavedCards (saved-card path)
|
|
41
|
+
└── PlaceOrder ← sibling, not a child of CardEntry/SavedCards
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Child order reflects visual order. `PlaceOrder` composes under **either** payment root with no change — both provide the same `payment` context and the same submission channel (ADR-0009).
|
|
45
|
+
|
|
46
|
+
`PlaceOrder` never reads or holds a card field. It calls `usePlaceOrder`, the T13 orchestration hook, which resolves a continuation that `CardEntry`/`SavedCards` registered on a submission channel by the shopper's entry path — this component only knows there is a hook to call, a loading state to block on, and an outcome to render. It also reads `usePayment().draft.billingAddressDifferent` (T11) and forwards it into `usePlaceOrder`, so the gateway descriptor carries whether the shopper picked a different billing address — the one other fact this component touches, and still not a card field.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Components
|
|
51
|
+
|
|
52
|
+
### PlaceOrder
|
|
53
|
+
|
|
54
|
+
No sub-components. Renders a button and, once the sequence has settled, one inline outcome region (error or success).
|
|
55
|
+
|
|
56
|
+
| Prop | Type | Default | Notes |
|
|
57
|
+
|------|------|---------|-------|
|
|
58
|
+
| `label` | string | `"Place order"` | Button label while idle. |
|
|
59
|
+
| `placingLabel` | string | `"Placing order..."` | Button label while the sequence is in flight. |
|
|
60
|
+
| `resumeLabel` | string | `"Complete your order"` | Button label while a surviving attempt offers a resume — the card already left the browser (or its fate is unknown) and the next click finalizes it instead of running the full sequence again. |
|
|
61
|
+
| `successMessage` | string | `"Order placed successfully."` | Shown once the sequence settles with no errors. |
|
|
62
|
+
| `missingSubmissionMessage` | string | `"Enter your payment details before placing the order."` | Shown when `placeOrder()` is invoked with no registered submission for the current path (e.g. the shopper never finished entering a card). |
|
|
63
|
+
| `missingMethodMessage` | string | `"Choose a payment method before placing the order."` | Shown when no payment method is selected yet. |
|
|
64
|
+
| `invalidTransactionMessage` | string | `"Unable to start the payment. Please try again."` | Shown when `startTransaction` resolves incomplete transaction data. |
|
|
65
|
+
| `networkErrorMessage` | string | `"Unable to complete the request. Please try again."` | Shown for an infrastructure-level failure reaching the payment gateway action. |
|
|
66
|
+
| `missingDocumentMessage` | string | `"Enter your document before placing the order."` | Shown when the selected method requires a cardholder document and none is set. |
|
|
67
|
+
| `invalidDocumentMessage` | string | `"Enter a valid document before placing the order."` | Shown when the typed cardholder document fails validation. |
|
|
68
|
+
| `unavailableMethodMessage` | string | `"Choose another payment method before placing the order."` | Shown instead of the two document messages above when the selected method calls for a cardholder document the page is not showing — a method the card form cannot pay with, or one outside the payment root's `group` — so the shopper has to choose another payment method. |
|
|
69
|
+
| `unsavedBillingAddressMessage` | string | `"Save your billing address before placing the order."` | Shown when the shopper declined "same as delivery" but never saved a custom billing address. |
|
|
70
|
+
| `invalidCardDataMessage` | string | `"Enter valid card details before placing the order."` | Shown when the new-card draft itself fails validation before ever reaching the gateway. |
|
|
71
|
+
| `invalidSecurityCodeMessage` | string | `"Enter a valid security code before placing the order."` | Shown when a saved-card submission is refused for a missing or invalid CVV. |
|
|
72
|
+
| `gatewayErrorMessage` | string | `"Unable to complete the payment. Please try again."` | Shown when the payment gateway itself refuses the submitted card. |
|
|
73
|
+
| `inFlightMessage` | string | `"This order is already being placed. Please wait."` | Shown when a click lands on an order-placement attempt whose own sequence is still running — a transaction this instance did not open, and which nothing here may finalize or re-run. |
|
|
74
|
+
| `unconfirmedMessage` | string | `"We could not confirm your order yet. Your payment may still be processing, so please do not pay again."` | Shown when the payment was handed to the gateway but the order could not be confirmed, so it may still be processing. Renders in its own `role="status"` node, never in the error alert. |
|
|
75
|
+
| `successHref` | string | *(none)* | Route **template** navigated to after a successful placement. The literal `{orderGroup}` is replaced with the placed order's group, percent-encoded. **Opt-in and success-gated**: no default is provided on purpose, so an absent prop keeps today's behavior byte-for-byte, and the navigation is requested only once an attempt settles with no errors and a non-null order group — never on a refusal. |
|
|
76
|
+
|
|
77
|
+
Every message prop above is resolved by the `code` of the client-synthesized `PaymentUserError` this CTA renders — in its alert, or in its status node for `unconfirmedMessage` — never by which component produced the error. A `PaymentUserError` whose code this component does not author (a server-returned code) renders that error's own `message` unchanged.
|
|
78
|
+
|
|
79
|
+
**Data:** declares its own thin `fragment PlaceOrderData on Query` selecting `payment.selectedMethodId`, `payment.selectedInstallment`, and `payment.methods { id groupName }` — the exact slice the orchestration hook needs, no more. **Actions:** declares `actions: { payment: ['startTransaction', 'gatewayCallback'], analytics: ['track'] }`. Must be inside a payment root (`requiresContext: 'payment'`).
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Canonical schema example
|
|
84
|
+
|
|
85
|
+
```json
|
|
86
|
+
{
|
|
87
|
+
"payment-section": {
|
|
88
|
+
"type": "PaymentSectionRoot",
|
|
89
|
+
"children": ["payment-method-list", "card-entry", "place-order"]
|
|
90
|
+
},
|
|
91
|
+
"payment-method-list": { "type": "PaymentMethodList" },
|
|
92
|
+
"card-entry": { "type": "CardEntry" },
|
|
93
|
+
"place-order": {
|
|
94
|
+
"type": "PlaceOrder",
|
|
95
|
+
"props": {
|
|
96
|
+
"label": "Place order",
|
|
97
|
+
"placingLabel": "Placing order...",
|
|
98
|
+
"successMessage": "Order placed successfully.",
|
|
99
|
+
"successHref": "/checkout/order-placed/{orderGroup}"
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
`successHref` is shown here for reference only — it is opt-in (see the props table above), and no store composes it today. It must be a path on the store's own origin (root-relative, like the example above, or an absolute URL on the same origin) — an absolute URL on a different origin, a protocol-relative URL, or a non-http(s) scheme is refused rather than navigated to.
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Common mistakes
|
|
110
|
+
|
|
111
|
+
- **Nesting `PlaceOrder` inside `CardEntry` or `SavedCards`.** It must be a sibling of both under the same payment root, so one CTA serves both the new-card and saved-card paths.
|
|
112
|
+
- **Placing `PlaceOrder` outside any `PaymentSectionRoot`/`PaymentSectionController`.** It throws at render — there is no payment context or submission channel to resolve.
|
|
113
|
+
- **Confusing it with `BuyButtonButton`.** `BuyButtonButton` adds a product to the cart on a PDP; `PlaceOrder` runs the payment sequence in checkout. They are never composed together.
|
|
114
|
+
- **Expecting a redirect or a 3D Secure challenge.** v1 is the direct-API mode only — the shopper stays on the page and the outcome renders inline. The redirect mode is phase 2.
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Known limitations
|
|
119
|
+
|
|
120
|
+
- **A remount reads the surviving attempt instead of losing it — but the attempt itself does not survive a page reload.** T29 (`specs/make-an-order-attempt-survive-a.md`) gave the place-order sequence an identity-and-phase record on the payment context, above this component's own mount, alongside `placing`. Closing and reopening a `Dialog`/`Drawer` popup with no `keepMounted` no longer resets it: a settled attempt renders disabled with no further calls (`succeeded` is derived from the record, not only from this instance's own `attempted`/`userErrors`), and an attempt whose card already reached the gateway before the remount offers a resume (`resumeLabel`) that finalizes the same transaction — never a fresh `startTransaction` or a second card submission. This closes the case this bullet used to attribute to "a settled-order read model that outlives the overlay (T16/T17)": both are merged, and the limitation still stood, because neither one ever touched this component's own mount-scoped guard, which is what actually caused the duplicate-order risk. What is still not covered: a full page reload loses the attempt (no `sessionStorage` or equivalent persists it today — T29's own OQ-3), and the direct-to-gateway POST's abort budget (30s, `sdk/payment/gateway.ts`) is an unconfirmed placeholder pending a number from payments (OQ-7).
|
|
121
|
+
- **Sibling placement (never nested inside `CardEntry`/`SavedCards`) is a convention, not something the compiler enforces.** `requiresContext: 'payment'` only fails a build with no payment-providing ancestor at any depth; it cannot detect that this component was nested one level too deep. Getting this wrong compiles clean and still leaves the other entry path with no CTA at all (see `specs/place-order.md`'s Open items).
|
|
122
|
+
- **`successHref` navigates with `router.replace`, not `router.push`.** The payment step is removed from the history stack, so the Back button from the confirmation page returns to whatever preceded payment, not to the payment step itself — deliberate (placing an order is terminal, and a live place-order CTA one Back press away risks a duplicate transaction), but a divergence from this package's other redirect-on-success component, `CartRedirectOnAdd`, which uses `push`.
|
|
123
|
+
- **A `successHref` authored without the literal `{orderGroup}` placeholder fails closed.** The component refuses to navigate rather than sending every shopper to the same unresolvable URL, and logs a `console.warn` outside production. This is a store-authoring mistake to fix in the template, not a runtime condition to handle.
|
|
124
|
+
- **A `successHref` that resolves off the store's own origin also fails closed.** An absolute URL on a different origin, a protocol-relative URL (`//host/...`), or a non-http(s) scheme is refused rather than navigated to, with the same dev-only `console.warn`. This keeps a store schema from turning the CTA into a redirect off the storefront right after a real purchase.
|
|
125
|
+
- **`successHref` is inert until a store authors it.** As of this writing no store composes it — see `specs/navigate-the-shopper-to-the-order.md` for the follow-up that wires Reserva's payment page to it, gated on the confirmation page's read path being fixed first.
|
|
126
|
+
|
|
127
|
+
## Styling
|
|
128
|
+
|
|
129
|
+
| Component | What you style | Plausible classes |
|
|
130
|
+
|---|---|---|
|
|
131
|
+
| `PlaceOrder` (root `div`) | Layout wrapper stacking the button and the outcome region | `flex flex-col gap-[8px] items-start` |
|
|
132
|
+
| Button (`data-fs-place-order-button`) | The CTA itself — full-width pill, disabled/busy states | `inline-flex items-center justify-center w-full h-[44px] px-[20px] bg-[#C8102E] text-white rounded-[4px] text-[14px] font-bold uppercase cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed` |
|
|
133
|
+
| Error (`data-fs-place-order-error`) | Inline error line, `role="alert"` | `text-[13px] text-[#C0392B]` |
|
|
134
|
+
| Unconfirmed (`data-fs-place-order-unconfirmed`) | Inline notice that the order could not be confirmed yet, `role="status"` | `text-[13px] text-[#8A6D3B]` |
|
|
135
|
+
| Success (`data-fs-place-order-success`) | Inline success line, `role="status"` | `text-[13px] text-[#1E8E3E]` |
|
|
136
|
+
| Success order id (`data-fs-place-order-success-order-id`) | The placed order's id, inside the success line | `font-mono text-[12px] text-[#666666]` |
|
|
137
|
+
|
|
138
|
+
Always include `disabled:` styling on the button — it disables automatically while the sequence runs and again once it succeeds.
|
|
139
|
+
|
|
140
|
+
Styled example:
|
|
141
|
+
|
|
142
|
+
```json
|
|
143
|
+
{
|
|
144
|
+
"place-order": {
|
|
145
|
+
"type": "PlaceOrder",
|
|
146
|
+
"style": ["flex", "flex-col", "gap-[8px]", "items-start"],
|
|
147
|
+
"props": { "label": "Place order" }
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Related
|
|
155
|
+
|
|
156
|
+
- [[payment]] — `PaymentSectionRoot` / `PaymentSectionController`, the roots `PlaceOrder` must compose under.
|
|
157
|
+
- [[buy-button]] — the PDP purchase CTA and this component's named precedent for the blocking loading-state pattern (disabled + `aria-busy` + a `-pending` hook).
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# ProductShelf
|
|
2
2
|
|
|
3
3
|
**Category:** commerce
|
|
4
|
-
**Purpose:** A data-owning product shelf. `ProductShelfRoot` owns a facet-filtered `products(filter:)` query from the catalog module, receives its slice as `$data`, and publishes each product through a per-item context. `ProductShelfItems` repeats those products into a `Carousel` track, one card per slide. The card parts (`ProductCard*`) read each product from context — the shelf holds the data and config, the carousel arranges the rows, the cards render one product each.
|
|
4
|
+
**Purpose:** A data-owning product shelf. `ProductShelfRoot` owns a facet-filtered `products(first:, orderBy:, filter:)` query from the catalog module whose arguments bind to this element's configuration (the merchant's CMS entry, else the props), receives its slice as `$data`, and publishes each product through a per-item context. `ProductShelfItems` repeats those products into a `Carousel` track, one card per slide. The card parts (`ProductCard*`) read each product from context — the shelf holds the data and config, the carousel arranges the rows, the cards render one product each.
|
|
5
5
|
|
|
6
6
|
---
|
|
7
7
|
|
|
@@ -18,7 +18,7 @@ A ProductShelf block contains any combination of:
|
|
|
18
18
|
|
|
19
19
|
If the image shows **any of the above**, use `ProductShelfRoot` composed with `CarouselRoot` and `ProductShelfItems`. When a section heading is visible, compose `ProductShelfHeading` inside `ProductShelfRoot` (above `CarouselRoot`) with the title as its `children` (like any `Text`). Pick only the card parts (`ProductCardImage`, `ProductCardName`, `ProductCardPrice`, `ProductCardListPrice`, `ProductCardDiscount`, …) that match what's visible in each card.
|
|
20
20
|
|
|
21
|
-
The image is the source of truth for the heading text, `first`/`itemsPerPage`, and which card parts to include. The **source of the shelf's products** (`categoryId` / `brandId` / `collectionId` / `searchTerm`
|
|
21
|
+
The image is the source of truth for the heading text, `first`/`itemsPerPage`, and which card parts to include. The **source of the shelf's products** (`categoryId` / `brandId` / `collectionId` / `searchTerm`) is a merchant configuration decision and cannot be read off the screenshot — leave those unset when the image gives no signal, and let the shelf fall back to catalog-wide relevance.
|
|
22
22
|
|
|
23
23
|
---
|
|
24
24
|
|
|
@@ -36,8 +36,9 @@ The image is the source of truth for the heading text, `first`/`itemsPerPage`, a
|
|
|
36
36
|
|
|
37
37
|
## Behavior model
|
|
38
38
|
|
|
39
|
-
- **The shelf owns the data.** `ProductShelfRoot` declares a `fragment ProductShelfData on Query` selecting `products(first: $first, orderBy: $orderBy, filter: $
|
|
40
|
-
- **
|
|
39
|
+
- **The shelf owns the data.** `ProductShelfRoot` declares a `fragment ProductShelfData on Query` selecting `products(first: $first, orderBy: $orderBy, filter: { categoryIds: $categoryId, brandIds: $brandId, collectionId: $collectionId, searchTerm: $searchTerm, availableOnly: $availableOnly })` and receives the resolved slice as `$data`. `fetchStrategy: 'lazy'` defers the load (below-the-fold), and a skeleton shows while pending.
|
|
40
|
+
- **The element's configuration feeds the query.** Every variable binds to this element: the compiler reads the field of the same name from the element's content slice (the `product-shelf` content model, in a store with a `cms` binding) and otherwise from the element's prop, then instances the fragment once per shelf on the page, so two shelves with different `first` or `sort` become two aliased `products` fields in one page query. `sort` reaches `$orderBy` (`ProductSortingInput`) through the `values` table in the definition's `variables` block, which equals `PRODUCT_SHELF_SORT_MAP`. A facet left empty is not sent, so the filter relaxes instead of sending `null`; `first` is honored by the API, never sliced on the client.
|
|
41
|
+
- **Props beside content.** Each content field is also a schema prop of the same name. The CMS value wins per field; the prop is the developer default and the only source in a store with no `cms` binding (Vivara, Storybook, tests). No field is `required`, so an unfilled entry never omits the shelf. `useProductShelf()` exposes the resolved `config` (`$content?.field ?? field`), `itemsPerPage` and `count`.
|
|
41
42
|
- **Two context boundaries.** The shelf owns product data + config; each row owns one item. Card parts read the item through a guarded hook and never take list-shaped props.
|
|
42
43
|
- **Presence is state-driven.** Empty result → the shelf renders nothing (no empty carousel). Unresolved lazy source → skeleton.
|
|
43
44
|
- **The carousel is composed, not baked.** Place `CarouselRoot` with `ProductShelfItems` inside it, plus `CarouselPrevious` / `CarouselNext` / `CarouselBullets` where you want them. `ProductShelfItems` renders the `CarouselTrack`, so the carousel counts one slide per product.
|
|
@@ -51,17 +52,23 @@ The image is the source of truth for the heading text, `first`/`itemsPerPage`, a
|
|
|
51
52
|
|
|
52
53
|
| Prop | Type | Default | Notes |
|
|
53
54
|
|------|------|---------|-------|
|
|
54
|
-
| `first` | number | `5` | Max products fetched (legacy `numberOfItems`) → `$first` |
|
|
55
55
|
| `itemsPerPage` | number | `5` | Cards per page; bind to `CarouselRoot`'s `itemsPerPage` |
|
|
56
|
-
| `
|
|
57
|
-
| `
|
|
58
|
-
| `
|
|
59
|
-
| `
|
|
60
|
-
| `
|
|
61
|
-
| `
|
|
62
|
-
| `
|
|
56
|
+
| `usePriceWithTaxes` | boolean | `false` | Card prices render tax-inclusive |
|
|
57
|
+
| `taxesLabel` | string | `'Tax included'` | Text for the tax label part |
|
|
58
|
+
| `first` | number | `5` | Content field. Products fetched (legacy `numberOfItems`) → `$first` |
|
|
59
|
+
| `sort` | `relevance \| price_asc \| price_desc \| name_asc \| name_desc \| release \| orders \| discount` | `relevance` | Content field → `$orderBy` through the `values` table |
|
|
60
|
+
| `searchTerm` | string | — | Content field → `filter.searchTerm` |
|
|
61
|
+
| `categoryId` | string | — | Content field → `filter.categoryIds` |
|
|
62
|
+
| `brandId` | string | — | Content field → `filter.brandIds` |
|
|
63
|
+
| `collectionId` | string | — | Content field → `filter.collectionId` (legacy `productClusterIds`) |
|
|
64
|
+
| `availableOnly` | boolean | `false` | Content field → `filter.availableOnly` (global "hide out-of-stock") |
|
|
65
|
+
| `$content` | slice | — | Compiler-injected `product-shelf` entry; each field wins over the prop of the same name |
|
|
63
66
|
| `$data` | slice | — | Compiler-injected fragment result |
|
|
64
|
-
| `products` | `ProductShelfItem[]` | — | Pre-shaped items (stories/tests; bypasses `$data`) |
|
|
67
|
+
| `products` | `ProductShelfItem[]` | — | Pre-shaped items (stories/tests; bypasses `$data`). Rendered as given: `first` does not limit them |
|
|
68
|
+
|
|
69
|
+
### Content model `product-shelf`
|
|
70
|
+
|
|
71
|
+
`ProductShelfContent` declares the merchant-facing fields: `first` (number, minimum 1), `sort` (the eight members above), `categoryId`, `brandId`, `collectionId`, `searchTerm` (string) and `availableOnly` (boolean). None is required. In a store with a `cms` binding the merchant fills them in the Content Platform entry keyed by the element id, and the shelf re-queries on the next request with no deploy; an empty field falls to the prop, and an empty facet relaxes the filter.
|
|
65
72
|
|
|
66
73
|
### ProductShelfItems
|
|
67
74
|
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# SaveCard
|
|
2
|
+
|
|
3
|
+
**Category:** commerce
|
|
4
|
+
**Purpose:** Opt-in control for authenticated shoppers to save the card they are currently entering for a future purchase, with a custom nickname. Off by default. Checking it also grants the account's data-storage consent; unchecking it never revokes that consent.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Visual cues (how to identify in an image)
|
|
9
|
+
|
|
10
|
+
Use these signals to detect that a SaveCard block is present. Position on the page is irrelevant for
|
|
11
|
+
detection — it can appear anywhere inside a payment section. The image is the source of truth for
|
|
12
|
+
layout.
|
|
13
|
+
|
|
14
|
+
A SaveCard block contains any combination of:
|
|
15
|
+
|
|
16
|
+
- A single **checkbox with a label reading something like "Save this card for next time"**, sitting
|
|
17
|
+
inside (or directly beside) a credit-card entry form.
|
|
18
|
+
- Immediately below or beside the checkbox, **small, secondary-styled helper text** mentioning storing
|
|
19
|
+
or saving personal data — present only when the account has not already granted that consent.
|
|
20
|
+
- When the checkbox is shown checked in the image, a **second text input labeled something like "Card
|
|
21
|
+
nickname" / "Name this card"** appears directly below it.
|
|
22
|
+
- This block always sits **inside a checkout payment section**, near card-number fields or a saved-cards
|
|
23
|
+
list — never on a product page, cart page, or as a standalone marketing checkbox.
|
|
24
|
+
|
|
25
|
+
If the image shows a single opt-in checkbox near card-entry fields, with an optional name/nickname text
|
|
26
|
+
field conditionally shown, use `SaveCard`. If the checkbox instead controls a newsletter or general
|
|
27
|
+
data-sharing preference with no relation to a card, that is `NewsletterOptInControl` /
|
|
28
|
+
`SavePersonalDataOptInControl` (`ClientPreferencesRoot`'s Controls), not this component.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## When to use
|
|
33
|
+
|
|
34
|
+
- Inside a checkout's payment section, as a sibling of the card-entry fields (and/or the saved-cards
|
|
35
|
+
list), so a logged-in shopper can opt in to saving the card they are currently using.
|
|
36
|
+
|
|
37
|
+
## When NOT to use
|
|
38
|
+
|
|
39
|
+
- A general "share my data with this store" consent, unrelated to a specific card → use
|
|
40
|
+
`SavePersonalDataOptInControl` (under `ClientPreferencesRoot`) instead. Different data ownership,
|
|
41
|
+
different section, and — critically — that surface's own `$data` read has a live defect (always reads
|
|
42
|
+
as unset); do not use it as a reference implementation for a new read.
|
|
43
|
+
- A newsletter opt-in → use `NewsletterOptInControl` instead.
|
|
44
|
+
- The list of already-saved cards → use `SavedCards` / `SavedCardOption` instead. `SaveCard` only offers
|
|
45
|
+
to save the card currently being entered; it never lists or selects an existing saved card.
|
|
46
|
+
- Outside a payment section, or outside an identity/session context → `SaveCard` requires **both** a
|
|
47
|
+
`PaymentSectionRoot`/`PaymentSectionController` ancestor and a `SessionRoot` ancestor. Placing it
|
|
48
|
+
without either is a build/runtime error, not a silent no-op.
|
|
49
|
+
- For a guest checkout flow with no login step at all → `SaveCard` self-gates to render nothing for
|
|
50
|
+
anyone who is not logged in; do not wrap it in an extra visibility condition in the schema.
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Composition
|
|
55
|
+
|
|
56
|
+
`SaveCard` is a single leaf — no root, no required children, no sub-components:
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
PaymentSectionRoot (or PaymentSectionController)
|
|
60
|
+
├── CardEntry (sibling — new-card fields)
|
|
61
|
+
├── SavedCards (sibling — saved-card picker)
|
|
62
|
+
├── Installments (sibling — installment selector)
|
|
63
|
+
└── SaveCard (sibling — this component)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
`SaveCard` must be a descendant of `PaymentSectionRoot` or `PaymentSectionController` (it reads and
|
|
67
|
+
writes the shopper's in-progress card draft) **and** must also have a `SessionRoot` ancestor somewhere
|
|
68
|
+
in the same composed page/layout tree (it reads the identity session state to self-gate). Place it as a
|
|
69
|
+
**sibling** of `CardEntry` and `SavedCards`, never nested inside either — one `SaveCard` instance covers
|
|
70
|
+
whichever entry path the shopper is on, the same way `Installments` does.
|
|
71
|
+
|
|
72
|
+
It self-gates to render nothing while the session is loading, for a guest, or for an
|
|
73
|
+
identified-but-not-logged-in shopper — safe to include speculatively inside any payment section, but for
|
|
74
|
+
image-to-schema work only include it when the image actually shows the checkbox described above.
|
|
75
|
+
|
|
76
|
+
It also renders nothing while the card payment is collapsed — the persisted selection is a method that
|
|
77
|
+
is not a card, or one the section does not serve, and a composed `PaymentMethodList` can bring the card
|
|
78
|
+
form back (see [[payment]]). The save-card flag and nickname stay on the draft, so the control comes back
|
|
79
|
+
as the shopper left it.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Components
|
|
84
|
+
|
|
85
|
+
### SaveCard
|
|
86
|
+
|
|
87
|
+
| Prop | Type | Default | Notes |
|
|
88
|
+
|------|------|---------|-------|
|
|
89
|
+
| `label` | string | `"Save this card for next time"` | The toggle's own label text. |
|
|
90
|
+
| `nicknameLabel` | string | `"Card nickname"` | Label for the nickname field; rendered only while checked. |
|
|
91
|
+
| `nicknamePlaceholder` | string | — | Optional placeholder for the nickname field. |
|
|
92
|
+
| `consentNoticeLabel` | string | `"Checking this also lets us store your personal data for future purchases."` | Shown beside the toggle while the account has not already granted the data-storage consent. |
|
|
93
|
+
| `disabled` | boolean | `false` | Disables both the toggle and the nickname field. |
|
|
94
|
+
|
|
95
|
+
**Data:** declares its own `fragment SaveCardData on Query` (selects `Query.clientPreferences.savePersonalData`
|
|
96
|
+
only — a single boolean, not a card field) — a small, thin fragment the compiler merges into the page
|
|
97
|
+
query alongside `PaymentSectionRoot`'s own selection; no explicit `dataSources` entry needed.
|
|
98
|
+
`fetchStrategy: 'reactive'` — the consent notice updates live from the cache, no refetch.
|
|
99
|
+
|
|
100
|
+
**The toggle never seeds from the standing consent.** It always starts unchecked, even when the account
|
|
101
|
+
has already granted `savePersonalData` elsewhere — saving *this* card is always a fresh, per-transaction
|
|
102
|
+
choice.
|
|
103
|
+
|
|
104
|
+
**Checking the toggle** records the shopper's intent for this card *and* grants the account's
|
|
105
|
+
data-storage consent in the same action; the nickname field appears immediately below. **Unchecking**
|
|
106
|
+
withdraws only this transaction's intent — it never revokes a consent an unrelated control may have
|
|
107
|
+
granted. On a rejected consent write, the first error message renders inline.
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Canonical schema example
|
|
112
|
+
|
|
113
|
+
```json
|
|
114
|
+
{
|
|
115
|
+
"payment-section": {
|
|
116
|
+
"type": "PaymentSectionRoot",
|
|
117
|
+
"children": ["card-entry", "saved-cards", "installments", "save-card"]
|
|
118
|
+
},
|
|
119
|
+
"save-card": {
|
|
120
|
+
"type": "SaveCard",
|
|
121
|
+
"props": { "label": "Save this card for next time" },
|
|
122
|
+
"style": ["flex", "flex-col", "gap-2"]
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## Common mistakes
|
|
130
|
+
|
|
131
|
+
- **Inventing `SaveCardControl` or `SaveCardToggle`.** The component is `SaveCard`.
|
|
132
|
+
- **Nesting `SaveCard` inside `CardEntry` or `SavedCards`.** It must be a sibling of both under the
|
|
133
|
+
same `PaymentSectionRoot`, exactly like `Installments`.
|
|
134
|
+
- **Placing `SaveCard` without a `SessionRoot` ancestor somewhere in the page.** It requires session
|
|
135
|
+
context to self-gate and throws without one.
|
|
136
|
+
- **Expecting the checkbox to start checked because the shopper already granted data-storage consent
|
|
137
|
+
elsewhere.** It always starts unchecked — the standing consent and the per-transaction save intent
|
|
138
|
+
are independent by design.
|
|
139
|
+
- **Expecting unchecking to revoke a previously granted consent.** It deliberately does not; see
|
|
140
|
+
`specs/save-card-control.md` Decision 1 for the full rationale.
|
|
141
|
+
- **Using `SaveCard` to render a general privacy/data-sharing consent form.** It is scoped to a single
|
|
142
|
+
boolean and a nickname for the card currently being entered — use `ClientPreferencesRoot`'s Controls
|
|
143
|
+
for anything broader.
|
|
144
|
+
- **Expecting a full card number or CVV field.** The fragment structurally selects neither, and no prop
|
|
145
|
+
accepts one — this component never touches sensitive card data.
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Styling
|
|
150
|
+
|
|
151
|
+
Single DOM root wrapping the toggle field, an optional nickname field, and an optional inline error.
|
|
152
|
+
|
|
153
|
+
| Component | What you style | Plausible classes |
|
|
154
|
+
|---|---|---|
|
|
155
|
+
| `SaveCard` (root `div`) | Stacks the toggle field, nickname field, and error | `flex flex-col gap-[8px]` |
|
|
156
|
+
| Consent notice (`data-fs-save-card-consent-notice`) | De-emphasized helper text | `text-[12px] text-[#666666]` |
|
|
157
|
+
| Nickname field wrapper (`data-fs-save-card-nickname-field`) | Spacing above the field | `mt-[8px]` |
|
|
158
|
+
| Inline error (`data-fs-save-card-error`) | Warning-toned alert text | `text-[12px] font-medium text-[#B00020]` |
|
|
159
|
+
| Placing (`data-fs-save-card-placing`) | Present while an order-placement sequence is in flight (T28) — distinct from `data-fs-save-card-pending`, which this component already emits for its own local consent dispatch; the root is also `inert` and `aria-disabled` while placing | `opacity-60 cursor-not-allowed` |
|
|
160
|
+
|
|
161
|
+
Styled example:
|
|
162
|
+
|
|
163
|
+
```json
|
|
164
|
+
{
|
|
165
|
+
"save-card": {
|
|
166
|
+
"type": "SaveCard",
|
|
167
|
+
"style": ["flex", "flex-col", "gap-2"]
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
There is no per-field styling hook beyond the particles' own `data-fs-checkbox-*` / `data-fs-field-*`
|
|
173
|
+
attributes (`Checkbox.Root`/`Checkbox.Indicator`, `Field.Root`/`Field.Label`/`Field.Control`) — style
|
|
174
|
+
the checked state via Base UI's own `data-checked` on `Checkbox.Root`, per the `style-components` skill.
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Related
|
|
179
|
+
|
|
180
|
+
- [[saved-cards]] — the list of already-saved cards, and the CVV re-entry for one; `SaveCard` only
|
|
181
|
+
offers to save the card currently being entered, never lists an existing one.
|
|
182
|
+
- [[installments]] — the closest structural sibling: a single leaf requiring `PaymentSectionRoot`, no
|
|
183
|
+
Root/leaf split, sibling placement under the same section.
|
|
184
|
+
- [[client-preferences]] — owns the broader `savePersonalData` / `optinNewsLetter` consent surface;
|
|
185
|
+
`SaveCard` reads the same underlying field but through its own fragment, never by composing under
|
|
186
|
+
`ClientPreferencesRoot` (whose own read of that field has a live, unrelated defect).
|