@vtex/faststore 0.0.5 → 0.0.7

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