@vtex/faststore 0.0.6 → 0.0.8
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 +99 -0
- package/dist/actions.json +152 -1
- package/dist/catalog.json +2541 -1278
- 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 +799 -0
- package/dist/cli/index.js +36459 -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 +49879 -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 +5 -1
- package/dist/client.js +5 -1
- package/dist/components.d.ts +38 -9
- package/dist/components.js +42 -13
- package/dist/content-types.json +616 -0
- package/dist/core/dist/define-component.d.ts +43 -1
- package/dist/core/dist/index.d.ts +2 -2
- package/dist/docs/account-access-gate.md +4 -4
- package/dist/docs/account-address-book.md +519 -0
- package/dist/docs/account-entry-point.md +1 -1
- package/dist/docs/account-menu-identity.md +160 -0
- package/dist/docs/account-menu.md +107 -24
- package/dist/docs/alert-dialog.md +2 -2
- package/dist/docs/avatar.md +103 -0
- package/dist/docs/billing-address.md +17 -9
- package/dist/docs/card-entry.md +37 -6
- package/dist/docs/cardholder-document.md +16 -14
- package/dist/docs/cart-item.md +1 -1
- package/dist/docs/cart.md +8 -8
- package/dist/docs/client-profile-fieldset.md +7 -5
- package/dist/docs/copy-text.md +84 -0
- package/dist/docs/document-field.md +8 -8
- package/dist/docs/footer-link-group.md +2 -2
- package/dist/docs/header-root.md +1 -1
- package/dist/docs/incentives.md +2 -2
- package/dist/docs/logo.md +5 -5
- package/dist/docs/my-account-profile.md +5 -5
- package/dist/docs/navigation-link-list.md +2 -2
- package/dist/docs/navigation-link.md +3 -3
- package/dist/docs/order-cancel.md +52 -0
- package/dist/docs/order-delivery-address.md +63 -0
- package/dist/docs/order-delivery-group.md +83 -0
- package/dist/docs/order-details-back.md +41 -0
- package/dist/docs/order-details-header.md +48 -0
- package/dist/docs/order-list-row.md +46 -0
- package/dist/docs/order-list-search.md +101 -0
- package/dist/docs/order-list-views.md +97 -0
- package/dist/docs/order-list.md +94 -0
- package/dist/docs/order-payment.md +94 -0
- package/dist/docs/order-placed-date.md +49 -0
- package/dist/docs/order-placed-payment-summary.md +13 -15
- package/dist/docs/order-placed.md +225 -0
- package/dist/docs/order-progress.md +88 -0
- package/dist/docs/order-status-badge.md +91 -0
- package/dist/docs/order-summary.md +53 -0
- package/dist/docs/payment-method-badges.md +14 -3
- package/dist/docs/place-order.md +13 -9
- package/dist/docs/product-shelf.md +20 -13
- package/dist/docs/saved-cards.md +2 -2
- package/dist/docs/select.md +19 -6
- package/dist/docs/session.md +1 -1
- package/dist/docs/shipping-address.md +1 -1
- package/dist/docs/sign-in-redirect.md +6 -6
- package/dist/docs/toast.md +7 -3
- package/dist/index.d.ts +7 -3
- package/dist/index.js +2 -1
- package/dist/internal/client.d.ts +6 -1
- package/dist/internal/client.js +8 -3
- package/dist/internal/content.d.ts +2 -2
- package/dist/internal/server.d.ts +8 -1
- package/dist/internal/server.js +8 -1
- package/dist/matter/account-access-gate/account-access-gate.d.ts +2 -1
- package/dist/matter/account-access-gate/account-access-gate.js +19 -16
- package/dist/matter/account-access-gate/account-access-gate.js.map +1 -1
- package/dist/matter/account-address-book/account-address-book-delete.d.ts +60 -0
- package/dist/matter/account-address-book/account-address-book-delete.js +184 -0
- package/dist/matter/account-address-book/account-address-book-delete.js.map +1 -0
- package/dist/matter/account-address-book/account-address-book-deliverability.d.ts +19 -0
- package/dist/matter/account-address-book/account-address-book-deliverability.js +50 -0
- package/dist/matter/account-address-book/account-address-book-deliverability.js.map +1 -0
- package/dist/matter/account-address-book/account-address-book-form-parts.d.ts +42 -0
- package/dist/matter/account-address-book/account-address-book-form-parts.js +146 -0
- package/dist/matter/account-address-book/account-address-book-form-parts.js.map +1 -0
- package/dist/matter/account-address-book/account-address-book-profile-notice.d.ts +25 -0
- package/dist/matter/account-address-book/account-address-book-profile-notice.js +72 -0
- package/dist/matter/account-address-book/account-address-book-profile-notice.js.map +1 -0
- package/dist/matter/account-address-book/account-address-book.d.ts +120 -0
- package/dist/matter/account-address-book/account-address-book.js +469 -0
- package/dist/matter/account-address-book/account-address-book.js.map +1 -0
- package/dist/matter/account-address-book/account-address-country-switch.js +59 -0
- package/dist/matter/account-address-book/account-address-country-switch.js.map +1 -0
- package/dist/matter/account-address-book/account-address-country.d.ts +36 -0
- package/dist/matter/account-address-book/account-address-country.js +151 -0
- package/dist/matter/account-address-book/account-address-country.js.map +1 -0
- package/dist/matter/account-address-book/account-address-first-line.js +11 -0
- package/dist/matter/account-address-book/account-address-first-line.js.map +1 -0
- package/dist/matter/account-address-book/account-address-form-context.d.ts +55 -0
- package/dist/matter/account-address-book/account-address-form-context.js +14 -0
- package/dist/matter/account-address-book/account-address-form-context.js.map +1 -0
- package/dist/matter/account-address-book/account-address-form.d.ts +85 -0
- package/dist/matter/account-address-book/account-address-form.js +946 -0
- package/dist/matter/account-address-book/account-address-form.js.map +1 -0
- package/dist/matter/account-address-book/account-address-postal-autofill.js +189 -0
- package/dist/matter/account-address-book/account-address-postal-autofill.js.map +1 -0
- package/dist/matter/account-address-book/account-address-record.d.ts +26 -0
- package/dist/matter/account-address-book/account-address-record.js +138 -0
- package/dist/matter/account-address-book/account-address-record.js.map +1 -0
- package/dist/matter/account-entry-point/account-entry-point.js +11 -11
- package/dist/matter/account-entry-point/account-entry-point.js.map +1 -1
- package/dist/matter/account-menu/account-menu.d.ts +77 -3
- package/dist/matter/account-menu/account-menu.js +252 -19
- 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 +30 -17
- package/dist/matter/billing-address/billing-address.js +128 -33
- package/dist/matter/billing-address/billing-address.js.map +1 -1
- package/dist/matter/breadcrumb/breadcrumb.js +2 -2
- package/dist/matter/breadcrumb/breadcrumb.js.map +1 -1
- package/dist/matter/card-entry/card-capture.js +4 -3
- package/dist/matter/card-entry/card-capture.js.map +1 -1
- package/dist/matter/card-entry/card-entry.d.ts +5 -3
- package/dist/matter/card-entry/card-entry.js +78 -27
- package/dist/matter/card-entry/card-entry.js.map +1 -1
- package/dist/matter/cardholder-document/cardholder-document.d.ts +10 -9
- package/dist/matter/cardholder-document/cardholder-document.js +36 -16
- package/dist/matter/cardholder-document/cardholder-document.js.map +1 -1
- package/dist/matter/cart/cart.d.ts +7 -2
- package/dist/matter/cart/cart.js +29 -26
- package/dist/matter/cart/cart.js.map +1 -1
- package/dist/matter/cart-item/cart-item.js +2 -2
- package/dist/matter/cart-item/cart-item.js.map +1 -1
- package/dist/matter/client-preferences/client-preferences-fieldset.js +2 -2
- package/dist/matter/client-profile-form/client-profile-fieldset.js +9 -6
- package/dist/matter/client-profile-form/client-profile-fieldset.js.map +1 -1
- package/dist/matter/identity-edit/identity-edit.js +3 -3
- package/dist/matter/identity-edit/identity-edit.js.map +1 -1
- package/dist/matter/identity-summary/identity-summary.js +4 -3
- package/dist/matter/identity-summary/identity-summary.js.map +1 -1
- package/dist/matter/my-account-profile/my-account-profile.d.ts +6 -3
- 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-cancel/order-cancel.d.ts +110 -0
- package/dist/matter/order-cancel/order-cancel.js +344 -0
- package/dist/matter/order-cancel/order-cancel.js.map +1 -0
- package/dist/matter/order-delivery-address/order-delivery-address.d.ts +73 -0
- package/dist/matter/order-delivery-address/order-delivery-address.js +172 -0
- package/dist/matter/order-delivery-address/order-delivery-address.js.map +1 -0
- package/dist/matter/order-delivery-group/order-delivery-group.d.ts +128 -0
- package/dist/matter/order-delivery-group/order-delivery-group.js +408 -0
- package/dist/matter/order-delivery-group/order-delivery-group.js.map +1 -0
- package/dist/matter/order-details-header/order-details-header.d.ts +104 -0
- package/dist/matter/order-details-header/order-details-header.js +190 -0
- package/dist/matter/order-details-header/order-details-header.js.map +1 -0
- package/dist/matter/order-list/customer-orders-filter.d.ts +12 -0
- package/dist/matter/order-list/customer-orders-filter.js +30 -0
- package/dist/matter/order-list/customer-orders-filter.js.map +1 -0
- package/dist/matter/order-list/order-list.d.ts +121 -0
- package/dist/matter/order-list/order-list.js +395 -0
- package/dist/matter/order-list/order-list.js.map +1 -0
- package/dist/matter/order-list-row/order-list-row.d.ts +44 -0
- package/dist/matter/order-list-row/order-list-row.js +127 -0
- package/dist/matter/order-list-row/order-list-row.js.map +1 -0
- package/dist/matter/order-list-search/order-list-search.d.ts +72 -0
- package/dist/matter/order-list-search/order-list-search.js +296 -0
- package/dist/matter/order-list-search/order-list-search.js.map +1 -0
- package/dist/matter/order-list-views/order-list-views.d.ts +65 -0
- package/dist/matter/order-list-views/order-list-views.js +154 -0
- package/dist/matter/order-list-views/order-list-views.js.map +1 -0
- package/dist/matter/order-payment/order-payment.d.ts +83 -0
- package/dist/matter/order-payment/order-payment.js +258 -0
- package/dist/matter/order-payment/order-payment.js.map +1 -0
- package/dist/matter/order-placed/order-placed-deliveries.d.ts +145 -0
- package/dist/matter/order-placed/order-placed-deliveries.js +380 -0
- package/dist/matter/order-placed/order-placed-deliveries.js.map +1 -0
- package/dist/matter/order-placed/order-placed.d.ts +122 -0
- package/dist/matter/order-placed/order-placed.js +452 -0
- package/dist/matter/order-placed/order-placed.js.map +1 -0
- package/dist/matter/order-placed-date/order-placed-date.d.ts +45 -0
- package/dist/matter/order-placed-date/order-placed-date.js +92 -0
- package/dist/matter/order-placed-date/order-placed-date.js.map +1 -0
- package/dist/matter/order-placed-payment-summary/order-placed-payment-summary.d.ts +5 -8
- package/dist/matter/order-placed-payment-summary/order-placed-payment-summary.js +29 -81
- package/dist/matter/order-placed-payment-summary/order-placed-payment-summary.js.map +1 -1
- package/dist/matter/order-progress/order-progress.d.ts +85 -0
- package/dist/matter/order-progress/order-progress.js +203 -0
- package/dist/matter/order-progress/order-progress.js.map +1 -0
- package/dist/matter/order-summary/order-summary.d.ts +81 -0
- package/dist/matter/order-summary/order-summary.js +192 -0
- package/dist/matter/order-summary/order-summary.js.map +1 -0
- package/dist/matter/payment/cardholder-document-rule.d.ts +35 -0
- package/dist/matter/payment/cardholder-document-rule.js +35 -0
- package/dist/matter/payment/cardholder-document-rule.js.map +1 -0
- package/dist/matter/payment/payment-card-capture.js +38 -23
- package/dist/matter/payment/payment-card-capture.js.map +1 -1
- package/dist/matter/payment/payment-controller.js +12 -0
- package/dist/matter/payment/payment-controller.js.map +1 -1
- package/dist/matter/payment/payment-method-badges.js +4 -2
- package/dist/matter/payment/payment-method-badges.js.map +1 -1
- package/dist/matter/payment/payment.js +12 -0
- package/dist/matter/payment/payment.js.map +1 -1
- package/dist/matter/payment/use-payment-method-selection.d.ts +39 -0
- package/dist/matter/payment/use-payment-method-selection.js +30 -2
- package/dist/matter/payment/use-payment-method-selection.js.map +1 -1
- package/dist/matter/payment/use-payment-submit-gate.d.ts +16 -2
- package/dist/matter/payment/use-payment-submit-gate.js +42 -14
- package/dist/matter/payment/use-payment-submit-gate.js.map +1 -1
- package/dist/matter/place-order/place-order.d.ts +12 -7
- package/dist/matter/place-order/place-order.js +126 -103
- package/dist/matter/place-order/place-order.js.map +1 -1
- package/dist/matter/pricing/pricing.js +5 -5
- package/dist/matter/pricing/pricing.js.map +1 -1
- package/dist/matter/product-shelf/product-shelf.d.ts +61 -31
- package/dist/matter/product-shelf/product-shelf.js +157 -57
- package/dist/matter/product-shelf/product-shelf.js.map +1 -1
- package/dist/matter/region/region.js +1 -1
- package/dist/matter/saved-cards/saved-cards.js +27 -15
- package/dist/matter/saved-cards/saved-cards.js.map +1 -1
- package/dist/matter/session/session.js +2 -2
- package/dist/matter/session/session.js.map +1 -1
- package/dist/matter/shipping-address/country-descriptors.d.ts +1 -0
- package/dist/matter/shipping-address/country-descriptors.js +23 -1
- package/dist/matter/shipping-address/country-descriptors.js.map +1 -1
- package/dist/matter/shipping-address/shipping-address.js +63 -15
- package/dist/matter/shipping-address/shipping-address.js.map +1 -1
- package/dist/matter/shipping-simulator/shipping-simulator.d.ts +1 -1
- package/dist/matter/shipping-simulator/shipping-simulator.js.map +1 -1
- package/dist/matter/sign-in-redirect/sign-in-redirect.js +48 -10
- package/dist/matter/sign-in-redirect/sign-in-redirect.js.map +1 -1
- package/dist/matter/variant-selector/variant-selector.js +2 -2
- package/dist/matter/variant-selector/variant-selector.js.map +1 -1
- package/dist/particles/address-summary/address-summary.js +2 -2
- package/dist/particles/address-summary/address-summary.js.map +1 -1
- package/dist/particles/alert-dialog/alert-dialog.d.ts +5 -1
- package/dist/particles/alert-dialog/alert-dialog.js +5 -2
- package/dist/particles/alert-dialog/alert-dialog.js.map +1 -1
- 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/copy-text/copy-text.d.ts +28 -0
- package/dist/particles/copy-text/copy-text.js +140 -0
- package/dist/particles/copy-text/copy-text.js.map +1 -0
- package/dist/particles/document-field/document-field.d.ts +34 -5
- package/dist/particles/document-field/document-field.js +115 -19
- package/dist/particles/document-field/document-field.js.map +1 -1
- package/dist/particles/field/field.js +1 -1
- package/dist/particles/field/field.js.map +1 -1
- package/dist/particles/footer-link-group/footer-link-group.d.ts +6 -2
- package/dist/particles/footer-link-group/footer-link-group.js +18 -7
- package/dist/particles/footer-link-group/footer-link-group.js.map +1 -1
- package/dist/particles/incentives/incentives.d.ts +6 -2
- package/dist/particles/incentives/incentives.js +7 -2
- package/dist/particles/incentives/incentives.js.map +1 -1
- package/dist/particles/logo/logo.d.ts +3 -2
- package/dist/particles/logo/logo.js +20 -8
- package/dist/particles/logo/logo.js.map +1 -1
- package/dist/particles/navigation-link/navigation-link.d.ts +2 -3
- package/dist/particles/navigation-link/navigation-link.js +4 -4
- package/dist/particles/navigation-link/navigation-link.js.map +1 -1
- package/dist/particles/navigation-link-list/navigation-link-list.d.ts +6 -2
- package/dist/particles/navigation-link-list/navigation-link-list.js +18 -7
- package/dist/particles/navigation-link-list/navigation-link-list.js.map +1 -1
- package/dist/particles/order-details-back/order-details-back.d.ts +28 -0
- package/dist/particles/order-details-back/order-details-back.js +52 -0
- package/dist/particles/order-details-back/order-details-back.js.map +1 -0
- package/dist/particles/order-status-badge/order-status-badge.d.ts +19 -0
- package/dist/particles/order-status-badge/order-status-badge.js +50 -0
- package/dist/particles/order-status-badge/order-status-badge.js.map +1 -0
- package/dist/particles/phone-field/phone-field.js +2 -2
- package/dist/particles/phone-field/phone-field.js.map +1 -1
- package/dist/particles/select/select.js +6 -6
- package/dist/particles/select/select.js.map +1 -1
- package/dist/particles/toast/toast.d.ts +1 -1
- package/dist/particles/toast/toast.js +32 -6
- package/dist/particles/toast/toast.js.map +1 -1
- package/dist/schema.graphql +1079 -54
- package/dist/sdk/actions/address-action-provider.d.ts +16 -0
- package/dist/sdk/actions/address-action-provider.js +20 -0
- package/dist/sdk/actions/address-action-provider.js.map +1 -0
- package/dist/sdk/actions/analytics-action-provider.js +1 -1
- package/dist/sdk/actions/index.d.ts +3 -1
- package/dist/sdk/actions/index.js +3 -1
- package/dist/sdk/actions/order-action-provider.d.ts +12 -0
- package/dist/sdk/actions/order-action-provider.js +16 -0
- package/dist/sdk/actions/order-action-provider.js.map +1 -0
- package/dist/sdk/actions/types.d.ts +6 -2
- package/dist/sdk/address/address-fields.js +38 -0
- package/dist/sdk/address/address-fields.js.map +1 -0
- package/dist/sdk/address/checkout-readiness.js +17 -0
- package/dist/sdk/address/checkout-readiness.js.map +1 -0
- package/dist/sdk/address/constants.js +60 -0
- package/dist/sdk/address/constants.js.map +1 -0
- package/dist/sdk/address/country-display.js +19 -0
- package/dist/sdk/address/country-display.js.map +1 -0
- package/dist/sdk/address/country-rules.js +30 -0
- package/dist/sdk/address/country-rules.js.map +1 -0
- package/dist/sdk/address/format-address.js +18 -0
- package/dist/sdk/address/format-address.js.map +1 -0
- package/dist/sdk/address/iso-countries.js +262 -0
- package/dist/sdk/address/iso-countries.js.map +1 -0
- package/dist/sdk/address/layouts.js +56 -0
- package/dist/sdk/address/layouts.js.map +1 -0
- package/dist/sdk/address/messages.js +97 -0
- package/dist/sdk/address/messages.js.map +1 -0
- package/dist/sdk/address/operations.d.ts +54 -0
- package/dist/sdk/address/operations.js +83 -0
- package/dist/sdk/address/operations.js.map +1 -0
- package/dist/sdk/address/postal-code-lookup-operations.js +23 -0
- package/dist/sdk/address/postal-code-lookup-operations.js.map +1 -0
- package/dist/sdk/address/postal-code.js +11 -0
- package/dist/sdk/address/postal-code.js.map +1 -0
- package/dist/sdk/address/schema.js +14 -0
- package/dist/sdk/address/schema.js.map +1 -0
- package/dist/sdk/address/server-fields.js +25 -0
- package/dist/sdk/address/server-fields.js.map +1 -0
- package/dist/sdk/address/switch-country.js +57 -0
- package/dist/sdk/address/switch-country.js.map +1 -0
- package/dist/sdk/address/types.d.ts +26 -0
- package/dist/sdk/address/use-address-actions.d.ts +29 -0
- package/dist/sdk/address/use-address-actions.js +247 -0
- package/dist/sdk/address/use-address-actions.js.map +1 -0
- package/dist/sdk/address/use-address-by-postal-code.js +57 -0
- package/dist/sdk/address/use-address-by-postal-code.js.map +1 -0
- package/dist/sdk/address/validate-address.js +62 -0
- package/dist/sdk/address/validate-address.js.map +1 -0
- package/dist/sdk/analytics/use-analytics-actions.js +2 -1
- package/dist/sdk/analytics/use-analytics-actions.js.map +1 -1
- package/dist/sdk/cache/storefront-cache.js +29 -4
- package/dist/sdk/cache/storefront-cache.js.map +1 -1
- package/dist/sdk/cart/use-cart-actions.js +9 -8
- 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/client/fetch-page-data.d.ts +11 -0
- package/dist/sdk/client/fetch-page-data.js +12 -1
- package/dist/sdk/client/fetch-page-data.js.map +1 -1
- package/dist/sdk/content/create-content-runtime.d.ts +22 -1
- package/dist/sdk/content/create-content-runtime.js +90 -9
- 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/failed-fields.js +23 -0
- package/dist/sdk/data/failed-fields.js.map +1 -0
- 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 +1 -1
- package/dist/sdk/data/page-reactive-data-root.d.ts +45 -7
- package/dist/sdk/data/page-reactive-data-root.js +88 -12
- package/dist/sdk/data/page-reactive-data-root.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 +4 -1
- package/dist/sdk/generated/gql.js.map +1 -1
- package/dist/sdk/generated/graphql.d.ts +8 -0
- package/dist/sdk/generated/graphql.js +296 -1
- package/dist/sdk/generated/graphql.js.map +1 -1
- package/dist/sdk/i18n/messages/en-US.js +138 -1
- package/dist/sdk/i18n/messages/en-US.json +138 -1
- package/dist/sdk/i18n/messages/en-US.json.d.ts +137 -0
- package/dist/sdk/i18n/messages/es-ES.js +138 -1
- package/dist/sdk/i18n/messages/es-ES.json +138 -1
- package/dist/sdk/i18n/messages/pt-BR.js +138 -1
- package/dist/sdk/i18n/messages/pt-BR.json +138 -1
- package/dist/sdk/identity/use-identity-actions.js +5 -4
- package/dist/sdk/identity/use-identity-actions.js.map +1 -1
- package/dist/sdk/navigation/current-page.js +30 -0
- package/dist/sdk/navigation/current-page.js.map +1 -0
- package/dist/sdk/navigation/not-found-log.d.ts +28 -0
- package/dist/sdk/navigation/not-found-log.js +133 -0
- package/dist/sdk/navigation/not-found-log.js.map +1 -0
- package/dist/sdk/navigation/path.js +25 -0
- package/dist/sdk/navigation/path.js.map +1 -0
- package/dist/sdk/navigation/redirect.d.ts +11 -0
- package/dist/sdk/navigation/redirect.js +22 -0
- package/dist/sdk/navigation/redirect.js.map +1 -0
- package/dist/sdk/navigation/report.js +70 -0
- package/dist/sdk/navigation/report.js.map +1 -0
- package/dist/sdk/navigation/resolve-storefront-href.d.ts +12 -0
- package/dist/sdk/navigation/resolve-storefront-href.js +214 -0
- package/dist/sdk/navigation/resolve-storefront-href.js.map +1 -0
- package/dist/sdk/navigation/route-identity-provider.d.ts +11 -0
- package/dist/sdk/navigation/route-identity-provider.js +20 -0
- package/dist/sdk/navigation/route-identity-provider.js.map +1 -0
- package/dist/sdk/navigation/route-map-provider.d.ts +19 -0
- package/dist/sdk/navigation/route-map-provider.js +29 -0
- package/dist/sdk/navigation/route-map-provider.js.map +1 -0
- package/dist/sdk/navigation/storefront-link.d.ts +16 -0
- package/dist/sdk/navigation/storefront-link.js +66 -0
- package/dist/sdk/navigation/storefront-link.js.map +1 -0
- package/dist/sdk/navigation/types.d.ts +45 -0
- package/dist/sdk/navigation/use-storefront-navigation.d.ts +26 -0
- package/dist/sdk/navigation/use-storefront-navigation.js +73 -0
- package/dist/sdk/navigation/use-storefront-navigation.js.map +1 -0
- package/dist/sdk/navigation/use-storefront-route.d.ts +26 -0
- package/dist/sdk/navigation/use-storefront-route.js +87 -0
- package/dist/sdk/navigation/use-storefront-route.js.map +1 -0
- package/dist/sdk/navigation/use-storefront-search-params.d.ts +11 -0
- package/dist/sdk/navigation/use-storefront-search-params.js +16 -0
- package/dist/sdk/navigation/use-storefront-search-params.js.map +1 -0
- package/dist/sdk/orders/completion-badge.d.ts +8 -0
- package/dist/sdk/orders/completion-badge.js +47 -0
- package/dist/sdk/orders/completion-badge.js.map +1 -0
- package/dist/sdk/orders/order-operations.d.ts +19 -0
- package/dist/sdk/orders/order-operations.js +21 -0
- package/dist/sdk/orders/order-operations.js.map +1 -0
- package/dist/sdk/orders/use-order-actions.d.ts +23 -0
- package/dist/sdk/orders/use-order-actions.js +62 -0
- package/dist/sdk/orders/use-order-actions.js.map +1 -0
- package/dist/sdk/payment/gateway-origin.js +26 -13
- package/dist/sdk/payment/gateway-origin.js.map +1 -1
- package/dist/sdk/payment/gateway.d.ts +14 -2
- package/dist/sdk/payment/gateway.js +32 -14
- package/dist/sdk/payment/gateway.js.map +1 -1
- package/dist/sdk/payment/operations.js +11 -0
- package/dist/sdk/payment/operations.js.map +1 -1
- package/dist/sdk/payment/submission-registry.d.ts +2 -1
- package/dist/sdk/payment/submission-registry.js +12 -8
- package/dist/sdk/payment/submission-registry.js.map +1 -1
- package/dist/sdk/payment/use-payment-actions.d.ts +5 -3
- package/dist/sdk/payment/use-payment-actions.js +6 -3
- package/dist/sdk/payment/use-payment-actions.js.map +1 -1
- package/dist/sdk/payment/use-place-order.d.ts +6 -3
- package/dist/sdk/payment/use-place-order.js +334 -144
- package/dist/sdk/payment/use-place-order.js.map +1 -1
- package/dist/sdk/region/use-region-actions.js +2 -1
- package/dist/sdk/region/use-region-actions.js.map +1 -1
- package/dist/sdk/seo/page-alternates.d.ts +55 -0
- package/dist/sdk/seo/page-alternates.js +132 -0
- package/dist/sdk/seo/page-alternates.js.map +1 -0
- 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 +3 -2
- package/dist/sdk/shipping/use-shipping-actions.js.map +1 -1
- package/dist/utils/own-value.js +19 -0
- package/dist/utils/own-value.js.map +1 -0
- package/dist/utils/safe-href.d.ts +11 -0
- package/dist/utils/safe-href.js +31 -22
- package/dist/utils/safe-href.js.map +1 -1
- package/package.json +17 -2
- package/skills/storefront-asset/SKILL.md +111 -0
- package/skills/storefront-asset/references/api-side.md +153 -0
- package/skills/storefront-asset/references/findings.md +40 -0
- package/skills/storefront-asset/references/ui-side.md +94 -0
- package/src/styles/tailwind.css +27 -0
|
@@ -4,15 +4,15 @@ import "../../core/dist/index.js";
|
|
|
4
4
|
import { useActionsOverride } from "../../sdk/actions/actions-override-context.js";
|
|
5
5
|
import { useDataOverride } from "../../sdk/data/data-override-context.js";
|
|
6
6
|
import { resolveAuthoredCopy } from "../../utils/resolve-authored-copy.js";
|
|
7
|
+
import { ListItemRefContext } from "../../sdk/data/list-item-ref-context.js";
|
|
7
8
|
import { Fieldset } from "../../particles/fieldset/fieldset.js";
|
|
8
9
|
import { RadioGroup, RadioIndicator, RadioRoot } from "../../particles/radio/radio.js";
|
|
9
10
|
import { useRegisterPaymentSubmission } from "../../sdk/payment/submission-registry.js";
|
|
10
11
|
import { PAYMENT_NETWORK_ERROR_CODE } from "../../sdk/payment/operations.js";
|
|
11
12
|
import { usePayment } from "../payment/payment.js";
|
|
12
13
|
import { SECURITY_CODE_MASK_STYLE, isSecurityCodeComplete } from "../../sdk/payment/security-code-mask.js";
|
|
13
|
-
import { ListItemRefContext } from "../../sdk/data/list-item-ref-context.js";
|
|
14
14
|
import { createContext, useCallback, useContext, useId, useRef, useState } from "react";
|
|
15
|
-
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
15
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
16
16
|
//#region src/matter/saved-cards/saved-cards.tsx
|
|
17
17
|
/**
|
|
18
18
|
* Pick a saved card and enter only the CVV (specs/saved-cards.md, T12 of the Credit Card epic;
|
|
@@ -123,6 +123,7 @@ const SavedCardCvvFieldDefinition = defineComponent({
|
|
|
123
123
|
component: SavedCardCvvField,
|
|
124
124
|
requiresContext: "saved-card-data",
|
|
125
125
|
actions: { payment: ["sendPayments"] },
|
|
126
|
+
hardened: true,
|
|
126
127
|
editor: {
|
|
127
128
|
label: "Saved Card CVV Field",
|
|
128
129
|
category: "commerce"
|
|
@@ -162,7 +163,8 @@ function resolveEffectiveSelectedAccountId(selectedAccountId, draftPath) {
|
|
|
162
163
|
}
|
|
163
164
|
function SavedCards({ $data: $dataProp, legend, className, children, ...props }) {
|
|
164
165
|
const $data = useDataOverride($dataProp);
|
|
165
|
-
const { selectedAccountId, selectMethod, selectionError, draft, placing, cardPaymentCollapsed } = usePayment();
|
|
166
|
+
const { selectedAccountId, selectMethod, selectionError, draft, placing: placingNow, attempt, cardPaymentCollapsed } = usePayment();
|
|
167
|
+
const placing = placingNow || attempt?.phase === "sent" || attempt?.phase === "unresolved";
|
|
166
168
|
const loading = $data == null;
|
|
167
169
|
const savedCards = $data?.savedCards ?? [];
|
|
168
170
|
const effectiveSelectedAccountId = resolveEffectiveSelectedAccountId(selectedAccountId, draft.path);
|
|
@@ -324,17 +326,27 @@ function SavedCardCvvInput({ accountId, bin, securityCodeLabel, securityCodeDesc
|
|
|
324
326
|
}, [cvv]);
|
|
325
327
|
const submit = useCallback(async (transaction) => {
|
|
326
328
|
const refusals = checkCvv();
|
|
327
|
-
if (refusals.length > 0) return
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
329
|
+
if (refusals.length > 0) return {
|
|
330
|
+
reachedGateway: false,
|
|
331
|
+
userErrors: refusals
|
|
332
|
+
};
|
|
333
|
+
try {
|
|
334
|
+
if (!sendPayments) return {
|
|
335
|
+
reachedGateway: false,
|
|
336
|
+
userErrors: [MISSING_ACTION_ERROR]
|
|
337
|
+
};
|
|
338
|
+
return await sendPayments({
|
|
339
|
+
transaction,
|
|
340
|
+
card: {
|
|
341
|
+
kind: "saved",
|
|
342
|
+
accountId,
|
|
343
|
+
bin,
|
|
344
|
+
securityCode: cvv
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
} finally {
|
|
348
|
+
setCvv("");
|
|
349
|
+
}
|
|
338
350
|
}, [
|
|
339
351
|
checkCvv,
|
|
340
352
|
sendPayments,
|
|
@@ -348,7 +360,7 @@ function SavedCardCvvInput({ accountId, bin, securityCodeLabel, securityCodeDesc
|
|
|
348
360
|
...props.style,
|
|
349
361
|
...SECURITY_CODE_MASK_STYLE
|
|
350
362
|
} : props.style;
|
|
351
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("input", {
|
|
363
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("input", {
|
|
352
364
|
...props,
|
|
353
365
|
ref: setInputRef,
|
|
354
366
|
type: "text",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"saved-cards.js","names":[],"sources":["../../../src/matter/saved-cards/saved-cards.tsx"],"sourcesContent":["'use client';\n\nimport { defineComponent } from '@vtex/fs-core';\nimport {\n type ComponentPropsWithRef,\n createContext,\n type ReactNode,\n useCallback,\n useContext,\n useId,\n useRef,\n useState,\n} from 'react';\nimport { Fieldset } from '../../particles/fieldset/fieldset.js';\nimport { RadioGroup, RadioIndicator, RadioRoot } from '../../particles/radio/radio.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 { ListItemRefContext } from '../../sdk/data/list-item-ref-context.js';\nimport { PAYMENT_NETWORK_ERROR_CODE, type PaymentUserError } from '../../sdk/payment/operations.js';\nimport { isSecurityCodeComplete, SECURITY_CODE_MASK_STYLE } from '../../sdk/payment/security-code-mask.js';\nimport {\n type PaymentPreflightCheck,\n type PaymentSubmission,\n useRegisterPaymentSubmission,\n} from '../../sdk/payment/submission-registry.js';\nimport { resolveAuthoredCopy } from '../../utils/resolve-authored-copy.js';\nimport { usePayment } from '../payment/payment.js';\n\n/**\n * Pick a saved card and enter only the CVV (specs/saved-cards.md, T12 of the Credit Card epic;\n * docs/features/credit-card/rfc.md § D7). Three components, split by data-fetch role rather than by\n * visual grouping (ADR-0012 Option B, per `SavedCard`'s own schema doc comment):\n *\n * - `SavedCards` — the list root. Owns the minimal `{ id, accountId, methodId }` selection needed to\n * iterate and to dispatch selection; publishes each row's entity ref via `ListItemRefContext`,\n * mirroring `CartList`.\n * - `SavedCardOption` — the per-row template. Independently subscribes to its own `SavedCard:<id>`\n * cache entity through the shipped `reactive-list-item` runtime, mirroring `CartItemRoot`.\n * - `SavedCardCvvField` — the one PCI-bearing, action-dispatching leaf. Declares no `fetchStrategy` of\n * its own; it reads its row through `SavedCardOption`'s plain context, mirroring `CartItemRemove`'s\n * split from `CartItemRoot` (spec Decision 1).\n *\n * The submission continuation is the PCI-critical core (epic § \"The card-data submission channel\"):\n * `SavedCardCvvField` (via its internal `SavedCardCvvInput`) registers the `'saved-card'` continuation\n * on T5's submission registry, closing over the re-entered CVV and the account reference, and calls\n * `$actions.payment.sendPayments` itself. T13's `usePlaceOrder` resolves and invokes it; this module\n * never returns the CVV, only `userErrors`.\n *\n * Selecting a row dispatches `usePayment().selectMethod({ methodId, accountId, bin })` — the\n * `bin` is this row's own projected BIN, never `usePayment().draft.bin`. The epic's and the RFC's\n * own text says the saved-card continuation closes over \"the account id and BIN\", which conflates\n * two different calls: `paymentMethodSelect` (this dispatch) sends the saved card's own BIN alongside\n * `accountId` — a `bin` that does not match the saved card's own BIN is rejected with\n * `MISMATCHED_BIN_FOR_SAVED_CARD`, verified against\n * `apps/storefront-api-core/src/graphql/modules/checkout/index.ts`. The gateway send (inside\n * `SavedCardCvvInput`, below) legitimately carries both, because it is a different wire contract\n * (spec Decision 2 has the full correction).\n */\n\n// ---------------------------------------------------------------------------\n// GraphQL Fragments — raw template literals, matching the `payment.tsx` sibling: the codegen\n// `documents` glob does not scan `src/matter/saved-cards*`. Both fragments drill the identical\n// `payment.savedCards` path and merge at compile time (ADR-0011/ADR-0012); `data-component-contract.\n// test.ts` validates each against the real schema.\n// ---------------------------------------------------------------------------\n\nconst fragment = /* GraphQL */ `\n fragment SavedCardsListData on Query {\n payment {\n __typename\n id\n savedCards {\n __typename\n id\n accountId\n methodId\n bin\n }\n }\n }\n`;\n\nconst optionFragment = /* GraphQL */ `\n fragment SavedCardOptionData on Query {\n payment {\n savedCards {\n __typename\n id\n accountId\n methodId\n network\n lastFourDigits\n bin\n label\n expired\n cvvRequired\n }\n }\n }\n`;\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** The minimal per-row identifiers `SavedCards` needs to iterate and to dispatch selection. */\nexport type SavedCardsListEntry = Readonly<{\n id: string;\n accountId: string;\n methodId: string;\n bin: string;\n}>;\n\nexport type SavedCardsData = {\n savedCards?: readonly SavedCardsListEntry[];\n} | null;\n\nexport type SavedCardsProps = ComponentPropsWithRef<'div'> & {\n /** Unwrapped `payment` value from the reactive runtime (arity-1 fragment, ADR-0015); `null` until resolved. */\n $data?: SavedCardsData;\n /** The per-row template, typically one `SavedCardOption`, repeated once per saved card. */\n children?: ReactNode;\n /**\n * The radio group's accessible name, rendered as a visible `Fieldset.Legend` (T38, Decision 1).\n * Resolved through `resolveAuthoredCopy`: an authored empty or whitespace-only string falls back to\n * the default rather than rendering a nameless group.\n */\n legend?: string;\n};\n\n/** The full per-row slice `SavedCardOption` independently resolves via `reactive-list-item`. */\nexport type SavedCardOptionSlice = Readonly<{\n accountId: string;\n methodId: string;\n network: string;\n lastFourDigits: string;\n bin: string;\n label: string | null;\n expired: boolean;\n cvvRequired: boolean;\n}>;\n\nexport type SavedCardOptionProps = ComponentPropsWithRef<'div'> & {\n /** Per-row slice from the reactive-list-item runtime; injected/overridden by the wrapper, or passed directly in tests. */\n $data?: SavedCardOptionSlice | null;\n /** Typically a `SavedCardCvvField`. */\n children?: ReactNode;\n};\n\nexport type SavedCardCvvFieldProps = ComponentPropsWithRef<'input'> & {\n /** Compiler-injected action surface; carries `payment.sendPayments`. */\n $actions?: ComponentActions;\n /**\n * Accessible name for the CVV input. No visible `<label>` renders (Accessibility Contract), so this\n * is the input's only accessible name — authorable rather than a hardcoded English string.\n */\n securityCodeLabel?: string;\n /**\n * Helper copy rendered as a sibling `<p>` and linked to the input via `aria-describedby` (this\n * component's root is the input itself, so there is no `Field.Root` to host a `Field.Description` —\n * saved-cards.md Decision 3). An empty string renders nothing. Copy configuration only — never the\n * security code itself.\n */\n securityCodeDescription?: string;\n};\n\n// ---------------------------------------------------------------------------\n// Saved-card-data context — provided by SavedCardOption, read only by SavedCardCvvField. A plain\n// React context, not a data-fetch strategy: SavedCardCvvField has no fragment of its own (Decision 1).\n// ---------------------------------------------------------------------------\n\nconst SavedCardDataContext = createContext<SavedCardOptionSlice | null>(null);\n\nfunction useSavedCardData(): SavedCardOptionSlice {\n const data = useContext(SavedCardDataContext);\n if (!data) {\n throw new Error('SavedCardCvvField must be used inside a <SavedCardOption> subtree.');\n }\n return data;\n}\n\n// ---------------------------------------------------------------------------\n// Definitions\n// ---------------------------------------------------------------------------\n\nexport const SavedCardsDefinition = defineComponent<SavedCardsProps>({\n name: 'SavedCards',\n importPath: '@vtex/faststore/components',\n component: SavedCards,\n fragment,\n fetchStrategy: 'reactive',\n requiresContext: 'payment',\n providesContext: 'saved-card',\n editor: { label: 'Saved Cards', category: 'commerce' },\n schema: {\n // T38: an inline literal, never an identifier — the native catalog builder extracts `schema`\n // statically and silently drops an identifier-valued default (INV-8).\n legend: { type: 'string', default: 'Saved cards', label: 'Legend' },\n },\n guideline:\n 'Radio list of the shopper\\'s saved cards (network, last four, label); expired cards render present but disabled, never selectable. Place a SavedCardOption inside as the per-card template, itself wrapping a SavedCardCvvField so the shopper can complete payment with a stored card — omitting the CVV field means a shopper can select a card but never pay with it. Picking a row dispatches the payment method selection by methodId and accountId only, never a card number or BIN, plus the inline selection error (role=\"alert\") if the server rejects it. \"Use a new card\" is CardEntry composed as a sibling, not a control inside this list. Renders one container div and no chrome of its own; always carries a loading/empty/ready state hook. Must be inside a PaymentSectionRoot or PaymentSectionController. See saved-cards.md.',\n});\n\nexport const SavedCardOptionDefinition = defineComponent<SavedCardOptionProps>({\n name: 'SavedCardOption',\n importPath: '@vtex/faststore/components',\n component: SavedCardOption,\n fragment: optionFragment,\n fetchStrategy: 'reactive-list-item',\n requiresContext: 'saved-card',\n providesContext: 'saved-card-data',\n editor: { label: 'Saved Card Option', category: 'commerce' },\n schema: {},\n guideline:\n 'One saved card as a radio row: network, masked last four, and its label when set; disabled when expired. Place a SavedCardCvvField inside so the CVV appears once this row is selected. Must be inside SavedCards.',\n});\n\nexport const SavedCardCvvFieldDefinition = defineComponent<SavedCardCvvFieldProps>({\n name: 'SavedCardCvvField',\n importPath: '@vtex/faststore/components',\n component: SavedCardCvvField,\n requiresContext: 'saved-card-data',\n actions: { payment: ['sendPayments'] },\n editor: { label: 'Saved Card CVV Field', category: 'commerce' },\n schema: {\n securityCodeLabel: { type: 'string', default: 'Security code', label: 'Security code label' },\n // en-US, per requirement 12.27's own quoted copy (spec mask-the-security-code-field-and.md,\n // OQ-1 Option B) — matches this component's existing en-US securityCodeLabel default.\n securityCodeDescription: {\n type: 'string',\n default: 'The 3-4 digit number on your card',\n label: 'Security code description',\n },\n },\n guideline:\n \"The CVV re-entry field for its saved card row; renders nothing until that row is selected and requires a CVV. Registers the direct-to-gateway submission for the saved-card path on selection — the CVV never transits our backend, only the gateway leg's userErrors return. Must be inside SavedCardOption.\",\n});\n\n// ---------------------------------------------------------------------------\n// Implementations\n// ---------------------------------------------------------------------------\n\n/** Mirrors `PaymentMethodList`'s own state hook, so an empty or loading region stays swatch-targetable. */\nfunction savedCardsState({ loading, count }: { loading: boolean; count: number }): 'loading' | 'empty' | 'ready' {\n if (loading) return 'loading';\n if (count === 0) return 'empty';\n return 'ready';\n}\n\n/**\n * The account a row should treat as \"the current selection.\" `selectedAccountId` is server state that\n * `selectNewCard()` never clears — only `draft.path` flips to `'new-card'` (`use-payment-method-\n * selection.ts`) — so reading `selectedAccountId` alone leaves the previously picked row wedged\n * checked, with its CVV field still mounted and registered, after the shopper picks \"use a new card\"\n * instead. Every read site in this module goes through this one function — the radio group's own\n * `value`, a row's `-selected` state, and the CVV field's visibility gate — so the three can never\n * drift out of sync with each other. Reads only `draft.path`, never `draft.bin`: that stays a fully\n * separate concern (Decision 2) that no call site in this module touches.\n */\nfunction resolveEffectiveSelectedAccountId(selectedAccountId: string | null, draftPath: string | null): string | null {\n return draftPath === 'new-card' ? null : selectedAccountId;\n}\n\nexport function SavedCards({ $data: $dataProp, legend, className, children, ...props }: SavedCardsProps) {\n const $data = useDataOverride($dataProp);\n // `draft` here only for `.path`, to resolve the effective selection below — never `.bin`. The\n // dispatch in handleValueChange still never reads `draft` at all; see its own comment.\n const { selectedAccountId, selectMethod, selectionError, draft, placing, cardPaymentCollapsed } = usePayment();\n const loading = $data == null;\n const savedCards = $data?.savedCards ?? [];\n const effectiveSelectedAccountId = resolveEffectiveSelectedAccountId(selectedAccountId, draft.path);\n\n const handleValueChange = useCallback(\n (accountId: string) => {\n const card = savedCards.find((entry) => entry.accountId === accountId);\n if (!card) return;\n // methodId, accountId, and this row's own bin — from this list's own read model, never\n // usePayment().draft.bin. draft.bin can be non-null on the saved-card path too (e.g.\n // Installments calling setDraftBin for its own BIN-keyed lookup; use-payment-method-selection.ts\n // does not clear it on this transition), but the bin sent here must be the saved card's own\n // so paymentMethodSelect can persist a BIN-precise selection. A bin that does not match the\n // saved card's own BIN is rejected with MISMATCHED_BIN_FOR_SAVED_CARD (spec Decision 2). The\n // gateway send (SavedCardCvvInput, below) is the different call that legitimately carries both\n // bin and accountId, reading bin from this row's own resolved slice, never the draft.\n selectMethod({\n methodId: card.methodId,\n accountId: card.accountId,\n ...(card.bin ? { bin: card.bin } : {}),\n });\n },\n [savedCards, selectMethod],\n );\n\n // T191 (FR-7, Decision 3): nothing renders while the card payment is collapsed — the card option brings\n // the list back. Returned after the last hook, so this instance stays mounted.\n if (cardPaymentCollapsed) return null;\n\n return (\n <div\n {...props}\n className={className}\n inert={placing}\n aria-disabled={placing || undefined}\n data-fs-saved-cards=\"\"\n data-fs-saved-cards-state={savedCardsState({ loading, count: savedCards.length })}\n {...(placing ? { 'data-fs-saved-cards-placing': '' } : {})}\n >\n {selectionError ? (\n <div role=\"alert\" data-fs-saved-cards-error=\"\">\n {selectionError}\n </div>\n ) : null}\n {savedCards.length > 0 ? (\n // T38, Decision 1: Fieldset.Root + Fieldset.Legend, exactly what card-entry.tsx:1101-1112\n // already ships — Base UI propagates the legend's id as aria-labelledby onto the descendant\n // RadioGroup, so this one wrapper names both the fieldset and the radiogroup (measured).\n <Fieldset.Root data-fs-saved-cards-fieldset=\"\">\n <Fieldset.Legend>{resolveAuthoredCopy(legend, 'Saved cards')}</Fieldset.Legend>\n <RadioGroup value={effectiveSelectedAccountId ?? ''} onValueChange={handleValueChange}>\n {savedCards.map((card) => (\n <ListItemRefContext\n key={card.id}\n // No fallback slice: this list's own fragment selects only { id, accountId, methodId }\n // (Decision 3), so it has no complete row to fall back to. An incomplete cache slice\n // should render nothing until it resolves, not a partial, silently-wrong row.\n value={{ __typename: 'SavedCard', id: card.id, fallbackData: undefined }}\n >\n {children}\n </ListItemRefContext>\n ))}\n </RadioGroup>\n </Fieldset.Root>\n ) : null}\n </div>\n );\n}\n\nexport function SavedCardOption({ $data: $dataProp, className, children, ...props }: SavedCardOptionProps) {\n // Delivery-agnostic: the slice arrives through the DataOverrideContext seam the reactive-list-item\n // wrapper publishes (or via $data in tests); this component calls no data hook of its own.\n const slice = useDataOverride<SavedCardOptionSlice | null | undefined>($dataProp) ?? null;\n const { selectedAccountId, draft } = usePayment();\n\n if (!slice) return null;\n\n // Path-aware, not just account-aware — see resolveEffectiveSelectedAccountId's own comment: without\n // this, a row that was picked before \"use a new card\" stays marked selected forever.\n const isSelected = slice.accountId === resolveEffectiveSelectedAccountId(selectedAccountId, draft.path);\n\n return (\n <SavedCardDataContext value={slice}>\n <div\n {...props}\n className={className}\n data-fs-saved-card-option=\"\"\n {...(isSelected ? { 'data-fs-saved-card-option-selected': '' } : {})}\n {...(slice.expired ? { 'data-fs-saved-card-option-expired': '' } : {})}\n >\n <RadioRoot value={slice.accountId} disabled={slice.expired}>\n <RadioIndicator data-fs-saved-card-option-indicator=\"\" />\n <span data-fs-saved-card-option-network=\"\">{slice.network}</span>\n <span data-fs-saved-card-option-last-four=\"\">{`•••• ${slice.lastFourDigits}`}</span>\n {slice.label ? <span data-fs-saved-card-option-label=\"\">{slice.label}</span> : null}\n </RadioRoot>\n {children}\n </div>\n </SavedCardDataContext>\n );\n}\n\nconst MISSING_ACTION_ERROR: PaymentUserError = {\n code: PAYMENT_NETWORK_ERROR_CODE,\n message: 'Unable to complete the request. Please try again.',\n};\n\n/**\n * The saved-card path's own client-synthesized refusal (T27, specs/place-order-preflight-card-\n * validation.md FR-11/FR-12; Decision 8). Distinct from `card-entry`'s `INVALID_CARD_DATA` (a\n * different path, a different set of fields, and importing it here would be a matter → matter\n * dependency) and from the gateway's own codes.\n *\n * Never name this constant, or its string value, inside `sdk/payment/use-place-order.ts`: it contains\n * `security` and would fail `CARD_FIELD_PATTERN`. The pre-flight only ever handles the opaque\n * `PaymentUserError` (spec NFR-1, Risk R9).\n */\nexport const SAVED_CARD_INVALID_SECURITY_CODE = 'INVALID_SECURITY_CODE';\n\nconst INVALID_SECURITY_CODE_ERROR: PaymentUserError = {\n code: SAVED_CARD_INVALID_SECURITY_CODE,\n message: 'Enter a valid security code before placing the order.',\n};\n\n/** VTEX's own CVV upper bound across the networks this codebase's masks cover (Decision 4). */\nconst CVV_MAX_LENGTH = 4;\n\nfunction sanitizeCvv(value: string): string {\n return value.replace(/\\D/g, '').slice(0, CVV_MAX_LENGTH);\n}\n\n/**\n * Three or four digits (T27, FR-11). `sanitizeCvv` already strips non-digits and caps at\n * `CVV_MAX_LENGTH`, so the only rule left is the floor — and three-or-four is `card-entry`'s own\n * fallback for a method that declares no `cvvRegex` (its `isValidCvv`, `/^\\d{3,4}$/`). Requiredness is\n * not checked here: `SavedCardCvvInput` renders only when the row is selected and `cvvRequired` is\n * true, so its presence *is* the requiredness signal.\n */\nconst SAVED_CARD_CVV_PATTERN = /^\\d{3,4}$/;\n\n/**\n * The length at which this field's security code counts as fully typed, and the mask engages (T173,\n * specs/mask-the-cvv-only-once-it.md, FR-3). Three — the floor of `SAVED_CARD_CVV_PATTERN` above,\n * for the same reason that pattern accepts either length: a saved card carries no per-network CVV\n * length. Its slice exposes a display `network` string, not a `cvvMask`, so nothing here tells a\n * 3-digit network apart from Amex's 4 the way `CardEntry` can, and inferring one from the display\n * name would hardcode a network table this package deliberately does not own.\n *\n * Masking at the floor is the side to err on: a complete code is never left legible on either kind\n * of card, and a 4-digit code is simply already masked when its last digit arrives.\n */\nconst SAVED_CARD_CVV_COMPLETE_LENGTH = 3;\n\ntype SavedCardCvvInputProps = ComponentPropsWithRef<'input'> & {\n /** `SavedCard.accountId` from this row's own resolved slice — never `usePayment().draft`. */\n accountId: string;\n /**\n * `SavedCard.bin` from this row's own resolved slice — the one this row is actually being paid\n * with. Deliberately **not** `usePayment().draft.bin`: that field can be populated on the\n * saved-card path too (e.g. Installments calling `setDraftBin` for its own BIN-keyed lookup;\n * `use-payment-method-selection.ts` does not clear it on this transition), so it can carry a\n * different card's BIN, or a stale one, at the moment this continuation runs. Forwarding it here\n * instead of the row's own field would be the same class of bug Decision 2 already closed for the\n * `selectMethod` dispatch — undetectable by type-checking, since both are plain `string`.\n */\n bin: string;\n /** Accessible name applied verbatim as `aria-label` — see `SavedCardCvvFieldProps`'s own doc comment. */\n securityCodeLabel: string;\n /** Helper copy rendered as a sibling `<p>` — see `SavedCardCvvFieldProps`'s own doc comment. Threaded\n * through exactly like `securityCodeLabel`. An empty string renders nothing. */\n securityCodeDescription: string;\n $actions?: ComponentActions;\n};\n\n/** Merges a caller-forwarded `aria-describedby` (from `...props`) with the description `<p>`'s own\n * id, rather than clobbering it — the accessibility wiring `Field.Root` would otherwise provide for\n * free (saved-cards.md Decision 3). */\nfunction mergeDescribedBy(existing: string | undefined, id: string): string {\n return existing ? `${existing} ${id}` : id;\n}\n\n/**\n * The stateful, registering half of the CVV field — split out from `SavedCardCvvField` so this row's\n * conditional visibility never skips a hook call (spec Decision 5). This component mounts wholesale\n * when its row is selected and requires a CVV, and unmounts wholesale otherwise, so its own hooks\n * always run the same number of times across its own lifetime; only its *presence* is conditional,\n * decided by its caller.\n */\nfunction SavedCardCvvInput({\n accountId,\n bin,\n securityCodeLabel,\n securityCodeDescription,\n $actions,\n ref: forwardedRef,\n ...props\n}: SavedCardCvvInputProps) {\n const [cvv, setCvv] = useState('');\n const inputRef = useRef<HTMLInputElement>(null);\n const sendPayments = $actions?.payment?.sendPayments;\n const descriptionId = useId();\n\n // Attaches both this component's own ref (needed to focus the field on refusal, in checkCvv below)\n // and whatever ref a caller already forwarded onto SavedCardCvvField/SavedCardCvvInput — `props`\n // spreads onto the element below and `SavedCardCvvInputProps` is `ComponentPropsWithRef<'input'>`, so\n // writing `ref={inputRef}` after the spread would silently drop a caller-forwarded ref instead.\n const setInputRef = useCallback(\n (node: HTMLInputElement | null) => {\n inputRef.current = node;\n\n if (typeof forwardedRef !== 'function') {\n if (forwardedRef) forwardedRef.current = node;\n return;\n }\n\n // React 19: a ref callback may itself return a cleanup function instead of being re-invoked\n // with `null` on detach. Preserve whichever form the caller used — never call a React-19\n // cleanup-style ref with `null`, a value that form is documented never to receive (fix pass,\n // review F2). A caller using the legacy form (no returned cleanup) keeps today's behaviour:\n // this callback returns nothing either, so React re-invokes it with `null` on detach, which\n // falls into this same branch and calls `forwardedRef(null)`, exactly as before.\n const cleanup = forwardedRef(node);\n if (typeof cleanup === 'function') {\n return () => {\n inputRef.current = null;\n cleanup();\n };\n }\n return;\n },\n [forwardedRef],\n );\n\n // The saved-card check (T27, FR-11/FR-12; Decision 8). Three-or-four digits — see\n // SAVED_CARD_CVV_PATTERN's own doc comment for why nothing narrower is checkable and nothing wider\n // is safe. The only field on this path, so there is no ordering rule and no inline error node to\n // fill (FR-12) — the message reaches the shopper through PlaceOrder's existing role=\"alert\".\n const checkCvv = useCallback<PaymentPreflightCheck>(() => {\n if (SAVED_CARD_CVV_PATTERN.test(cvv)) return [];\n inputRef.current?.focus();\n return [INVALID_SECURITY_CODE_ERROR];\n }, [cvv]);\n\n const submit = useCallback<PaymentSubmission>(\n async (transaction) => {\n // Same function the pre-flight calls (FR-3, applied to this path) — the two moments cannot\n // diverge here either.\n const refusals = checkCvv();\n if (refusals.length > 0) return refusals;\n if (!sendPayments) return [MISSING_ACTION_ERROR];\n // `bin` here is always the `accountId`/`bin` pair from this row's own props — see their doc\n // comments above for why `usePayment().draft.bin` must never substitute for either.\n return sendPayments({ transaction, card: { kind: 'saved', accountId, bin, securityCode: cvv } });\n },\n [checkCvv, sendPayments, accountId, bin, cvv],\n );\n\n useRegisterPaymentSubmission('saved-card', submit, checkCvv);\n\n // Merges with a caller-forwarded aria-describedby rather than clobbering it — mirrors the ref\n // merge above (setInputRef) for the same \"don't drop what a caller already passed\" reason.\n const ariaDescribedBy = securityCodeDescription\n ? mergeDescribedBy(props['aria-describedby'], descriptionId)\n : props['aria-describedby'];\n\n // T101 — the mask merges *over* a caller-forwarded style, inverted from this repo's usual\n // `...props.style`-last idiom (cart-item.tsx, product-list.tsx): deliberately, so a caller can\n // never remove the security-code mask. See security-code-mask.ts.\n //\n // T173 — that merge now happens from the keystroke that completes the code, not from the first\n // one, so a shopper can read back what they typed while they type it. Below the trigger the\n // caller's own style passes through untouched, which is the same thing it did before this field\n // ever had a mask; the guarantee above is about what a caller cannot do once the code is complete.\n const inputStyle = isSecurityCodeComplete(cvv, SAVED_CARD_CVV_COMPLETE_LENGTH)\n ? { ...props.style, ...SECURITY_CODE_MASK_STYLE }\n : props.style;\n\n return (\n <>\n <input\n {...props}\n ref={setInputRef}\n type=\"text\"\n inputMode=\"numeric\"\n autoComplete=\"off\"\n aria-label={securityCodeLabel}\n aria-describedby={ariaDescribedBy}\n value={cvv}\n onChange={(event) => setCvv(sanitizeCvv(event.target.value))}\n data-fs-saved-card-cvv-field=\"\"\n style={inputStyle}\n />\n {securityCodeDescription ? (\n <p id={descriptionId} data-fs-saved-card-cvv-description=\"\">\n {securityCodeDescription}\n </p>\n ) : null}\n </>\n );\n}\n\nexport function SavedCardCvvField({\n $actions: $actionsProp,\n securityCodeLabel = 'Security code',\n securityCodeDescription = 'The 3-4 digit number on your card',\n className,\n ...props\n}: SavedCardCvvFieldProps) {\n const $actions = useActionsOverride($actionsProp);\n const { accountId, bin, cvvRequired } = useSavedCardData();\n const { selectedAccountId, draft } = usePayment();\n\n // Path-aware, matching SavedCardOption's own isSelected — otherwise this field stays mounted and\n // registered after \"use a new card\", with selectedAccountId still equal to this row's own accountId.\n if (accountId !== resolveEffectiveSelectedAccountId(selectedAccountId, draft.path) || !cvvRequired) {\n return null;\n }\n\n return (\n <SavedCardCvvInput\n {...props}\n className={className}\n accountId={accountId}\n bin={bin}\n securityCodeLabel={securityCodeLabel}\n securityCodeDescription={securityCodeDescription}\n $actions={$actions}\n />\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"saved-cards.js","names":[],"sources":["../../../src/matter/saved-cards/saved-cards.tsx"],"sourcesContent":["'use client';\n\nimport { defineComponent } from '@vtex/fs-core';\nimport {\n type ComponentPropsWithRef,\n createContext,\n type ReactNode,\n useCallback,\n useContext,\n useId,\n useRef,\n useState,\n} from 'react';\nimport { Fieldset } from '../../particles/fieldset/fieldset.js';\nimport { RadioGroup, RadioIndicator, RadioRoot } from '../../particles/radio/radio.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 { ListItemRefContext } from '../../sdk/data/list-item-ref-context.js';\nimport { PAYMENT_NETWORK_ERROR_CODE, type PaymentUserError } from '../../sdk/payment/operations.js';\nimport { isSecurityCodeComplete, SECURITY_CODE_MASK_STYLE } from '../../sdk/payment/security-code-mask.js';\nimport {\n type PaymentPreflightCheck,\n type PaymentSubmission,\n useRegisterPaymentSubmission,\n} from '../../sdk/payment/submission-registry.js';\nimport { resolveAuthoredCopy } from '../../utils/resolve-authored-copy.js';\nimport { usePayment } from '../payment/payment.js';\n\n/**\n * Pick a saved card and enter only the CVV (specs/saved-cards.md, T12 of the Credit Card epic;\n * docs/features/credit-card/rfc.md § D7). Three components, split by data-fetch role rather than by\n * visual grouping (ADR-0012 Option B, per `SavedCard`'s own schema doc comment):\n *\n * - `SavedCards` — the list root. Owns the minimal `{ id, accountId, methodId }` selection needed to\n * iterate and to dispatch selection; publishes each row's entity ref via `ListItemRefContext`,\n * mirroring `CartList`.\n * - `SavedCardOption` — the per-row template. Independently subscribes to its own `SavedCard:<id>`\n * cache entity through the shipped `reactive-list-item` runtime, mirroring `CartItemRoot`.\n * - `SavedCardCvvField` — the one PCI-bearing, action-dispatching leaf. Declares no `fetchStrategy` of\n * its own; it reads its row through `SavedCardOption`'s plain context, mirroring `CartItemRemove`'s\n * split from `CartItemRoot` (spec Decision 1).\n *\n * The submission continuation is the PCI-critical core (epic § \"The card-data submission channel\"):\n * `SavedCardCvvField` (via its internal `SavedCardCvvInput`) registers the `'saved-card'` continuation\n * on T5's submission registry, closing over the re-entered CVV and the account reference, and calls\n * `$actions.payment.sendPayments` itself. T13's `usePlaceOrder` resolves and invokes it; this module\n * never returns the CVV, only `userErrors`.\n *\n * Selecting a row dispatches `usePayment().selectMethod({ methodId, accountId, bin })` — the\n * `bin` is this row's own projected BIN, never `usePayment().draft.bin`. The epic's and the RFC's\n * own text says the saved-card continuation closes over \"the account id and BIN\", which conflates\n * two different calls: `paymentMethodSelect` (this dispatch) sends the saved card's own BIN alongside\n * `accountId` — a `bin` that does not match the saved card's own BIN is rejected with\n * `MISMATCHED_BIN_FOR_SAVED_CARD`, verified against\n * `apps/storefront-api-core/src/graphql/modules/checkout/index.ts`. The gateway send (inside\n * `SavedCardCvvInput`, below) legitimately carries both, because it is a different wire contract\n * (spec Decision 2 has the full correction).\n */\n\n// ---------------------------------------------------------------------------\n// GraphQL Fragments — raw template literals, matching the `payment.tsx` sibling: the codegen\n// `documents` glob does not scan `src/matter/saved-cards*`. Both fragments drill the identical\n// `payment.savedCards` path and merge at compile time (ADR-0011/ADR-0012); `data-component-contract.\n// test.ts` validates each against the real schema.\n// ---------------------------------------------------------------------------\n\nconst fragment = /* GraphQL */ `\n fragment SavedCardsListData on Query {\n payment {\n __typename\n id\n savedCards {\n __typename\n id\n accountId\n methodId\n bin\n }\n }\n }\n`;\n\nconst optionFragment = /* GraphQL */ `\n fragment SavedCardOptionData on Query {\n payment {\n savedCards {\n __typename\n id\n accountId\n methodId\n network\n lastFourDigits\n bin\n label\n expired\n cvvRequired\n }\n }\n }\n`;\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** The minimal per-row identifiers `SavedCards` needs to iterate and to dispatch selection. */\nexport type SavedCardsListEntry = Readonly<{\n id: string;\n accountId: string;\n methodId: string;\n bin: string;\n}>;\n\nexport type SavedCardsData = {\n savedCards?: readonly SavedCardsListEntry[];\n} | null;\n\nexport type SavedCardsProps = ComponentPropsWithRef<'div'> & {\n /** Unwrapped `payment` value from the reactive runtime (arity-1 fragment, ADR-0015); `null` until resolved. */\n $data?: SavedCardsData;\n /** The per-row template, typically one `SavedCardOption`, repeated once per saved card. */\n children?: ReactNode;\n /**\n * The radio group's accessible name, rendered as a visible `Fieldset.Legend` (T38, Decision 1).\n * Resolved through `resolveAuthoredCopy`: an authored empty or whitespace-only string falls back to\n * the default rather than rendering a nameless group.\n */\n legend?: string;\n};\n\n/** The full per-row slice `SavedCardOption` independently resolves via `reactive-list-item`. */\nexport type SavedCardOptionSlice = Readonly<{\n accountId: string;\n methodId: string;\n network: string;\n lastFourDigits: string;\n bin: string;\n label: string | null;\n expired: boolean;\n cvvRequired: boolean;\n}>;\n\nexport type SavedCardOptionProps = ComponentPropsWithRef<'div'> & {\n /** Per-row slice from the reactive-list-item runtime; injected/overridden by the wrapper, or passed directly in tests. */\n $data?: SavedCardOptionSlice | null;\n /** Typically a `SavedCardCvvField`. */\n children?: ReactNode;\n};\n\nexport type SavedCardCvvFieldProps = ComponentPropsWithRef<'input'> & {\n /** Compiler-injected action surface; carries `payment.sendPayments`. */\n $actions?: ComponentActions;\n /**\n * Accessible name for the CVV input. No visible `<label>` renders (Accessibility Contract), so this\n * is the input's only accessible name — authorable rather than a hardcoded English string.\n */\n securityCodeLabel?: string;\n /**\n * Helper copy rendered as a sibling `<p>` and linked to the input via `aria-describedby` (this\n * component's root is the input itself, so there is no `Field.Root` to host a `Field.Description` —\n * saved-cards.md Decision 3). An empty string renders nothing. Copy configuration only — never the\n * security code itself.\n */\n securityCodeDescription?: string;\n};\n\n// ---------------------------------------------------------------------------\n// Saved-card-data context — provided by SavedCardOption, read only by SavedCardCvvField. A plain\n// React context, not a data-fetch strategy: SavedCardCvvField has no fragment of its own (Decision 1).\n// ---------------------------------------------------------------------------\n\nconst SavedCardDataContext = createContext<SavedCardOptionSlice | null>(null);\n\nfunction useSavedCardData(): SavedCardOptionSlice {\n const data = useContext(SavedCardDataContext);\n if (!data) {\n throw new Error('SavedCardCvvField must be used inside a <SavedCardOption> subtree.');\n }\n return data;\n}\n\n// ---------------------------------------------------------------------------\n// Definitions\n// ---------------------------------------------------------------------------\n\nexport const SavedCardsDefinition = defineComponent<SavedCardsProps>({\n name: 'SavedCards',\n importPath: '@vtex/faststore/components',\n component: SavedCards,\n fragment,\n fetchStrategy: 'reactive',\n requiresContext: 'payment',\n providesContext: 'saved-card',\n editor: { label: 'Saved Cards', category: 'commerce' },\n schema: {\n // T38: an inline literal, never an identifier — the native catalog builder extracts `schema`\n // statically and silently drops an identifier-valued default (INV-8).\n legend: { type: 'string', default: 'Saved cards', label: 'Legend' },\n },\n guideline:\n 'Radio list of the shopper\\'s saved cards (network, last four, label); expired cards render present but disabled, never selectable. Place a SavedCardOption inside as the per-card template, itself wrapping a SavedCardCvvField so the shopper can complete payment with a stored card — omitting the CVV field means a shopper can select a card but never pay with it. Picking a row dispatches the payment method selection by methodId and accountId only, never a card number or BIN, plus the inline selection error (role=\"alert\") if the server rejects it. \"Use a new card\" is CardEntry composed as a sibling, not a control inside this list. Renders one container div and no chrome of its own; always carries a loading/empty/ready state hook. Must be inside a PaymentSectionRoot or PaymentSectionController. See saved-cards.md.',\n});\n\nexport const SavedCardOptionDefinition = defineComponent<SavedCardOptionProps>({\n name: 'SavedCardOption',\n importPath: '@vtex/faststore/components',\n component: SavedCardOption,\n fragment: optionFragment,\n fetchStrategy: 'reactive-list-item',\n requiresContext: 'saved-card',\n providesContext: 'saved-card-data',\n editor: { label: 'Saved Card Option', category: 'commerce' },\n schema: {},\n guideline:\n 'One saved card as a radio row: network, masked last four, and its label when set; disabled when expired. Place a SavedCardCvvField inside so the CVV appears once this row is selected. Must be inside SavedCards.',\n});\n\nexport const SavedCardCvvFieldDefinition = defineComponent<SavedCardCvvFieldProps>({\n name: 'SavedCardCvvField',\n importPath: '@vtex/faststore/components',\n component: SavedCardCvvField,\n requiresContext: 'saved-card-data',\n actions: { payment: ['sendPayments'] },\n hardened: true,\n editor: { label: 'Saved Card CVV Field', category: 'commerce' },\n schema: {\n securityCodeLabel: { type: 'string', default: 'Security code', label: 'Security code label' },\n // en-US, per requirement 12.27's own quoted copy (spec mask-the-security-code-field-and.md,\n // OQ-1 Option B) — matches this component's existing en-US securityCodeLabel default.\n securityCodeDescription: {\n type: 'string',\n default: 'The 3-4 digit number on your card',\n label: 'Security code description',\n },\n },\n guideline:\n \"The CVV re-entry field for its saved card row; renders nothing until that row is selected and requires a CVV. Registers the direct-to-gateway submission for the saved-card path on selection — the CVV never transits our backend, only the gateway leg's userErrors return. Must be inside SavedCardOption.\",\n});\n\n// ---------------------------------------------------------------------------\n// Implementations\n// ---------------------------------------------------------------------------\n\n/** Mirrors `PaymentMethodList`'s own state hook, so an empty or loading region stays swatch-targetable. */\nfunction savedCardsState({ loading, count }: { loading: boolean; count: number }): 'loading' | 'empty' | 'ready' {\n if (loading) return 'loading';\n if (count === 0) return 'empty';\n return 'ready';\n}\n\n/**\n * The account a row should treat as \"the current selection.\" `selectedAccountId` is server state that\n * `selectNewCard()` never clears — only `draft.path` flips to `'new-card'` (`use-payment-method-\n * selection.ts`) — so reading `selectedAccountId` alone leaves the previously picked row wedged\n * checked, with its CVV field still mounted and registered, after the shopper picks \"use a new card\"\n * instead. Every read site in this module goes through this one function — the radio group's own\n * `value`, a row's `-selected` state, and the CVV field's visibility gate — so the three can never\n * drift out of sync with each other. Reads only `draft.path`, never `draft.bin`: that stays a fully\n * separate concern (Decision 2) that no call site in this module touches.\n */\nfunction resolveEffectiveSelectedAccountId(selectedAccountId: string | null, draftPath: string | null): string | null {\n return draftPath === 'new-card' ? null : selectedAccountId;\n}\n\nexport function SavedCards({ $data: $dataProp, legend, className, children, ...props }: SavedCardsProps) {\n const $data = useDataOverride($dataProp);\n // `draft` here only for `.path`, to resolve the effective selection below — never `.bin`. The\n // dispatch in handleValueChange still never reads `draft` at all; see its own comment.\n const {\n selectedAccountId,\n selectMethod,\n selectionError,\n draft,\n placing: placingNow,\n attempt,\n cardPaymentCollapsed,\n } = usePayment();\n // T202: frozen while the order attempt is resume-only too (`sent` / `unresolved`): the card may already have\n // been sent and `placeOrder` only resumes, so a security code typed here would never be submitted or cleared.\n const placing = placingNow || attempt?.phase === 'sent' || attempt?.phase === 'unresolved';\n const loading = $data == null;\n const savedCards = $data?.savedCards ?? [];\n const effectiveSelectedAccountId = resolveEffectiveSelectedAccountId(selectedAccountId, draft.path);\n\n const handleValueChange = useCallback(\n (accountId: string) => {\n const card = savedCards.find((entry) => entry.accountId === accountId);\n if (!card) return;\n // methodId, accountId, and this row's own bin — from this list's own read model, never\n // usePayment().draft.bin. draft.bin can be non-null on the saved-card path too (e.g.\n // Installments calling setDraftBin for its own BIN-keyed lookup; use-payment-method-selection.ts\n // does not clear it on this transition), but the bin sent here must be the saved card's own\n // so paymentMethodSelect can persist a BIN-precise selection. A bin that does not match the\n // saved card's own BIN is rejected with MISMATCHED_BIN_FOR_SAVED_CARD (spec Decision 2). The\n // gateway send (SavedCardCvvInput, below) is the different call that legitimately carries both\n // bin and accountId, reading bin from this row's own resolved slice, never the draft.\n selectMethod({\n methodId: card.methodId,\n accountId: card.accountId,\n ...(card.bin ? { bin: card.bin } : {}),\n });\n },\n [savedCards, selectMethod],\n );\n\n // T191 (FR-7, Decision 3): nothing renders while the card payment is collapsed — the card option brings\n // the list back. Returned after the last hook, so this instance stays mounted.\n if (cardPaymentCollapsed) return null;\n\n return (\n <div\n {...props}\n className={className}\n inert={placing}\n aria-disabled={placing || undefined}\n data-fs-saved-cards=\"\"\n data-fs-saved-cards-state={savedCardsState({ loading, count: savedCards.length })}\n {...(placing ? { 'data-fs-saved-cards-placing': '' } : {})}\n >\n {selectionError ? (\n <div role=\"alert\" data-fs-saved-cards-error=\"\">\n {selectionError}\n </div>\n ) : null}\n {savedCards.length > 0 ? (\n // T38, Decision 1: Fieldset.Root + Fieldset.Legend, exactly what card-entry.tsx:1101-1112\n // already ships — Base UI propagates the legend's id as aria-labelledby onto the descendant\n // RadioGroup, so this one wrapper names both the fieldset and the radiogroup (measured).\n <Fieldset.Root data-fs-saved-cards-fieldset=\"\">\n <Fieldset.Legend>{resolveAuthoredCopy(legend, 'Saved cards')}</Fieldset.Legend>\n <RadioGroup value={effectiveSelectedAccountId ?? ''} onValueChange={handleValueChange}>\n {savedCards.map((card) => (\n <ListItemRefContext\n key={card.id}\n // No fallback slice: this list's own fragment selects only { id, accountId, methodId }\n // (Decision 3), so it has no complete row to fall back to. An incomplete cache slice\n // should render nothing until it resolves, not a partial, silently-wrong row.\n value={{ __typename: 'SavedCard', id: card.id, fallbackData: undefined }}\n >\n {children}\n </ListItemRefContext>\n ))}\n </RadioGroup>\n </Fieldset.Root>\n ) : null}\n </div>\n );\n}\n\nexport function SavedCardOption({ $data: $dataProp, className, children, ...props }: SavedCardOptionProps) {\n // Delivery-agnostic: the slice arrives through the DataOverrideContext seam the reactive-list-item\n // wrapper publishes (or via $data in tests); this component calls no data hook of its own.\n const slice = useDataOverride<SavedCardOptionSlice | null | undefined>($dataProp) ?? null;\n const { selectedAccountId, draft } = usePayment();\n\n if (!slice) return null;\n\n // Path-aware, not just account-aware — see resolveEffectiveSelectedAccountId's own comment: without\n // this, a row that was picked before \"use a new card\" stays marked selected forever.\n const isSelected = slice.accountId === resolveEffectiveSelectedAccountId(selectedAccountId, draft.path);\n\n return (\n <SavedCardDataContext value={slice}>\n <div\n {...props}\n className={className}\n data-fs-saved-card-option=\"\"\n {...(isSelected ? { 'data-fs-saved-card-option-selected': '' } : {})}\n {...(slice.expired ? { 'data-fs-saved-card-option-expired': '' } : {})}\n >\n <RadioRoot value={slice.accountId} disabled={slice.expired}>\n <RadioIndicator data-fs-saved-card-option-indicator=\"\" />\n <span data-fs-saved-card-option-network=\"\">{slice.network}</span>\n <span data-fs-saved-card-option-last-four=\"\">{`•••• ${slice.lastFourDigits}`}</span>\n {slice.label ? <span data-fs-saved-card-option-label=\"\">{slice.label}</span> : null}\n </RadioRoot>\n {children}\n </div>\n </SavedCardDataContext>\n );\n}\n\nconst MISSING_ACTION_ERROR: PaymentUserError = {\n code: PAYMENT_NETWORK_ERROR_CODE,\n message: 'Unable to complete the request. Please try again.',\n};\n\n/**\n * The saved-card path's own client-synthesized refusal (T27, specs/place-order-preflight-card-\n * validation.md FR-11/FR-12; Decision 8). Distinct from `card-entry`'s `INVALID_CARD_DATA` (a\n * different path, a different set of fields, and importing it here would be a matter → matter\n * dependency) and from the gateway's own codes.\n *\n * Never name this constant, or its string value, inside `sdk/payment/use-place-order.ts`: it contains\n * `security` and would fail `CARD_FIELD_PATTERN`. The pre-flight only ever handles the opaque\n * `PaymentUserError` (spec NFR-1, Risk R9).\n */\nexport const SAVED_CARD_INVALID_SECURITY_CODE = 'INVALID_SECURITY_CODE';\n\nconst INVALID_SECURITY_CODE_ERROR: PaymentUserError = {\n code: SAVED_CARD_INVALID_SECURITY_CODE,\n message: 'Enter a valid security code before placing the order.',\n};\n\n/** VTEX's own CVV upper bound across the networks this codebase's masks cover (Decision 4). */\nconst CVV_MAX_LENGTH = 4;\n\nfunction sanitizeCvv(value: string): string {\n return value.replace(/\\D/g, '').slice(0, CVV_MAX_LENGTH);\n}\n\n/**\n * Three or four digits (T27, FR-11). `sanitizeCvv` already strips non-digits and caps at\n * `CVV_MAX_LENGTH`, so the only rule left is the floor — and three-or-four is `card-entry`'s own\n * fallback for a method that declares no `cvvRegex` (its `isValidCvv`, `/^\\d{3,4}$/`). Requiredness is\n * not checked here: `SavedCardCvvInput` renders only when the row is selected and `cvvRequired` is\n * true, so its presence *is* the requiredness signal.\n */\nconst SAVED_CARD_CVV_PATTERN = /^\\d{3,4}$/;\n\n/**\n * The length at which this field's security code counts as fully typed, and the mask engages (T173,\n * specs/mask-the-cvv-only-once-it.md, FR-3). Three — the floor of `SAVED_CARD_CVV_PATTERN` above,\n * for the same reason that pattern accepts either length: a saved card carries no per-network CVV\n * length. Its slice exposes a display `network` string, not a `cvvMask`, so nothing here tells a\n * 3-digit network apart from Amex's 4 the way `CardEntry` can, and inferring one from the display\n * name would hardcode a network table this package deliberately does not own.\n *\n * Masking at the floor is the side to err on: a complete code is never left legible on either kind\n * of card, and a 4-digit code is simply already masked when its last digit arrives.\n */\nconst SAVED_CARD_CVV_COMPLETE_LENGTH = 3;\n\ntype SavedCardCvvInputProps = ComponentPropsWithRef<'input'> & {\n /** `SavedCard.accountId` from this row's own resolved slice — never `usePayment().draft`. */\n accountId: string;\n /**\n * `SavedCard.bin` from this row's own resolved slice — the one this row is actually being paid\n * with. Deliberately **not** `usePayment().draft.bin`: that field can be populated on the\n * saved-card path too (e.g. Installments calling `setDraftBin` for its own BIN-keyed lookup;\n * `use-payment-method-selection.ts` does not clear it on this transition), so it can carry a\n * different card's BIN, or a stale one, at the moment this continuation runs. Forwarding it here\n * instead of the row's own field would be the same class of bug Decision 2 already closed for the\n * `selectMethod` dispatch — undetectable by type-checking, since both are plain `string`.\n */\n bin: string;\n /** Accessible name applied verbatim as `aria-label` — see `SavedCardCvvFieldProps`'s own doc comment. */\n securityCodeLabel: string;\n /** Helper copy rendered as a sibling `<p>` — see `SavedCardCvvFieldProps`'s own doc comment. Threaded\n * through exactly like `securityCodeLabel`. An empty string renders nothing. */\n securityCodeDescription: string;\n $actions?: ComponentActions;\n};\n\n/** Merges a caller-forwarded `aria-describedby` (from `...props`) with the description `<p>`'s own\n * id, rather than clobbering it — the accessibility wiring `Field.Root` would otherwise provide for\n * free (saved-cards.md Decision 3). */\nfunction mergeDescribedBy(existing: string | undefined, id: string): string {\n return existing ? `${existing} ${id}` : id;\n}\n\n/**\n * The stateful, registering half of the CVV field — split out from `SavedCardCvvField` so this row's\n * conditional visibility never skips a hook call (spec Decision 5). This component mounts wholesale\n * when its row is selected and requires a CVV, and unmounts wholesale otherwise, so its own hooks\n * always run the same number of times across its own lifetime; only its *presence* is conditional,\n * decided by its caller.\n */\nfunction SavedCardCvvInput({\n accountId,\n bin,\n securityCodeLabel,\n securityCodeDescription,\n $actions,\n ref: forwardedRef,\n ...props\n}: SavedCardCvvInputProps) {\n const [cvv, setCvv] = useState('');\n const inputRef = useRef<HTMLInputElement>(null);\n const sendPayments = $actions?.payment?.sendPayments;\n const descriptionId = useId();\n\n // Attaches both this component's own ref (needed to focus the field on refusal, in checkCvv below)\n // and whatever ref a caller already forwarded onto SavedCardCvvField/SavedCardCvvInput — `props`\n // spreads onto the element below and `SavedCardCvvInputProps` is `ComponentPropsWithRef<'input'>`, so\n // writing `ref={inputRef}` after the spread would silently drop a caller-forwarded ref instead.\n const setInputRef = useCallback(\n (node: HTMLInputElement | null) => {\n inputRef.current = node;\n\n if (typeof forwardedRef !== 'function') {\n if (forwardedRef) forwardedRef.current = node;\n return;\n }\n\n // React 19: a ref callback may itself return a cleanup function instead of being re-invoked\n // with `null` on detach. Preserve whichever form the caller used — never call a React-19\n // cleanup-style ref with `null`, a value that form is documented never to receive (fix pass,\n // review F2). A caller using the legacy form (no returned cleanup) keeps today's behaviour:\n // this callback returns nothing either, so React re-invokes it with `null` on detach, which\n // falls into this same branch and calls `forwardedRef(null)`, exactly as before.\n const cleanup = forwardedRef(node);\n if (typeof cleanup === 'function') {\n return () => {\n inputRef.current = null;\n cleanup();\n };\n }\n return;\n },\n [forwardedRef],\n );\n\n // The saved-card check (T27, FR-11/FR-12; Decision 8). Three-or-four digits — see\n // SAVED_CARD_CVV_PATTERN's own doc comment for why nothing narrower is checkable and nothing wider\n // is safe. The only field on this path, so there is no ordering rule and no inline error node to\n // fill (FR-12) — the message reaches the shopper through PlaceOrder's existing role=\"alert\".\n const checkCvv = useCallback<PaymentPreflightCheck>(() => {\n if (SAVED_CARD_CVV_PATTERN.test(cvv)) return [];\n inputRef.current?.focus();\n return [INVALID_SECURITY_CODE_ERROR];\n }, [cvv]);\n\n const submit = useCallback<PaymentSubmission>(\n async (transaction) => {\n // Same function the pre-flight calls (FR-3, applied to this path) — the two moments cannot\n // diverge here either.\n // Both refusals precede any request, so each reports `reachedGateway: false` (T112): the attempt is\n // freed rather than left resume-only on a card that was never sent.\n const refusals = checkCvv();\n if (refusals.length > 0) return { reachedGateway: false, userErrors: refusals };\n // T202: past the field's own check the code is not needed again, whatever the outcome. A throw is\n // re-thrown untouched; the `finally` is the one place certain to run.\n try {\n if (!sendPayments) return { reachedGateway: false, userErrors: [MISSING_ACTION_ERROR] };\n // `bin` here is always the `accountId`/`bin` pair from this row's own props — see their doc\n // comments above for why `usePayment().draft.bin` must never substitute for either.\n return await sendPayments({ transaction, card: { kind: 'saved', accountId, bin, securityCode: cvv } });\n } finally {\n setCvv('');\n }\n },\n [checkCvv, sendPayments, accountId, bin, cvv],\n );\n\n useRegisterPaymentSubmission('saved-card', submit, checkCvv);\n\n // Merges with a caller-forwarded aria-describedby rather than clobbering it — mirrors the ref\n // merge above (setInputRef) for the same \"don't drop what a caller already passed\" reason.\n const ariaDescribedBy = securityCodeDescription\n ? mergeDescribedBy(props['aria-describedby'], descriptionId)\n : props['aria-describedby'];\n\n // T101 — the mask merges *over* a caller-forwarded style, inverted from this repo's usual\n // `...props.style`-last idiom (cart-item.tsx, product-list.tsx): deliberately, so a caller can\n // never remove the security-code mask. See security-code-mask.ts.\n //\n // T173 — that merge now happens from the keystroke that completes the code, not from the first\n // one, so a shopper can read back what they typed while they type it. Below the trigger the\n // caller's own style passes through untouched, which is the same thing it did before this field\n // ever had a mask; the guarantee above is about what a caller cannot do once the code is complete.\n const inputStyle = isSecurityCodeComplete(cvv, SAVED_CARD_CVV_COMPLETE_LENGTH)\n ? { ...props.style, ...SECURITY_CODE_MASK_STYLE }\n : props.style;\n\n return (\n <>\n <input\n {...props}\n ref={setInputRef}\n type=\"text\"\n inputMode=\"numeric\"\n autoComplete=\"off\"\n aria-label={securityCodeLabel}\n aria-describedby={ariaDescribedBy}\n value={cvv}\n onChange={(event) => setCvv(sanitizeCvv(event.target.value))}\n data-fs-saved-card-cvv-field=\"\"\n style={inputStyle}\n />\n {securityCodeDescription ? (\n <p id={descriptionId} data-fs-saved-card-cvv-description=\"\">\n {securityCodeDescription}\n </p>\n ) : null}\n </>\n );\n}\n\nexport function SavedCardCvvField({\n $actions: $actionsProp,\n securityCodeLabel = 'Security code',\n securityCodeDescription = 'The 3-4 digit number on your card',\n className,\n ...props\n}: SavedCardCvvFieldProps) {\n const $actions = useActionsOverride($actionsProp);\n const { accountId, bin, cvvRequired } = useSavedCardData();\n const { selectedAccountId, draft } = usePayment();\n\n // Path-aware, matching SavedCardOption's own isSelected — otherwise this field stays mounted and\n // registered after \"use a new card\", with selectedAccountId still equal to this row's own accountId.\n if (accountId !== resolveEffectiveSelectedAccountId(selectedAccountId, draft.path) || !cvvRequired) {\n return null;\n }\n\n return (\n <SavedCardCvvInput\n {...props}\n className={className}\n accountId={accountId}\n bin={bin}\n securityCodeLabel={securityCodeLabel}\n securityCodeDescription={securityCodeDescription}\n $actions={$actions}\n />\n );\n}\n"],"mappings":"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"}
|
|
@@ -4,7 +4,7 @@ import "../../core/dist/index.js";
|
|
|
4
4
|
import { graphql } from "../../sdk/generated/gql.js";
|
|
5
5
|
import { useDataOverride } from "../../sdk/data/data-override-context.js";
|
|
6
6
|
import { createContext, useContext, useMemo } from "react";
|
|
7
|
-
import { Fragment, jsx } from "react/jsx-runtime";
|
|
7
|
+
import { Fragment as Fragment$1, jsx } from "react/jsx-runtime";
|
|
8
8
|
//#region src/matter/session/session.tsx
|
|
9
9
|
const fragment = graphql(`
|
|
10
10
|
fragment SessionData on Query {
|
|
@@ -100,7 +100,7 @@ function SessionRoot({ $data: $dataProp, children }) {
|
|
|
100
100
|
function SessionGuard({ when, children }) {
|
|
101
101
|
const { state } = useSession();
|
|
102
102
|
if (!when?.includes(state)) return null;
|
|
103
|
-
return /* @__PURE__ */ jsx(Fragment, { children });
|
|
103
|
+
return /* @__PURE__ */ jsx(Fragment$1, { children });
|
|
104
104
|
}
|
|
105
105
|
const Session = {
|
|
106
106
|
Root: SessionRoot,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.js","names":[],"sources":["../../../src/matter/session/session.tsx"],"sourcesContent":["'use client';\n\nimport { defineComponent } from '@vtex/fs-core';\nimport { createContext, type ReactNode, useContext, useMemo } from 'react';\nimport { useDataOverride } from '../../sdk/data/data-override-context.js';\nimport { graphql } from '../../sdk/generated/gql.js';\nimport type { SessionEntity } from '../../sdk/session/operations.js';\n\n// ---------------------------------------------------------------------------\n// GraphQL Fragment — typed via @graphql-codegen client-preset\n// ---------------------------------------------------------------------------\n\nconst fragment = graphql(`\n fragment SessionData on Query {\n session {\n ...SessionFragment\n }\n }\n`);\n\n// ---------------------------------------------------------------------------\n// Tri-state derivation — the single home of the truth table (Decision 2).\n// ---------------------------------------------------------------------------\n\nexport type SessionState = 'anonymous' | 'identified' | 'loggedIn';\n\n/**\n * Derive the identity tri-state from the two raw session flags. `loggedIn` takes precedence: an\n * authenticated shopper is always `loggedIn` regardless of `isIdentified`.\n */\nexport function deriveSessionState({\n loggedIn,\n isIdentified,\n}: {\n loggedIn: boolean;\n isIdentified: boolean;\n}): SessionState {\n if (loggedIn) return 'loggedIn';\n if (isIdentified) return 'identified';\n return 'anonymous';\n}\n\n// ---------------------------------------------------------------------------\n// Session context (read-only derived state + raw flags)\n// ---------------------------------------------------------------------------\n\nexport type SessionContextValue = Readonly<{\n /** Derived tri-state: anonymous | identified | loggedIn. */\n state: SessionState;\n /** Raw authenticated-session flag from `session.loggedIn`. */\n loggedIn: boolean;\n /** Raw edit-permission flag from `session.canEditData`; lets Controls render editable vs. masked. */\n canEditData: boolean;\n /** True while the Session entity has not yet resolved. */\n loading: boolean;\n}>;\n\nconst SESSION_DATA_UNSET = Symbol('SessionContextUnset');\n\nconst SessionContextInternal = createContext<SessionContextValue | typeof SESSION_DATA_UNSET>(SESSION_DATA_UNSET);\n\nexport const SessionContext = SessionContextInternal as React.Context<SessionContextValue>;\n\n/**\n * Read the derived session state and raw flags from the nearest `SessionRoot`. Throws when called\n * outside a `SessionRoot` subtree (a misplaced `useSession()` is a composition bug the author should\n * see immediately, not a silent degrade to anonymous), mirroring `useCart()` (Decision 6).\n *\n * While the Session entity is unresolved the context carries `loading: true` and `state:\n * 'anonymous'` (the least-privileged default); consumers that must not flash gate on `loading`.\n */\nexport function useSession(): SessionContextValue {\n const ctx = useContext(SessionContextInternal);\n if (ctx === SESSION_DATA_UNSET) {\n throw new Error('useSession() must be used inside a <SessionRoot> subtree.');\n }\n return ctx;\n}\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type SessionRootProps = {\n /**\n * Session entity slice forwarded by the reactive runtime (`ReactiveDataProvider`) — the value of\n * the `session` top-level field on `SessionData on Query`, not the wrapping `{ session: … }` shape.\n * Receives `null` while the entity has not yet resolved.\n */\n $data?: SessionEntity | null;\n children?: ReactNode;\n};\n\nexport type SessionGuardProps = {\n /** The session states under which the children render. Renders nothing for any other state. */\n when?: SessionState[];\n children?: ReactNode;\n};\n\n// ---------------------------------------------------------------------------\n// Definitions\n// ---------------------------------------------------------------------------\n\nexport const SessionRootDefinition = defineComponent<SessionRootProps>({\n name: 'SessionRoot',\n importPath: '@vtex/faststore/components',\n component: SessionRoot,\n fragment,\n fetchStrategy: 'reactive',\n editor: { label: 'Session Root', category: 'identity' },\n schema: {},\n providesContext: 'session',\n guideline:\n 'Read-only identity-state provider, normally the root of the shared layout so the header and every page it serves resolve the same session: reads the Session and broadcasts the anonymous/identified/logged-in tri-state plus the raw loggedIn and canEditData flags. Layout scope fetches once client-side and reuses the cache across navigation; page scope fetches fresh server-side on every request. Exactly one per rendered tree — a page must not add its own when the layout already hosts one. See session.md.',\n});\n\nexport const SessionGuardDefinition = defineComponent<SessionGuardProps>({\n name: 'SessionGuard',\n importPath: '@vtex/faststore/components',\n component: SessionGuard,\n editor: { label: 'Session Guard', category: 'identity' },\n schema: {\n when: { type: 'array', items: ['anonymous', 'identified', 'loggedIn'] as const, label: 'When State' },\n },\n requiresContext: 'session',\n guideline:\n 'Declarative gate: renders its children only when the current session state is one of the values in `when` (e.g. {\"when\":[\"identified\",\"loggedIn\"]}), else renders nothing. Use it in page or layout JSON to show or hide an identity subtree by state without writing the gate into a component. Must be inside SessionRoot, which may be hosted by the page or by its layout.',\n});\n\n// ---------------------------------------------------------------------------\n// Implementations\n// ---------------------------------------------------------------------------\n\nexport function SessionRoot({ $data: $dataProp, children }: SessionRootProps) {\n const $data = useDataOverride($dataProp);\n const session = $data ?? null;\n const loading = session == null;\n\n const value: SessionContextValue = useMemo(() => {\n const loggedIn = session?.loggedIn ?? false;\n const isIdentified = session?.isIdentified ?? false;\n return {\n state: deriveSessionState({ loggedIn, isIdentified }),\n loggedIn,\n canEditData: session?.canEditData ?? false,\n loading,\n };\n }, [session?.loggedIn, session?.isIdentified, session?.canEditData, loading]);\n\n return <SessionContextInternal value={value}>{children}</SessionContextInternal>;\n}\n\nexport function SessionGuard({ when, children }: SessionGuardProps) {\n const { state } = useSession();\n if (!when?.includes(state)) return null;\n return <>{children}</>;\n}\n\n// ---------------------------------------------------------------------------\n// Namespace export\n// ---------------------------------------------------------------------------\n\nexport const Session = {\n Root: SessionRoot,\n Guard: SessionGuard,\n};\n"],"mappings":";;;;;;;;AAYA,MAAM,WAAW,QAAQ;;;;;;CAMxB;;;;;AAYD,SAAgB,mBAAmB,EACjC,UACA,gBAIe;CACf,IAAI,UAAU,OAAO;CACrB,IAAI,cAAc,OAAO;CACzB,OAAO;AACT;AAiBA,MAAM,qBAAqB,OAAO,qBAAqB;AAEvD,MAAM,yBAAyB,cAA+D,kBAAkB;AAEhH,MAAa,iBAAiB;;;;;;;;;AAU9B,SAAgB,aAAkC;CAChD,MAAM,MAAM,WAAW,sBAAsB;CAC7C,IAAI,QAAQ,oBACV,MAAM,IAAI,MAAM,2DAA2D;CAE7E,OAAO;AACT;AA0BA,MAAa,wBAAwB,gBAAkC;CACrE,MAAM;CACN,YAAY;CACZ,WAAW;CACX;CACA,eAAe;CACf,QAAQ;EAAE,OAAO;EAAgB,UAAU;CAAW;CACtD,QAAQ,CAAC;CACT,iBAAiB;CACjB,WACE;AACJ,CAAC;AAED,MAAa,yBAAyB,gBAAmC;CACvE,MAAM;CACN,YAAY;CACZ,WAAW;CACX,QAAQ;EAAE,OAAO;EAAiB,UAAU;CAAW;CACvD,QAAQ,EACN,MAAM;EAAE,MAAM;EAAS,OAAO;GAAC;GAAa;GAAc;EAAU;EAAY,OAAO;CAAa,EACtG;CACA,iBAAiB;CACjB,WACE;AACJ,CAAC;AAMD,SAAgB,YAAY,EAAE,OAAO,WAAW,YAA8B;CAE5E,MAAM,UADQ,gBAAgB,SACV,KAAK;CACzB,MAAM,UAAU,WAAW;CAE3B,MAAM,QAA6B,cAAc;EAC/C,MAAM,WAAW,SAAS,YAAY;EAEtC,OAAO;GACL,OAAO,mBAAmB;IAAE;IAAU,cAFnB,SAAS,gBAAgB;GAEO,CAAC;GACpD;GACA,aAAa,SAAS,eAAe;GACrC;EACF;CACF,GAAG;EAAC,SAAS;EAAU,SAAS;EAAc,SAAS;EAAa;CAAO,CAAC;CAE5E,OAAO,oBAAC,wBAAD;EAA+B;EAAQ;CAAiC,CAAA;AACjF;AAEA,SAAgB,aAAa,EAAE,MAAM,YAA+B;CAClE,MAAM,EAAE,UAAU,WAAW;CAC7B,IAAI,CAAC,MAAM,SAAS,KAAK,GAAG,OAAO;CACnC,OAAO,oBAAA,
|
|
1
|
+
{"version":3,"file":"session.js","names":[],"sources":["../../../src/matter/session/session.tsx"],"sourcesContent":["'use client';\n\nimport { defineComponent } from '@vtex/fs-core';\nimport { createContext, type ReactNode, useContext, useMemo } from 'react';\nimport { useDataOverride } from '../../sdk/data/data-override-context.js';\nimport { graphql } from '../../sdk/generated/gql.js';\nimport type { SessionEntity } from '../../sdk/session/operations.js';\n\n// ---------------------------------------------------------------------------\n// GraphQL Fragment — typed via @graphql-codegen client-preset\n// ---------------------------------------------------------------------------\n\nconst fragment = graphql(`\n fragment SessionData on Query {\n session {\n ...SessionFragment\n }\n }\n`);\n\n// ---------------------------------------------------------------------------\n// Tri-state derivation — the single home of the truth table (Decision 2).\n// ---------------------------------------------------------------------------\n\nexport type SessionState = 'anonymous' | 'identified' | 'loggedIn';\n\n/**\n * Derive the identity tri-state from the two raw session flags. `loggedIn` takes precedence: an\n * authenticated shopper is always `loggedIn` regardless of `isIdentified`.\n */\nexport function deriveSessionState({\n loggedIn,\n isIdentified,\n}: {\n loggedIn: boolean;\n isIdentified: boolean;\n}): SessionState {\n if (loggedIn) return 'loggedIn';\n if (isIdentified) return 'identified';\n return 'anonymous';\n}\n\n// ---------------------------------------------------------------------------\n// Session context (read-only derived state + raw flags)\n// ---------------------------------------------------------------------------\n\nexport type SessionContextValue = Readonly<{\n /** Derived tri-state: anonymous | identified | loggedIn. */\n state: SessionState;\n /** Raw authenticated-session flag from `session.loggedIn`. */\n loggedIn: boolean;\n /** Raw edit-permission flag from `session.canEditData`; lets Controls render editable vs. masked. */\n canEditData: boolean;\n /** True while the Session entity has not yet resolved. */\n loading: boolean;\n}>;\n\nconst SESSION_DATA_UNSET = Symbol('SessionContextUnset');\n\nconst SessionContextInternal = createContext<SessionContextValue | typeof SESSION_DATA_UNSET>(SESSION_DATA_UNSET);\n\nexport const SessionContext = SessionContextInternal as React.Context<SessionContextValue>;\n\n/**\n * Read the derived session state and raw flags from the nearest `SessionRoot`. Throws when called\n * outside a `SessionRoot` subtree (a misplaced `useSession()` is a composition bug the author should\n * see immediately, not a silent degrade to anonymous), mirroring `useCart()` (Decision 6).\n *\n * While the Session entity is unresolved the context carries `loading: true` and `state:\n * 'anonymous'` (the least-privileged default); consumers that must not flash gate on `loading`.\n */\nexport function useSession(): SessionContextValue {\n const ctx = useContext(SessionContextInternal);\n if (ctx === SESSION_DATA_UNSET) {\n throw new Error('useSession() must be used inside a <SessionRoot> subtree.');\n }\n return ctx;\n}\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type SessionRootProps = {\n /**\n * Session entity slice forwarded by the reactive runtime (`ReactiveDataProvider`) — the value of\n * the `session` top-level field on `SessionData on Query`, not the wrapping `{ session: … }` shape.\n * Receives `null` while the entity has not yet resolved.\n */\n $data?: SessionEntity | null;\n children?: ReactNode;\n};\n\nexport type SessionGuardProps = {\n /** The session states under which the children render. Renders nothing for any other state. */\n when?: SessionState[];\n children?: ReactNode;\n};\n\n// ---------------------------------------------------------------------------\n// Definitions\n// ---------------------------------------------------------------------------\n\nexport const SessionRootDefinition = defineComponent<SessionRootProps>({\n name: 'SessionRoot',\n importPath: '@vtex/faststore/components',\n component: SessionRoot,\n fragment,\n fetchStrategy: 'reactive',\n editor: { label: 'Session Root', category: 'identity' },\n schema: {},\n providesContext: 'session',\n guideline:\n 'Read-only identity-state provider, normally the root of the shared layout so the header and every page it serves resolve the same session: reads the Session and broadcasts the anonymous/identified/logged-in tri-state plus the raw loggedIn and canEditData flags. Layout scope fetches once client-side and reuses the cache across navigation; page scope fetches fresh server-side on every request. Exactly one per rendered tree — a page must not add its own when the layout already hosts one. See session.md.',\n});\n\nexport const SessionGuardDefinition = defineComponent<SessionGuardProps>({\n name: 'SessionGuard',\n importPath: '@vtex/faststore/components',\n component: SessionGuard,\n editor: { label: 'Session Guard', category: 'identity' },\n schema: {\n when: { type: 'array', items: ['anonymous', 'identified', 'loggedIn'] as const, label: 'When State' },\n },\n requiresContext: 'session',\n guideline:\n 'Declarative gate: renders its children only when the current session state is one of the values in `when` (e.g. {\"when\":[\"identified\",\"loggedIn\"]}), else renders nothing. Use it in page or layout JSON to show or hide an identity subtree by state without writing the gate into a component. Must be inside SessionRoot, which may be hosted by the page or by its layout.',\n});\n\n// ---------------------------------------------------------------------------\n// Implementations\n// ---------------------------------------------------------------------------\n\nexport function SessionRoot({ $data: $dataProp, children }: SessionRootProps) {\n const $data = useDataOverride($dataProp);\n const session = $data ?? null;\n const loading = session == null;\n\n const value: SessionContextValue = useMemo(() => {\n const loggedIn = session?.loggedIn ?? false;\n const isIdentified = session?.isIdentified ?? false;\n return {\n state: deriveSessionState({ loggedIn, isIdentified }),\n loggedIn,\n canEditData: session?.canEditData ?? false,\n loading,\n };\n }, [session?.loggedIn, session?.isIdentified, session?.canEditData, loading]);\n\n return <SessionContextInternal value={value}>{children}</SessionContextInternal>;\n}\n\nexport function SessionGuard({ when, children }: SessionGuardProps) {\n const { state } = useSession();\n if (!when?.includes(state)) return null;\n return <>{children}</>;\n}\n\n// ---------------------------------------------------------------------------\n// Namespace export\n// ---------------------------------------------------------------------------\n\nexport const Session = {\n Root: SessionRoot,\n Guard: SessionGuard,\n};\n"],"mappings":";;;;;;;;AAYA,MAAM,WAAW,QAAQ;;;;;;CAMxB;;;;;AAYD,SAAgB,mBAAmB,EACjC,UACA,gBAIe;CACf,IAAI,UAAU,OAAO;CACrB,IAAI,cAAc,OAAO;CACzB,OAAO;AACT;AAiBA,MAAM,qBAAqB,OAAO,qBAAqB;AAEvD,MAAM,yBAAyB,cAA+D,kBAAkB;AAEhH,MAAa,iBAAiB;;;;;;;;;AAU9B,SAAgB,aAAkC;CAChD,MAAM,MAAM,WAAW,sBAAsB;CAC7C,IAAI,QAAQ,oBACV,MAAM,IAAI,MAAM,2DAA2D;CAE7E,OAAO;AACT;AA0BA,MAAa,wBAAwB,gBAAkC;CACrE,MAAM;CACN,YAAY;CACZ,WAAW;CACX;CACA,eAAe;CACf,QAAQ;EAAE,OAAO;EAAgB,UAAU;CAAW;CACtD,QAAQ,CAAC;CACT,iBAAiB;CACjB,WACE;AACJ,CAAC;AAED,MAAa,yBAAyB,gBAAmC;CACvE,MAAM;CACN,YAAY;CACZ,WAAW;CACX,QAAQ;EAAE,OAAO;EAAiB,UAAU;CAAW;CACvD,QAAQ,EACN,MAAM;EAAE,MAAM;EAAS,OAAO;GAAC;GAAa;GAAc;EAAU;EAAY,OAAO;CAAa,EACtG;CACA,iBAAiB;CACjB,WACE;AACJ,CAAC;AAMD,SAAgB,YAAY,EAAE,OAAO,WAAW,YAA8B;CAE5E,MAAM,UADQ,gBAAgB,SACV,KAAK;CACzB,MAAM,UAAU,WAAW;CAE3B,MAAM,QAA6B,cAAc;EAC/C,MAAM,WAAW,SAAS,YAAY;EAEtC,OAAO;GACL,OAAO,mBAAmB;IAAE;IAAU,cAFnB,SAAS,gBAAgB;GAEO,CAAC;GACpD;GACA,aAAa,SAAS,eAAe;GACrC;EACF;CACF,GAAG;EAAC,SAAS;EAAU,SAAS;EAAc,SAAS;EAAa;CAAO,CAAC;CAE5E,OAAO,oBAAC,wBAAD;EAA+B;EAAQ;CAAiC,CAAA;AACjF;AAEA,SAAgB,aAAa,EAAE,MAAM,YAA+B;CAClE,MAAM,EAAE,UAAU,WAAW;CAC7B,IAAI,CAAC,MAAM,SAAS,KAAK,GAAG,OAAO;CACnC,OAAO,oBAAA,YAAA,EAAG,SAAW,CAAA;AACvB;AAMA,MAAa,UAAU;CACrB,MAAM;CACN,OAAO;AACT"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import "../../particles/field/field.js";
|
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
//#region src/matter/shipping-address/country-descriptors.ts
|
|
2
|
+
/**
|
|
3
|
+
* Whether `value` satisfies the field's declared `rule.pattern`. A field with no pattern, or a blank
|
|
4
|
+
* value (empty or whitespace-only: blankness is the required rule's business, and a form that trims
|
|
5
|
+
* before judging, as `BillingAddress` does, reads such a value as required, never as a format failure),
|
|
6
|
+
* satisfies it. A non-blank value is tested raw, equivalent to the predicate react-hook-form applies
|
|
7
|
+
* (`value.match(regex) !== null`), so both forms agree on every non-blank value. Blank values are the
|
|
8
|
+
* one deliberate divergence: react-hook-form skips the pattern only for `''`, so for `' '` the
|
|
9
|
+
* delivery form runs the pattern and reports the format, while this helper reports it satisfied and
|
|
10
|
+
* leaves the verdict to the required rule. A caller that needs react-hook-form's exact verdict on a
|
|
11
|
+
* blank value must not rely on this helper alone. `search` ignores a global regex's `lastIndex`, so
|
|
12
|
+
* the descriptor's shared regex is never written to.
|
|
13
|
+
*/
|
|
14
|
+
function satisfiesFieldPattern(field, value) {
|
|
15
|
+
const pattern = field.rule?.pattern;
|
|
16
|
+
if (!pattern || value.trim() === "") return true;
|
|
17
|
+
return value.search(pattern.value) !== -1;
|
|
18
|
+
}
|
|
1
19
|
/**
|
|
2
20
|
* All available country descriptors, keyed by ISO alpha-3 code.
|
|
3
21
|
*
|
|
@@ -8,6 +26,8 @@ const countryDescriptors = {
|
|
|
8
26
|
BRA: {
|
|
9
27
|
iso: "BRA",
|
|
10
28
|
label: "Brasil",
|
|
29
|
+
countryLabel: "país",
|
|
30
|
+
requiredSuffix: "é obrigatório",
|
|
11
31
|
fields: [
|
|
12
32
|
{
|
|
13
33
|
name: "recipientName",
|
|
@@ -51,6 +71,8 @@ const countryDescriptors = {
|
|
|
51
71
|
USA: {
|
|
52
72
|
iso: "USA",
|
|
53
73
|
label: "United States",
|
|
74
|
+
countryLabel: "country",
|
|
75
|
+
requiredSuffix: "is required",
|
|
54
76
|
fields: [
|
|
55
77
|
{
|
|
56
78
|
name: "recipientName",
|
|
@@ -106,6 +128,6 @@ function getCountryDescriptor(iso) {
|
|
|
106
128
|
return countryDescriptors[iso];
|
|
107
129
|
}
|
|
108
130
|
//#endregion
|
|
109
|
-
export { countryDescriptors, getAvailableCountries, getCountryDescriptor };
|
|
131
|
+
export { countryDescriptors, getAvailableCountries, getCountryDescriptor, satisfiesFieldPattern };
|
|
110
132
|
|
|
111
133
|
//# sourceMappingURL=country-descriptors.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"country-descriptors.js","names":[],"sources":["../../../src/matter/shipping-address/country-descriptors.ts"],"sourcesContent":["/**\n * Country-adaptive shipping address form descriptors.\n *\n * Each descriptor declares the form shape (field set, order, and labels) for a specific country.\n * Descriptors are primarily data — field set, order, and labels — but a `FieldDescriptor` may also\n * carry `required` and `rule`, the two validation facts a country varies. `ShippingAddressFieldset`\n * is the only reader of either (specs/shipping-address-fieldset.md, Decision 3): a field whose\n * descriptor entry is absent is never rendered, so it can carry no requiredness at all.\n * Country adaptation is data-driven; adding a country is a data-only change (a new descriptor entry).\n *\n * Canonical field names must be shared/consistent across descriptors for fields both countries have\n * (recipientName, postalCode, street, number, complement, city, state), so that switching countries\n * preserves shared field values in the batched draft state (Decision 1/Invariant).\n *\n * Per Decision 2, this narrows the RFC's descriptor scope to form shape plus two per-field validation\n * facts: the non-blank recipient name and, per country, the postal code's format. Both descriptors'\n * `postalCode` entry carries a `pattern` rule matching that country's CEP or ZIP shape; every other\n * field's `rule` stays undefined. There is no server-side postal-code validation to fall back on:\n * `shippingAddressUpdate` types `postalCode` as a plain `z.string()`, forwards it verbatim with\n * `clearAddressIfPostalCodeNotFound: false`, and has no try/catch around the upstream call, so a\n * malformed value still returns HTTP 200 with an empty `userErrors` array. This client-side rule is\n * the only postal-code guard in the system today; per-field blur validation and a future\n * @vtex/country-forms adoption remain deferred.\n */\n\nimport type { FieldRule } from '../../particles/field/field.js';\n\n/**\n * Describes a single form field within a country descriptor.\n *\n * @property name - Canonical field name (e.g., \"recipientName\", \"street\", \"neighborhood\").\n * Shared field names across descriptors enable draft state preservation on country switch.\n * @property label - Human-readable label for the field. Named `label` (not a key) to allow future\n * swapping with i18n keys once the codebase adopts an i18n layer.\n */\nexport type FieldDescriptor = Readonly<{\n name: string;\n label: string;\n /**\n * Whether the fieldset requires a non-blank value. Read only by ShippingAddressFieldset, which\n * turns it into the field contract's `rule`. A field whose descriptor entry is absent is not\n * rendered, so it can carry no requiredness at all — that is the point (Decision 3).\n */\n required?: boolean;\n /**\n * Extra react-hook-form rules for this field in this country: pattern, minLength, and so on.\n * Merged under the generated `required`. Only `postalCode` carries one today.\n */\n rule?: FieldRule;\n}>;\n\n/**\n * Describes the form shape for a single country.\n *\n * @property iso - ISO 3166-1 alpha-3 country code (e.g., \"BRA\", \"USA\"). Must be a valid 3-letter code\n * matching the Address.country field type (plain String!, ISO alpha-3 by convention).\n * @property label - Human-readable country name for the country selector (e.g., \"Brazil\", \"United States\").\n * Named `label` (not a key) to allow future swapping with i18n keys.\n * @property fields - Ordered field set for this country. Order defines the visual order in the form.\n * Only fields this country uses are listed; absent fields are not rendered.\n */\nexport type CountryDescriptor = Readonly<{\n iso: string;\n label: string;\n fields: readonly FieldDescriptor[];\n}>;\n\n/**\n * Brazil (BRA) address form descriptor.\n *\n * Field order:\n * 1. recipientName — recipient of the package\n * 2. street — street name\n * 3. number — street number\n * 4. complement — apartment, suite, etc. (optional per GraphQL but no form-level validation)\n * 5. neighborhood — bairro (BR-specific, not in US form)\n * 6. city — city name\n * 7. state — state/UF code (canonical name shared with US, though US calls it state abbreviation)\n * 8. postalCode — CEP format, client-validated via `rule` (8 digits, hyphen after the fifth optional)\n */\nconst brDescriptor: CountryDescriptor = {\n iso: 'BRA',\n label: 'Brasil',\n fields: [\n { name: 'recipientName', label: 'nome do destinatário', required: true },\n { name: 'street', label: 'rua' },\n { name: 'number', label: 'número' },\n { name: 'complement', label: 'complemento (opcional)' },\n { name: 'neighborhood', label: 'bairro' },\n { name: 'city', label: 'cidade' },\n { name: 'state', label: 'estado' },\n {\n name: 'postalCode',\n label: 'cep',\n rule: { pattern: { value: /^\\d{5}-?\\d{3}$/, message: 'cep inválido' } },\n },\n ],\n};\n\n/**\n * United States (USA) address form descriptor.\n *\n * Field order:\n * 1. recipientName — recipient of the package\n * 2. street — street name\n * 3. number — street number\n * 4. complement — apartment, suite, etc. (optional per GraphQL but no form-level validation)\n * 5. city — city name\n * 6. state — state abbreviation (canonical name shared with BR for draft preservation)\n * 7. postalCode — ZIP/ZIP+4 format, client-validated via `rule` (5 digits, optional 4-digit\n * extension, hyphen before it optional either way — accepts both `02101-1234` and `021011234`)\n *\n * Note: neighborhood is not included in the US v1 form. If the shopper switches from BR to US,\n * the neighborhood value is retained in the draft but hidden (descriptor controls visibility only);\n * switching back to BR resurfaces it.\n */\nconst usaDescriptor: CountryDescriptor = {\n iso: 'USA',\n label: 'United States',\n fields: [\n { name: 'recipientName', label: 'recipient name', required: true },\n { name: 'street', label: 'street' },\n { name: 'number', label: 'number' },\n { name: 'complement', label: 'apartment, suite, etc. (optional)' },\n { name: 'city', label: 'city' },\n { name: 'state', label: 'state' },\n {\n name: 'postalCode',\n label: 'zip code',\n rule: { pattern: { value: /^\\d{5}(-?\\d{4})?$/, message: 'invalid zip code' } },\n },\n ],\n};\n\n/**\n * All available country descriptors, keyed by ISO alpha-3 code.\n *\n * The country selector lists only countries with a descriptor; a descriptor-less country is unreachable.\n * Adding a country is a data-only change: add a new CountryDescriptor entry here.\n */\nexport const countryDescriptors: Readonly<Record<string, CountryDescriptor>> = {\n BRA: brDescriptor,\n USA: usaDescriptor,\n};\n\n/**\n * Returns an ordered list of available country ISO codes (those with a descriptor).\n *\n * @returns ISO alpha-3 codes of all implemented countries, in definition order.\n */\nexport function getAvailableCountries(): readonly string[] {\n return Object.keys(countryDescriptors);\n}\n\n/**\n * Retrieves the descriptor for a given country.\n *\n * @param iso - ISO 3166-1 alpha-3 country code (e.g., \"BRA\", \"USA\")\n * @returns The CountryDescriptor for the country, or undefined if not implemented.\n */\nexport function getCountryDescriptor(iso: string): CountryDescriptor | undefined {\n return countryDescriptors[iso];\n}\n"],"mappings":";;;;;;AA4IA,MAAa,qBAAkE;CAC7E,KAAK;EA5DL,KAAK;EACL,OAAO;EACP,QAAQ;GACN;IAAE,MAAM;IAAiB,OAAO;IAAwB,UAAU;GAAK;GACvE;IAAE,MAAM;IAAU,OAAO;GAAM;GAC/B;IAAE,MAAM;IAAU,OAAO;GAAS;GAClC;IAAE,MAAM;IAAc,OAAO;GAAyB;GACtD;IAAE,MAAM;IAAgB,OAAO;GAAS;GACxC;IAAE,MAAM;IAAQ,OAAO;GAAS;GAChC;IAAE,MAAM;IAAS,OAAO;GAAS;GACjC;IACE,MAAM;IACN,OAAO;IACP,MAAM,EAAE,SAAS;KAAE,OAAO;KAAkB,SAAS;IAAe,EAAE;GACxE;EACF;CA6CK;CACL,KAAK;EAzBL,KAAK;EACL,OAAO;EACP,QAAQ;GACN;IAAE,MAAM;IAAiB,OAAO;IAAkB,UAAU;GAAK;GACjE;IAAE,MAAM;IAAU,OAAO;GAAS;GAClC;IAAE,MAAM;IAAU,OAAO;GAAS;GAClC;IAAE,MAAM;IAAc,OAAO;GAAoC;GACjE;IAAE,MAAM;IAAQ,OAAO;GAAO;GAC9B;IAAE,MAAM;IAAS,OAAO;GAAQ;GAChC;IACE,MAAM;IACN,OAAO;IACP,MAAM,EAAE,SAAS;KAAE,OAAO;KAAqB,SAAS;IAAmB,EAAE;GAC/E;EACF;CAWK;AACP;;;;;;AAOA,SAAgB,wBAA2C;CACzD,OAAO,OAAO,KAAK,kBAAkB;AACvC;;;;;;;AAQA,SAAgB,qBAAqB,KAA4C;CAC/E,OAAO,mBAAmB;AAC5B"}
|
|
1
|
+
{"version":3,"file":"country-descriptors.js","names":[],"sources":["../../../src/matter/shipping-address/country-descriptors.ts"],"sourcesContent":["/**\n * Country-adaptive shipping address form descriptors.\n *\n * Each descriptor declares the form shape (field set, order, and labels) for a specific country.\n * Descriptors are primarily data — field set, order, and labels — but a `FieldDescriptor` may also\n * carry `required` and `rule`, the two validation facts a country varies. `ShippingAddressFieldset`\n * is the only reader of `required` (specs/shipping-address-fieldset.md, Decision 3): a field whose\n * descriptor entry is absent is never rendered, so it can carry no requiredness at all. `rule.pattern`\n * has two readers: the fieldset, through react-hook-form, and `satisfiesFieldPattern` below, which\n * `BillingAddress` applies to its own submit gate and field errors. A descriptor also carries the two\n * strings a country's form speaks for itself — the Country label and the required-message suffix — so\n * no copy of the delivery form is hardcoded in the fieldset.\n * Country adaptation is data-driven; adding a country is a data-only change (a new descriptor entry).\n *\n * Canonical field names must be shared/consistent across descriptors for fields both countries have\n * (recipientName, postalCode, street, number, complement, city, state), so that switching countries\n * preserves shared field values in the batched draft state (Decision 1/Invariant).\n *\n * Per Decision 2, this narrows the RFC's descriptor scope to form shape plus two per-field validation\n * facts: the non-blank recipient name and, per country, the postal code's format. Both descriptors'\n * `postalCode` entry carries a `pattern` rule matching that country's CEP or ZIP shape; every other\n * field's `rule` stays undefined. There is no server-side postal-code validation to fall back on:\n * `shippingAddressUpdate` types `postalCode` as a plain `z.string()`, forwards it verbatim with\n * `clearAddressIfPostalCodeNotFound: false`, and has no try/catch around the upstream call, so a\n * malformed value still returns HTTP 200 with an empty `userErrors` array. This client-side rule is\n * the only postal-code guard in the system today. The two forms apply it on different triggers: the\n * billing form judges a touched field on every render (through `getFieldIssue`, which reads\n * `satisfiesFieldPattern`), touching it on blur or when a refused submit focuses it, while the\n * delivery form's react-hook-form rule runs on submit and re-validates after it. A future\n * @vtex/country-forms adoption remains deferred.\n */\n\nimport type { FieldRule, FieldRuleConstraint } from '../../particles/field/field.js';\n\n/**\n * A descriptor's validation rule: a `FieldRule` whose `pattern` always carries the message a failing value\n * shows. A bare `RegExp` is not accepted, so both forms of a country's address read the one message for\n * one rule and no consumer needs a fallback string of its own.\n */\nexport type FieldDescriptorRule = Omit<FieldRule, 'pattern'> &\n Readonly<{\n pattern?: FieldRuleConstraint<RegExp>;\n }>;\n\n/**\n * Describes a single form field within a country descriptor.\n *\n * @property name - Canonical field name (e.g., \"recipientName\", \"street\", \"neighborhood\").\n * Shared field names across descriptors enable draft state preservation on country switch.\n * @property label - Human-readable label for the field. Named `label` (not a key) to allow future\n * swapping with i18n keys once the codebase adopts an i18n layer.\n */\nexport type FieldDescriptor = Readonly<{\n name: string;\n label: string;\n /**\n * Whether the fieldset requires a non-blank value. Read only by ShippingAddressFieldset, which\n * turns it into the field contract's `rule`. A field whose descriptor entry is absent is not\n * rendered, so it can carry no requiredness at all — that is the point (Decision 3).\n */\n required?: boolean;\n /**\n * Extra react-hook-form rules for this field in this country: pattern, minLength, and so on.\n * Merged under the generated `required`. Only `postalCode` carries one today. `pattern` is also read\n * by `satisfiesFieldPattern`, for forms that are not built on react-hook-form, and its `message` is the\n * copy every form shows for it.\n */\n rule?: FieldDescriptorRule;\n}>;\n\n/**\n * Describes the form shape for a single country.\n *\n * @property iso - ISO 3166-1 alpha-3 country code (e.g., \"BRA\", \"USA\"). Must be a valid 3-letter code\n * matching the Address.country field type (plain String!, ISO alpha-3 by convention).\n * @property label - Human-readable country name for the country selector (e.g., \"Brazil\", \"United States\").\n * Named `label` (not a key) to allow future swapping with i18n keys.\n * @property countryLabel - Default label of the country selector on this country's form, in the language of\n * the form's other copy. An authored `label` on the control wins.\n * @property requiredSuffix - Ends a required field's message (`${label} ${requiredSuffix}`), in the same\n * language as the field labels.\n * @property fields - Ordered field set for this country. Order defines the visual order in the form.\n * Only fields this country uses are listed; absent fields are not rendered.\n */\nexport type CountryDescriptor = Readonly<{\n iso: string;\n label: string;\n /** Default label of the country selector on this country's form; an authored `label` wins. */\n countryLabel: string;\n /** Closes a required field's message: `${label} ${requiredSuffix}`. */\n requiredSuffix: string;\n fields: readonly FieldDescriptor[];\n}>;\n\n/**\n * Whether `value` satisfies the field's declared `rule.pattern`. A field with no pattern, or a blank\n * value (empty or whitespace-only: blankness is the required rule's business, and a form that trims\n * before judging, as `BillingAddress` does, reads such a value as required, never as a format failure),\n * satisfies it. A non-blank value is tested raw, equivalent to the predicate react-hook-form applies\n * (`value.match(regex) !== null`), so both forms agree on every non-blank value. Blank values are the\n * one deliberate divergence: react-hook-form skips the pattern only for `''`, so for `' '` the\n * delivery form runs the pattern and reports the format, while this helper reports it satisfied and\n * leaves the verdict to the required rule. A caller that needs react-hook-form's exact verdict on a\n * blank value must not rely on this helper alone. `search` ignores a global regex's `lastIndex`, so\n * the descriptor's shared regex is never written to.\n */\nexport function satisfiesFieldPattern(field: FieldDescriptor, value: string): boolean {\n const pattern = field.rule?.pattern;\n if (!pattern || value.trim() === '') return true;\n return value.search(pattern.value) !== -1;\n}\n\n/**\n * Brazil (BRA) address form descriptor.\n *\n * Field order:\n * 1. recipientName — recipient of the package\n * 2. street — street name\n * 3. number — street number\n * 4. complement — apartment, suite, etc. (optional per GraphQL but no form-level validation)\n * 5. neighborhood — bairro (BR-specific, not in US form)\n * 6. city — city name\n * 7. state — state/UF code (canonical name shared with US, though US calls it state abbreviation)\n * 8. postalCode — CEP format, client-validated via `rule` (8 digits, hyphen after the fifth optional)\n */\nconst brDescriptor: CountryDescriptor = {\n iso: 'BRA',\n label: 'Brasil',\n countryLabel: 'país',\n requiredSuffix: 'é obrigatório',\n fields: [\n { name: 'recipientName', label: 'nome do destinatário', required: true },\n { name: 'street', label: 'rua' },\n { name: 'number', label: 'número' },\n { name: 'complement', label: 'complemento (opcional)' },\n { name: 'neighborhood', label: 'bairro' },\n { name: 'city', label: 'cidade' },\n { name: 'state', label: 'estado' },\n {\n name: 'postalCode',\n label: 'cep',\n rule: { pattern: { value: /^\\d{5}-?\\d{3}$/, message: 'cep inválido' } },\n },\n ],\n};\n\n/**\n * United States (USA) address form descriptor.\n *\n * Field order:\n * 1. recipientName — recipient of the package\n * 2. street — street name\n * 3. number — street number\n * 4. complement — apartment, suite, etc. (optional per GraphQL but no form-level validation)\n * 5. city — city name\n * 6. state — state abbreviation (canonical name shared with BR for draft preservation)\n * 7. postalCode — ZIP/ZIP+4 format, client-validated via `rule` (5 digits, optional 4-digit\n * extension, hyphen before it optional either way — accepts both `02101-1234` and `021011234`)\n *\n * Note: neighborhood is not included in the US v1 form. If the shopper switches from BR to US,\n * the neighborhood value is retained in the draft but hidden (descriptor controls visibility only);\n * switching back to BR resurfaces it.\n */\nconst usaDescriptor: CountryDescriptor = {\n iso: 'USA',\n label: 'United States',\n countryLabel: 'country',\n requiredSuffix: 'is required',\n fields: [\n { name: 'recipientName', label: 'recipient name', required: true },\n { name: 'street', label: 'street' },\n { name: 'number', label: 'number' },\n { name: 'complement', label: 'apartment, suite, etc. (optional)' },\n { name: 'city', label: 'city' },\n { name: 'state', label: 'state' },\n {\n name: 'postalCode',\n label: 'zip code',\n rule: { pattern: { value: /^\\d{5}(-?\\d{4})?$/, message: 'invalid zip code' } },\n },\n ],\n};\n\n/**\n * All available country descriptors, keyed by ISO alpha-3 code.\n *\n * The country selector lists only countries with a descriptor; a descriptor-less country is unreachable.\n * Adding a country is a data-only change: add a new CountryDescriptor entry here.\n */\nexport const countryDescriptors: Readonly<Record<string, CountryDescriptor>> = {\n BRA: brDescriptor,\n USA: usaDescriptor,\n};\n\n/**\n * Returns an ordered list of available country ISO codes (those with a descriptor).\n *\n * @returns ISO alpha-3 codes of all implemented countries, in definition order.\n */\nexport function getAvailableCountries(): readonly string[] {\n return Object.keys(countryDescriptors);\n}\n\n/**\n * Retrieves the descriptor for a given country.\n *\n * @param iso - ISO 3166-1 alpha-3 country code (e.g., \"BRA\", \"USA\")\n * @returns The CountryDescriptor for the country, or undefined if not implemented.\n */\nexport function getCountryDescriptor(iso: string): CountryDescriptor | undefined {\n return countryDescriptors[iso];\n}\n"],"mappings":";;;;;;;;;;;;;AA0GA,SAAgB,sBAAsB,OAAwB,OAAwB;CACpF,MAAM,UAAU,MAAM,MAAM;CAC5B,IAAI,CAAC,WAAW,MAAM,KAAK,MAAM,IAAI,OAAO;CAC5C,OAAO,MAAM,OAAO,QAAQ,KAAK,MAAM;AACzC;;;;;;;AA+EA,MAAa,qBAAkE;CAC7E,KAAK;EAhEL,KAAK;EACL,OAAO;EACP,cAAc;EACd,gBAAgB;EAChB,QAAQ;GACN;IAAE,MAAM;IAAiB,OAAO;IAAwB,UAAU;GAAK;GACvE;IAAE,MAAM;IAAU,OAAO;GAAM;GAC/B;IAAE,MAAM;IAAU,OAAO;GAAS;GAClC;IAAE,MAAM;IAAc,OAAO;GAAyB;GACtD;IAAE,MAAM;IAAgB,OAAO;GAAS;GACxC;IAAE,MAAM;IAAQ,OAAO;GAAS;GAChC;IAAE,MAAM;IAAS,OAAO;GAAS;GACjC;IACE,MAAM;IACN,OAAO;IACP,MAAM,EAAE,SAAS;KAAE,OAAO;KAAkB,SAAS;IAAe,EAAE;GACxE;EACF;CA+CK;CACL,KAAK;EA3BL,KAAK;EACL,OAAO;EACP,cAAc;EACd,gBAAgB;EAChB,QAAQ;GACN;IAAE,MAAM;IAAiB,OAAO;IAAkB,UAAU;GAAK;GACjE;IAAE,MAAM;IAAU,OAAO;GAAS;GAClC;IAAE,MAAM;IAAU,OAAO;GAAS;GAClC;IAAE,MAAM;IAAc,OAAO;GAAoC;GACjE;IAAE,MAAM;IAAQ,OAAO;GAAO;GAC9B;IAAE,MAAM;IAAS,OAAO;GAAQ;GAChC;IACE,MAAM;IACN,OAAO;IACP,MAAM,EAAE,SAAS;KAAE,OAAO;KAAqB,SAAS;IAAmB,EAAE;GAC/E;EACF;CAWK;AACP;;;;;;AAOA,SAAgB,wBAA2C;CACzD,OAAO,OAAO,KAAK,kBAAkB;AACvC;;;;;;;AAQA,SAAgB,qBAAqB,KAA4C;CAC/E,OAAO,mBAAmB;AAC5B"}
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { defineComponent } from "../../core/dist/define-component.js";
|
|
3
3
|
import "../../core/dist/index.js";
|
|
4
|
-
import { Field, FieldBinding, FieldsetFieldContext, useFieldsetField, useFieldsetFieldNames, useNoAncestorFieldset } from "../../particles/field/field.js";
|
|
4
|
+
import { Field, FieldBinding, FieldsetFieldContext, readBaseline, useFieldsetField, useFieldsetFieldNames, useNoAncestorFieldset } from "../../particles/field/field.js";
|
|
5
5
|
import { useActionsOverride } from "../../sdk/actions/actions-override-context.js";
|
|
6
6
|
import { useDataOverride } from "../../sdk/data/data-override-context.js";
|
|
7
|
-
import {
|
|
8
|
-
import { getAvailableCountries, getCountryDescriptor } from "./country-descriptors.js";
|
|
7
|
+
import { resolveAuthoredCopy } from "../../utils/resolve-authored-copy.js";
|
|
9
8
|
import { useFormRegistry } from "../../particles/form/form.js";
|
|
10
9
|
import { useEntityDataResolved } from "../../sdk/data/entity-resolution-context.js";
|
|
11
10
|
import { useReactiveDataSettled } from "../../sdk/data/use-reactive-data-settled.js";
|
|
11
|
+
import { Fieldset } from "../../particles/fieldset/fieldset.js";
|
|
12
|
+
import { getAvailableCountries, getCountryDescriptor } from "./country-descriptors.js";
|
|
12
13
|
import { FieldSelect } from "../../particles/select/field-select.js";
|
|
13
14
|
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from "react";
|
|
14
|
-
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
15
|
-
import { useWatch } from "react-hook-form";
|
|
15
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
16
|
+
import { useFormContext, useWatch } from "react-hook-form";
|
|
16
17
|
//#region src/matter/shipping-address/shipping-address.tsx
|
|
17
18
|
/**
|
|
18
19
|
* Shipping address step (T2 — Enter and save the delivery address).
|
|
@@ -97,11 +98,11 @@ const FIELD_AUTOCOMPLETE = {
|
|
|
97
98
|
* declaration of the baseline. The blank check it used to hand-write alongside lives in
|
|
98
99
|
* `resolveRequiredness` now, installed for every bound control rather than only for this one.
|
|
99
100
|
*/
|
|
100
|
-
function buildRule(field, label) {
|
|
101
|
+
function buildRule(field, label, requiredSuffix) {
|
|
101
102
|
if (!field.required) return field.rule;
|
|
102
103
|
return {
|
|
103
104
|
...field.rule,
|
|
104
|
-
required: `${label}
|
|
105
|
+
required: `${label} ${requiredSuffix}`
|
|
105
106
|
};
|
|
106
107
|
}
|
|
107
108
|
/** Keeps only the AddressDraft keys `values` actually defines — a field no control composed is absent, not `undefined`. */
|
|
@@ -349,6 +350,49 @@ const ShippingAddressCountryControlDefinition = defineComponent({
|
|
|
349
350
|
} },
|
|
350
351
|
guideline: "Country selector; lists only countries with an implemented descriptor. Must be a descendant of ShippingAddressFieldset, which provides the shipping-address context this control requires. Recomposable: compose Field.Label, FieldSelect.Root and Field.Error as children, in any order; a childless composition keeps the default label-select-error order."
|
|
351
352
|
});
|
|
353
|
+
/** The message a constrained rule member carries; a bare value (or a bare `RegExp`) carries none. */
|
|
354
|
+
function messageOf(constraint) {
|
|
355
|
+
return typeof constraint === "object" && !(constraint instanceof RegExp) ? constraint.message : "";
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Every static message a rule carries, in a fixed order: the `required` copy in any of its three shapes,
|
|
359
|
+
* then the message of each constrained member. A `validate` record has no static copy, so it adds none.
|
|
360
|
+
*/
|
|
361
|
+
function messagesOf(rule) {
|
|
362
|
+
return [
|
|
363
|
+
readBaseline(rule).message ?? "",
|
|
364
|
+
messageOf(rule?.pattern),
|
|
365
|
+
messageOf(rule?.min),
|
|
366
|
+
messageOf(rule?.max),
|
|
367
|
+
messageOf(rule?.minLength),
|
|
368
|
+
messageOf(rule?.maxLength)
|
|
369
|
+
];
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* An error already on screen keeps the message of the rule that raised it, so a country switch would
|
|
373
|
+
* leave "é obrigatório" under a USA form until the shopper edits or submits again. A bound control
|
|
374
|
+
* therefore revalidates itself when the copy of its own rule changes while it carries an error: that
|
|
375
|
+
* re-words it from the new rule without newly flagging a field the shopper has not reached.
|
|
376
|
+
*
|
|
377
|
+
* It runs inside the control, as an effect of the component that renders the field's own subtree, so it
|
|
378
|
+
* always fires after that field's registration — `useController` re-registers `rules` during render, and
|
|
379
|
+
* descendants' effects precede their parent's — and never validates against the previous rule, whichever
|
|
380
|
+
* of the two the registration becomes. The first run counts as a change too: a field that remounts after
|
|
381
|
+
* a country without it keeps its error across the unmount (`shouldUnregister` being false) and comes
|
|
382
|
+
* back carrying the old wording.
|
|
383
|
+
*/
|
|
384
|
+
function useRewordErroredField(name, rule) {
|
|
385
|
+
const { getFieldState, trigger } = useFormContext();
|
|
386
|
+
const copy = messagesOf(rule).join("\n");
|
|
387
|
+
useEffect(() => {
|
|
388
|
+
if (getFieldState(name).error) trigger(name);
|
|
389
|
+
}, [
|
|
390
|
+
copy,
|
|
391
|
+
name,
|
|
392
|
+
getFieldState,
|
|
393
|
+
trigger
|
|
394
|
+
]);
|
|
395
|
+
}
|
|
352
396
|
/**
|
|
353
397
|
* ShippingAddressFieldset — this entity's root: same data, same action as a page-level submit
|
|
354
398
|
* would use, dispatched at form-submit time instead of through a button of its own. Registers one
|
|
@@ -408,12 +452,13 @@ function ShippingAddressFieldset({ defaultCountry = "BRA", skeletonCount = SHIPP
|
|
|
408
452
|
});
|
|
409
453
|
const activeCountry = typeof watchedCountry === "string" && watchedCountry || seed.country;
|
|
410
454
|
const descriptor = getCountryDescriptor(activeCountry);
|
|
455
|
+
const requiredSuffix = descriptor?.requiredSuffix ?? "";
|
|
411
456
|
const fieldFor = useCallback((key) => descriptor?.fields.find((f) => f.name === key), [descriptor]);
|
|
412
457
|
const seedFor = useCallback((key) => seed[key], [seed]);
|
|
413
458
|
const ruleFor = useCallback((key, label) => {
|
|
414
459
|
const field = fieldFor(key);
|
|
415
|
-
return field ? buildRule(field, label
|
|
416
|
-
}, [fieldFor]);
|
|
460
|
+
return field ? buildRule(field, label, requiredSuffix) : void 0;
|
|
461
|
+
}, [fieldFor, requiredSuffix]);
|
|
417
462
|
const countryOptions = useMemo(() => getAvailableCountries().map((iso) => ({
|
|
418
463
|
value: iso,
|
|
419
464
|
label: getCountryDescriptor(iso)?.label ?? iso
|
|
@@ -510,18 +555,20 @@ function ShippingAddressFieldsetSkeleton({ count = SHIPPING_ADDRESS_SKELETON_ROW
|
|
|
510
555
|
ShippingAddressFieldset.Skeleton = ShippingAddressFieldsetSkeleton;
|
|
511
556
|
function BoundAddressTextField({ fieldKey, slug, className, label, placeholder, children, fieldsetCtx, fieldDescriptor }) {
|
|
512
557
|
const name = useFieldsetField(SHIPPING_ADDRESS_NAMESPACE, fieldKey);
|
|
513
|
-
const resolvedLabel = label
|
|
558
|
+
const resolvedLabel = resolveAuthoredCopy(label, fieldDescriptor.label);
|
|
559
|
+
const rule = fieldsetCtx.ruleFor(fieldKey, resolvedLabel);
|
|
560
|
+
useRewordErroredField(name, rule);
|
|
514
561
|
return /* @__PURE__ */ jsx(FieldBinding, {
|
|
515
562
|
name,
|
|
516
563
|
label: resolvedLabel,
|
|
517
564
|
placeholder,
|
|
518
565
|
defaultValue: fieldsetCtx.seedFor(fieldKey),
|
|
519
|
-
rule
|
|
566
|
+
rule,
|
|
520
567
|
autoComplete: FIELD_AUTOCOMPLETE[fieldKey],
|
|
521
568
|
children: /* @__PURE__ */ jsx(Field.Root, {
|
|
522
569
|
className,
|
|
523
570
|
"data-fs-shipping-address-field": slug,
|
|
524
|
-
children: children ?? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
571
|
+
children: children ?? /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
525
572
|
/* @__PURE__ */ jsx(Field.Label, {}),
|
|
526
573
|
/* @__PURE__ */ jsx(Field.Control, {}),
|
|
527
574
|
/* @__PURE__ */ jsx(Field.Error, {})
|
|
@@ -673,16 +720,17 @@ function ShippingAddressCountryControl({ className, label, placeholder, children
|
|
|
673
720
|
return /* @__PURE__ */ jsx(BoundShippingAddressCountryControl, {
|
|
674
721
|
className,
|
|
675
722
|
label,
|
|
723
|
+
defaultLabel: fieldsetCtx.descriptor.countryLabel,
|
|
676
724
|
placeholder,
|
|
677
725
|
fieldsetCtx,
|
|
678
726
|
children
|
|
679
727
|
});
|
|
680
728
|
}
|
|
681
|
-
function BoundShippingAddressCountryControl({ className, label, placeholder, children, fieldsetCtx }) {
|
|
729
|
+
function BoundShippingAddressCountryControl({ className, label, defaultLabel, placeholder, children, fieldsetCtx }) {
|
|
682
730
|
const name = useFieldsetField(SHIPPING_ADDRESS_NAMESPACE, "country");
|
|
683
731
|
return /* @__PURE__ */ jsx(FieldBinding, {
|
|
684
732
|
name,
|
|
685
|
-
label: label
|
|
733
|
+
label: resolveAuthoredCopy(label, defaultLabel),
|
|
686
734
|
placeholder: placeholder ?? fieldsetCtx.descriptor?.label,
|
|
687
735
|
defaultValue: fieldsetCtx.seedFor("country"),
|
|
688
736
|
options: fieldsetCtx.countryOptions,
|
|
@@ -690,7 +738,7 @@ function BoundShippingAddressCountryControl({ className, label, placeholder, chi
|
|
|
690
738
|
children: /* @__PURE__ */ jsx(Field.Root, {
|
|
691
739
|
className,
|
|
692
740
|
"data-fs-shipping-address-field": "country",
|
|
693
|
-
children: children ?? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
741
|
+
children: children ?? /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
694
742
|
/* @__PURE__ */ jsx(Field.Label, {}),
|
|
695
743
|
/* @__PURE__ */ jsx(FieldSelect.Root, {}),
|
|
696
744
|
/* @__PURE__ */ jsx(Field.Error, {})
|