@forgecart/cli 2.202610280649.0 → 2.202610280926.0

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 (218) hide show
  1. package/dist/src/commands/init-questions.d.ts +12 -11
  2. package/dist/src/commands/init-questions.js +15 -23
  3. package/dist/src/commands/init-questions.js.map +1 -1
  4. package/dist/src/commands/init.d.ts +13 -13
  5. package/dist/src/commands/init.js +75 -144
  6. package/dist/src/commands/init.js.map +1 -1
  7. package/dist/src/commands/refresh.d.ts +3 -3
  8. package/dist/src/commands/refresh.js +4 -14
  9. package/dist/src/commands/refresh.js.map +1 -1
  10. package/dist/src/commands/template-manifest.d.ts +2 -5
  11. package/dist/src/commands/template-manifest.js +1 -1
  12. package/dist/src/commands/template-manifest.js.map +1 -1
  13. package/dist/src/commands/type/init.input.d.ts +1 -1
  14. package/dist/src/commands/type/init.types.d.ts +4 -4
  15. package/dist/src/template/template-set.d.ts +17 -27
  16. package/dist/src/template/template-set.js +25 -42
  17. package/dist/src/template/template-set.js.map +1 -1
  18. package/dist/src/template/template-unavailable-error.d.ts +3 -4
  19. package/dist/src/template/template-unavailable-error.js +3 -4
  20. package/dist/src/template/template-unavailable-error.js.map +1 -1
  21. package/dist/src/template/type/template-set.types.d.ts +14 -17
  22. package/dist/src/template/type/template-set.types.js +6 -1
  23. package/dist/src/template/type/template-set.types.js.map +1 -1
  24. package/dist/src/template/util/template-identity.util.d.ts +1 -3
  25. package/dist/src/template/util/template-identity.util.js +0 -5
  26. package/dist/src/template/util/template-identity.util.js.map +1 -1
  27. package/package.json +1 -1
  28. package/templates/storefront-blank/.forgecartignore +2 -0
  29. package/templates/storefront-blank/Procfile +1 -0
  30. package/templates/storefront-blank/README.md +86 -0
  31. package/templates/storefront-blank/SEO-MIGRATION.md +718 -0
  32. package/templates/storefront-blank/_gitignore +21 -0
  33. package/templates/storefront-blank/next.config.js +105 -0
  34. package/templates/storefront-blank/package.json +35 -0
  35. package/templates/storefront-blank/postcss.config.js +5 -0
  36. package/templates/storefront-blank/src/app/%5F%5Ffc/identify/route.ts +205 -0
  37. package/templates/storefront-blank/src/app/%5F%5Ffc/track/route.ts +189 -0
  38. package/templates/storefront-blank/src/app/%5F%5Fforge_beacon/route.ts +87 -0
  39. package/templates/storefront-blank/src/app/api/%5F%5Fbackend/methods/route.ts +27 -0
  40. package/templates/storefront-blank/src/app/error.tsx +21 -0
  41. package/templates/storefront-blank/src/app/global-error.tsx +23 -0
  42. package/templates/storefront-blank/src/app/globals.css +110 -0
  43. package/templates/storefront-blank/src/app/layout.tsx +102 -0
  44. package/templates/storefront-blank/src/app/page.tsx +52 -0
  45. package/templates/storefront-blank/src/app/pages/[slug]/not-found.tsx +23 -0
  46. package/templates/storefront-blank/src/app/pages/[slug]/page.tsx +115 -0
  47. package/templates/storefront-blank/src/app/ping/route.ts +21 -0
  48. package/templates/storefront-blank/src/app/robots.ts +69 -0
  49. package/templates/storefront-blank/src/app/sitemap.ts +107 -0
  50. package/templates/storefront-blank/src/components/ForgeErrorBeacon.tsx +102 -0
  51. package/templates/storefront-blank/src/components/ForgeTracker.tsx +479 -0
  52. package/templates/storefront-blank/src/components/ForgecartDesigner.tsx +43 -0
  53. package/templates/storefront-blank/src/components/LocaleLink.tsx +49 -0
  54. package/templates/storefront-blank/src/instrumentation.ts +109 -0
  55. package/templates/storefront-blank/src/lib/account/account-link.ts +76 -0
  56. package/templates/storefront-blank/src/lib/account/account-state.ts +152 -0
  57. package/templates/storefront-blank/src/lib/account/register-state.ts +133 -0
  58. package/templates/storefront-blank/src/lib/account/reset-password-state.ts +111 -0
  59. package/templates/storefront-blank/src/lib/account/verify-state.ts +56 -0
  60. package/templates/storefront-blank/src/lib/account-actions.ts +76 -0
  61. package/templates/storefront-blank/src/lib/account-session.ts +114 -0
  62. package/templates/storefront-blank/src/lib/action-result.ts +30 -0
  63. package/templates/storefront-blank/src/lib/asset-alt.ts +34 -0
  64. package/templates/storefront-blank/src/lib/backend-actions.ts +20 -0
  65. package/templates/storefront-blank/src/lib/backend-client.ts +47 -0
  66. package/templates/storefront-blank/src/lib/cart-context.tsx +236 -0
  67. package/templates/storefront-blank/src/lib/checkout-session.ts +185 -0
  68. package/templates/storefront-blank/src/lib/content/page-metadata.ts +113 -0
  69. package/templates/storefront-blank/src/lib/content/render-fields.tsx +256 -0
  70. package/templates/storefront-blank/src/lib/content/resolve-page.ts +143 -0
  71. package/templates/storefront-blank/src/lib/error-messages.ts +118 -0
  72. package/templates/storefront-blank/src/lib/experiments.ts +333 -0
  73. package/templates/storefront-blank/src/lib/forgecart.ts +464 -0
  74. package/templates/storefront-blank/src/lib/format.ts +89 -0
  75. package/templates/storefront-blank/src/lib/identify-forward.ts +152 -0
  76. package/templates/storefront-blank/src/lib/locale/channel-locales-loader.ts +169 -0
  77. package/templates/storefront-blank/src/lib/locale/channel-locales-map.ts +46 -0
  78. package/templates/storefront-blank/src/lib/locale/channel-locales.ts +191 -0
  79. package/templates/storefront-blank/src/lib/locale/grammar.ts +194 -0
  80. package/templates/storefront-blank/src/lib/locale/localized-path.ts +55 -0
  81. package/templates/storefront-blank/src/lib/locale/middleware-plan.ts +107 -0
  82. package/templates/storefront-blank/src/lib/locale/request-binding.ts +80 -0
  83. package/templates/storefront-blank/src/lib/locale/request-locale.ts +66 -0
  84. package/templates/storefront-blank/src/lib/marketing-params.ts +213 -0
  85. package/templates/storefront-blank/src/lib/money.ts +50 -0
  86. package/templates/storefront-blank/src/lib/seo/alternates.ts +123 -0
  87. package/templates/storefront-blank/src/lib/seo/json-ld.ts +266 -0
  88. package/templates/storefront-blank/src/lib/seo/metadata.ts +419 -0
  89. package/templates/storefront-blank/src/lib/seo/noindex.ts +223 -0
  90. package/templates/storefront-blank/src/lib/seo/public-origin.ts +166 -0
  91. package/templates/storefront-blank/src/lib/seo/redirect-plan.ts +86 -0
  92. package/templates/storefront-blank/src/lib/seo/resolve-path.ts +107 -0
  93. package/templates/storefront-blank/src/lib/seo/scaffolded-routes.ts +83 -0
  94. package/templates/storefront-blank/src/lib/seo/sidecar.ts +75 -0
  95. package/templates/storefront-blank/src/lib/seo/site-verification.ts +98 -0
  96. package/templates/storefront-blank/src/lib/seo/sitemap-cache.ts +114 -0
  97. package/templates/storefront-blank/src/lib/seo/sitemap-entries.ts +310 -0
  98. package/templates/storefront-blank/src/lib/session-actions.ts +61 -0
  99. package/templates/storefront-blank/src/lib/session-cookies.ts +98 -0
  100. package/templates/storefront-blank/src/lib/shop-config.ts +51 -0
  101. package/templates/storefront-blank/src/lib/shop-session.ts +204 -0
  102. package/templates/storefront-blank/src/lib/track-forward.ts +200 -0
  103. package/templates/storefront-blank/src/lib/uuid.ts +19 -0
  104. package/templates/storefront-blank/src/middleware.ts +379 -0
  105. package/templates/storefront-blank/src/seo/redirects.ts +44 -0
  106. package/templates/storefront-blank/src/seo/static-routes.ts +12 -0
  107. package/templates/storefront-blank/src/server/app.module.ts +18 -0
  108. package/templates/storefront-blank/src/server/backend-api.ts +21 -0
  109. package/templates/storefront-blank/src/server/backend-method.decorator.ts +23 -0
  110. package/templates/storefront-blank/src/server/bootstrap.ts +122 -0
  111. package/templates/storefront-blank/src/server/forge/live-revision.ts +158 -0
  112. package/templates/storefront-blank/src/server/forgecart/forgecart-client.factory.ts +73 -0
  113. package/templates/storefront-blank/src/server/forgecart/forgecart.module.ts +9 -0
  114. package/templates/storefront-blank/src/server/runner.ts +90 -0
  115. package/templates/storefront-blank/src/server/types.ts +36 -0
  116. package/templates/storefront-blank/tsconfig.json +25 -0
  117. package/templates/storefront-blank-sdk-floor.json +1174 -0
  118. package/templates/storefront-shadcn-blank/.forgecartignore +2 -0
  119. package/templates/storefront-shadcn-blank/Procfile +1 -0
  120. package/templates/storefront-shadcn-blank/README.md +93 -0
  121. package/templates/storefront-shadcn-blank/SEO-MIGRATION.md +718 -0
  122. package/templates/storefront-shadcn-blank/_gitignore +21 -0
  123. package/templates/storefront-shadcn-blank/components.json +20 -0
  124. package/templates/storefront-shadcn-blank/next.config.js +105 -0
  125. package/templates/storefront-shadcn-blank/package.json +42 -0
  126. package/templates/storefront-shadcn-blank/postcss.config.js +5 -0
  127. package/templates/storefront-shadcn-blank/src/app/%5F%5Ffc/identify/route.ts +205 -0
  128. package/templates/storefront-shadcn-blank/src/app/%5F%5Ffc/track/route.ts +189 -0
  129. package/templates/storefront-shadcn-blank/src/app/%5F%5Fforge_beacon/route.ts +87 -0
  130. package/templates/storefront-shadcn-blank/src/app/api/%5F%5Fbackend/methods/route.ts +27 -0
  131. package/templates/storefront-shadcn-blank/src/app/error.tsx +23 -0
  132. package/templates/storefront-shadcn-blank/src/app/global-error.tsx +23 -0
  133. package/templates/storefront-shadcn-blank/src/app/globals.css +156 -0
  134. package/templates/storefront-shadcn-blank/src/app/layout.tsx +102 -0
  135. package/templates/storefront-shadcn-blank/src/app/page.tsx +53 -0
  136. package/templates/storefront-shadcn-blank/src/app/pages/[slug]/not-found.tsx +24 -0
  137. package/templates/storefront-shadcn-blank/src/app/pages/[slug]/page.tsx +115 -0
  138. package/templates/storefront-shadcn-blank/src/app/ping/route.ts +21 -0
  139. package/templates/storefront-shadcn-blank/src/app/robots.ts +69 -0
  140. package/templates/storefront-shadcn-blank/src/app/sitemap.ts +107 -0
  141. package/templates/storefront-shadcn-blank/src/components/ForgeErrorBeacon.tsx +102 -0
  142. package/templates/storefront-shadcn-blank/src/components/ForgeTracker.tsx +479 -0
  143. package/templates/storefront-shadcn-blank/src/components/ForgecartDesigner.tsx +43 -0
  144. package/templates/storefront-shadcn-blank/src/components/LocaleLink.tsx +49 -0
  145. package/templates/storefront-shadcn-blank/src/components/ui/alert.tsx +75 -0
  146. package/templates/storefront-shadcn-blank/src/components/ui/badge.tsx +40 -0
  147. package/templates/storefront-shadcn-blank/src/components/ui/button.tsx +64 -0
  148. package/templates/storefront-shadcn-blank/src/components/ui/card.tsx +28 -0
  149. package/templates/storefront-shadcn-blank/src/components/ui/input.tsx +26 -0
  150. package/templates/storefront-shadcn-blank/src/components/ui/label.tsx +22 -0
  151. package/templates/storefront-shadcn-blank/src/components/ui/native-select.tsx +27 -0
  152. package/templates/storefront-shadcn-blank/src/components/ui/skeleton.tsx +21 -0
  153. package/templates/storefront-shadcn-blank/src/components/ui/utils.ts +16 -0
  154. package/templates/storefront-shadcn-blank/src/instrumentation.ts +109 -0
  155. package/templates/storefront-shadcn-blank/src/lib/account/account-link.ts +76 -0
  156. package/templates/storefront-shadcn-blank/src/lib/account/account-state.ts +152 -0
  157. package/templates/storefront-shadcn-blank/src/lib/account/register-state.ts +133 -0
  158. package/templates/storefront-shadcn-blank/src/lib/account/reset-password-state.ts +111 -0
  159. package/templates/storefront-shadcn-blank/src/lib/account/verify-state.ts +56 -0
  160. package/templates/storefront-shadcn-blank/src/lib/account-actions.ts +76 -0
  161. package/templates/storefront-shadcn-blank/src/lib/account-session.ts +114 -0
  162. package/templates/storefront-shadcn-blank/src/lib/action-result.ts +30 -0
  163. package/templates/storefront-shadcn-blank/src/lib/asset-alt.ts +34 -0
  164. package/templates/storefront-shadcn-blank/src/lib/backend-actions.ts +20 -0
  165. package/templates/storefront-shadcn-blank/src/lib/backend-client.ts +47 -0
  166. package/templates/storefront-shadcn-blank/src/lib/cart-context.tsx +236 -0
  167. package/templates/storefront-shadcn-blank/src/lib/checkout-session.ts +185 -0
  168. package/templates/storefront-shadcn-blank/src/lib/content/page-metadata.ts +113 -0
  169. package/templates/storefront-shadcn-blank/src/lib/content/render-fields.tsx +256 -0
  170. package/templates/storefront-shadcn-blank/src/lib/content/resolve-page.ts +143 -0
  171. package/templates/storefront-shadcn-blank/src/lib/error-messages.ts +118 -0
  172. package/templates/storefront-shadcn-blank/src/lib/experiments.ts +333 -0
  173. package/templates/storefront-shadcn-blank/src/lib/forgecart.ts +464 -0
  174. package/templates/storefront-shadcn-blank/src/lib/format.ts +89 -0
  175. package/templates/storefront-shadcn-blank/src/lib/identify-forward.ts +152 -0
  176. package/templates/storefront-shadcn-blank/src/lib/locale/channel-locales-loader.ts +169 -0
  177. package/templates/storefront-shadcn-blank/src/lib/locale/channel-locales-map.ts +46 -0
  178. package/templates/storefront-shadcn-blank/src/lib/locale/channel-locales.ts +191 -0
  179. package/templates/storefront-shadcn-blank/src/lib/locale/grammar.ts +194 -0
  180. package/templates/storefront-shadcn-blank/src/lib/locale/localized-path.ts +55 -0
  181. package/templates/storefront-shadcn-blank/src/lib/locale/middleware-plan.ts +107 -0
  182. package/templates/storefront-shadcn-blank/src/lib/locale/request-binding.ts +80 -0
  183. package/templates/storefront-shadcn-blank/src/lib/locale/request-locale.ts +66 -0
  184. package/templates/storefront-shadcn-blank/src/lib/marketing-params.ts +213 -0
  185. package/templates/storefront-shadcn-blank/src/lib/money.ts +50 -0
  186. package/templates/storefront-shadcn-blank/src/lib/seo/alternates.ts +123 -0
  187. package/templates/storefront-shadcn-blank/src/lib/seo/json-ld.ts +266 -0
  188. package/templates/storefront-shadcn-blank/src/lib/seo/metadata.ts +419 -0
  189. package/templates/storefront-shadcn-blank/src/lib/seo/noindex.ts +223 -0
  190. package/templates/storefront-shadcn-blank/src/lib/seo/public-origin.ts +166 -0
  191. package/templates/storefront-shadcn-blank/src/lib/seo/redirect-plan.ts +86 -0
  192. package/templates/storefront-shadcn-blank/src/lib/seo/resolve-path.ts +107 -0
  193. package/templates/storefront-shadcn-blank/src/lib/seo/scaffolded-routes.ts +83 -0
  194. package/templates/storefront-shadcn-blank/src/lib/seo/sidecar.ts +75 -0
  195. package/templates/storefront-shadcn-blank/src/lib/seo/site-verification.ts +98 -0
  196. package/templates/storefront-shadcn-blank/src/lib/seo/sitemap-cache.ts +114 -0
  197. package/templates/storefront-shadcn-blank/src/lib/seo/sitemap-entries.ts +310 -0
  198. package/templates/storefront-shadcn-blank/src/lib/session-actions.ts +61 -0
  199. package/templates/storefront-shadcn-blank/src/lib/session-cookies.ts +98 -0
  200. package/templates/storefront-shadcn-blank/src/lib/shop-config.ts +51 -0
  201. package/templates/storefront-shadcn-blank/src/lib/shop-session.ts +204 -0
  202. package/templates/storefront-shadcn-blank/src/lib/track-forward.ts +200 -0
  203. package/templates/storefront-shadcn-blank/src/lib/uuid.ts +19 -0
  204. package/templates/storefront-shadcn-blank/src/middleware.ts +379 -0
  205. package/templates/storefront-shadcn-blank/src/seo/redirects.ts +44 -0
  206. package/templates/storefront-shadcn-blank/src/seo/static-routes.ts +12 -0
  207. package/templates/storefront-shadcn-blank/src/server/app.module.ts +18 -0
  208. package/templates/storefront-shadcn-blank/src/server/backend-api.ts +21 -0
  209. package/templates/storefront-shadcn-blank/src/server/backend-method.decorator.ts +23 -0
  210. package/templates/storefront-shadcn-blank/src/server/bootstrap.ts +122 -0
  211. package/templates/storefront-shadcn-blank/src/server/forge/live-revision.ts +158 -0
  212. package/templates/storefront-shadcn-blank/src/server/forgecart/forgecart-client.factory.ts +73 -0
  213. package/templates/storefront-shadcn-blank/src/server/forgecart/forgecart.module.ts +9 -0
  214. package/templates/storefront-shadcn-blank/src/server/runner.ts +90 -0
  215. package/templates/storefront-shadcn-blank/src/server/types.ts +36 -0
  216. package/templates/storefront-shadcn-blank/tsconfig.json +25 -0
  217. package/templates/storefront-shadcn-blank-sdk-floor.json +1174 -0
  218. package/templates/template-set.json +15 -0
@@ -0,0 +1,185 @@
1
+ 'use client';
2
+
3
+ import type { CreateAddressInput, SelectRateInput, SetCustomerInput } from '@forgecart/sdk/shop';
4
+
5
+ import type { ActionResult } from './action-result';
6
+ import type {
7
+ CheckoutOrder,
8
+ CheckoutOrderView,
9
+ ConfirmedPaymentSession,
10
+ EligiblePaymentProvider,
11
+ PaymentSession,
12
+ PaymentTemplate,
13
+ PaymentVariables,
14
+ ShippingRateGroup,
15
+ } from './forgecart';
16
+ import { runSessionOp } from './shop-session';
17
+
18
+ /**
19
+ * Checkout operations on the shopper's OWN websocket (`shop-session.ts`):
20
+ * contact → address → rates → payment → confirmation. Same names, same
21
+ * `ActionResult` envelopes as the retired Server-Action module — the flow
22
+ * components changed one import, not their contracts. Every operation here
23
+ * is session-scoped, which is exactly why it runs client-side; the only
24
+ * server-fetched checkout data is the public country list (channel read).
25
+ */
26
+
27
+ /** Attach the guest contact (email + name) to the active order. */
28
+ export async function setCustomer(
29
+ input: SetCustomerInput,
30
+ ): Promise<ActionResult<CheckoutOrderView | null>> {
31
+ return runSessionOp(
32
+ async (client) => (await client.cart.setCustomerForOrder({ input })).setCustomerForOrder,
33
+ );
34
+ }
35
+
36
+ /**
37
+ * Persist the shipping address. `countryId` accepts the canonical Country id
38
+ * OR an ISO code (an autocomplete suggestion's `countryCode` feeds it
39
+ * directly); `phoneNumber` is required server-side
40
+ * (SHIPPING_ADDRESS_PHONE_REQUIRED arrives as a typed field error otherwise).
41
+ */
42
+ export async function setShippingAddress(
43
+ input: CreateAddressInput,
44
+ ): Promise<ActionResult<CheckoutOrderView | null>> {
45
+ return runSessionOp(
46
+ async (client) =>
47
+ (await client.checkout.setOrderShippingAddress({ input })).setOrderShippingAddress,
48
+ );
49
+ }
50
+
51
+ /** Type-ahead address suggestions ([] = no provider → manual entry). */
52
+ export async function suggestAddresses(
53
+ query: string,
54
+ sessionToken: string,
55
+ countryCode?: string,
56
+ ): Promise<ActionResult<{ id: string; description: string }[]>> {
57
+ return runSessionOp(
58
+ async (client) =>
59
+ (await client.address.addressAutocomplete({ input: { query, sessionToken, countryCode } }))
60
+ .addressAutocomplete,
61
+ );
62
+ }
63
+
64
+ /** Resolve one suggestion into a structured address for the form. */
65
+ export async function resolveAddressSuggestion(
66
+ id: string,
67
+ sessionToken: string,
68
+ ): Promise<
69
+ ActionResult<{
70
+ streetLine1: string;
71
+ streetLine2?: string | null;
72
+ city?: string | null;
73
+ province?: string | null;
74
+ postalCode?: string | null;
75
+ countryCode: string;
76
+ }>
77
+ > {
78
+ return runSessionOp(
79
+ async (client) =>
80
+ (await client.address.addressSuggestion({ input: { id, sessionToken } })).addressSuggestion,
81
+ );
82
+ }
83
+
84
+ /**
85
+ * Explicit synchronous carrier re-quote — run on rate-step entry and after
86
+ * an address change. Per-provider failures arrive in each group's
87
+ * `lastRateErrors`; the read query never quotes (#863's read/refresh split).
88
+ */
89
+ export async function refreshShippingRates(): Promise<ActionResult<ShippingRateGroup[]>> {
90
+ return runSessionOp(
91
+ async (client) => (await client.shipping.refreshShippingRateGroups()).refreshShippingRateGroups,
92
+ );
93
+ }
94
+
95
+ /** Read the persisted rate groups (no carrier quoting). */
96
+ export async function getShippingRates(): Promise<ActionResult<ShippingRateGroup[]>> {
97
+ return runSessionOp(
98
+ async (client) => (await client.shipping.shippingRateGroups()).shippingRateGroups,
99
+ );
100
+ }
101
+
102
+ /** The CUSTOMER's rate pick — the ArrangingPayment gate requires one per group. */
103
+ export async function selectShippingRate(
104
+ input: SelectRateInput,
105
+ ): Promise<ActionResult<CheckoutOrderView | null>> {
106
+ return runSessionOp(
107
+ async (client) => (await client.shipping.selectShippingRate({ input })).selectShippingRate,
108
+ );
109
+ }
110
+
111
+ /** Advance the order to ArrangingPayment (gated: address+phone, items, selected rates). */
112
+ export async function transitionToPayment(): Promise<ActionResult<CheckoutOrderView | null>> {
113
+ return runSessionOp(
114
+ async (client) =>
115
+ (await client.checkout.transitionOrderToState({ input: { state: 'arrangingPayment' } }))
116
+ .transitionOrderToState,
117
+ );
118
+ }
119
+
120
+ /** Providers eligible for this order (eligibilityMessage is data, not an error). */
121
+ export async function getEligiblePaymentProviders(
122
+ orderId: string,
123
+ ): Promise<ActionResult<EligiblePaymentProvider[]>> {
124
+ return runSessionOp(
125
+ async (client) =>
126
+ (await client.payment.shopEligiblePaymentProviders({ orderId })).eligiblePaymentProviders,
127
+ );
128
+ }
129
+
130
+ /** Create the payment session (ONE per order — coupon edits freeze at this point). */
131
+ export async function createPaymentSession(
132
+ orderId: string,
133
+ providerCode?: string,
134
+ ): Promise<ActionResult<PaymentSession>> {
135
+ return runSessionOp(
136
+ async (client) =>
137
+ (await client.payment.createPaymentSession({ input: { orderId, providerCode } }))
138
+ .createPaymentSession,
139
+ );
140
+ }
141
+
142
+ /**
143
+ * Raw variables for the in-page payment adapter (`PaymentElementForm`):
144
+ * providerCode, publishableKey, scriptUrl, amount, currency — straight from
145
+ * the order + the channel's provider config, no session required.
146
+ */
147
+ export async function getPaymentVariables(
148
+ orderId: string,
149
+ ): Promise<ActionResult<PaymentVariables>> {
150
+ return runSessionOp(
151
+ async (client) => (await client.payment.getSessionVariables({ orderId })).getSessionVariables,
152
+ );
153
+ }
154
+
155
+ /**
156
+ * The provider's hosted form for `PaymentFormEmbed`. `origin` is the
157
+ * embedding storefront's own origin — the template pins every postMessage of
158
+ * the #865 handshake (iframe-ready / payment-success / payment-error) to it.
159
+ */
160
+ export async function getPaymentFormTemplate(
161
+ orderId: string,
162
+ origin: string,
163
+ ): Promise<ActionResult<PaymentTemplate>> {
164
+ return runSessionOp(
165
+ async (client) =>
166
+ (await client.payment.getSessionTemplate({ input: { orderId, origin } })).getSessionTemplate,
167
+ );
168
+ }
169
+
170
+ /** Confirm the session where the provider flow requires it (token/redirect models). */
171
+ export async function confirmPaymentSession(
172
+ sessionId: string,
173
+ ): Promise<ActionResult<ConfirmedPaymentSession>> {
174
+ return runSessionOp(
175
+ async (client) =>
176
+ (await client.payment.confirmPaymentSession({ sessionId })).confirmPaymentSession,
177
+ );
178
+ }
179
+
180
+ /** The settled order for the confirmation step (by the code shown to the shopper). */
181
+ export async function getOrderByCode(code: string): Promise<ActionResult<CheckoutOrder | null>> {
182
+ return runSessionOp(
183
+ async (client) => (await client.order.shopOrderByCode({ code })).orderByCode ?? null,
184
+ );
185
+ }
@@ -0,0 +1,113 @@
1
+ import { staticPathsByLocale } from '../seo/alternates';
2
+ import { entityMetadata } from '../seo/metadata';
3
+ import type { MetadataInput, SeoMetaRow } from '../seo/metadata';
4
+ import { advertisedPathsByLocale, ownContentRow } from '../seo/sidecar';
5
+ import type { SeoLanguageRow } from '../seo/sidecar';
6
+ import type { PageResolution } from './resolve-page';
7
+
8
+ /**
9
+ * What a page URL claims about its own CONTENT (#1375, epic launch#54 D11 as
10
+ * amended by ruling ANSWER-1547Z) — the half of `/pages/<route>`'s metadata the
11
+ * page decides, as a value the route spreads into `routeMetadata`.
12
+ *
13
+ * Every claim is read off the page record's per-language SEO sidecar, through
14
+ * the SAME rules the product route uses, so a page and a product can never
15
+ * disagree about what a language without its own copy is (ruling Q6: product
16
+ * parity):
17
+ *
18
+ * - title, description and social image come from `entityMetadata` — the
19
+ * sidecar's row for this locale, the title falling back to the page
20
+ * definition's own name, and the social card armed only by a STORED image
21
+ * (a page's mapped image never arms it);
22
+ * - the alternates cluster is the route's address in every language the
23
+ * channel offers, narrowed to the languages that hold content of their own
24
+ * (`advertisedPathsByLocale`) — an untranslated language is never an
25
+ * hreflang alternate, and its own copy claims no canonical;
26
+ * - indexability is the merchant's choice for a language with its own copy,
27
+ * and `false` for a derived fallback (`ownContentRow`).
28
+ *
29
+ * PURE BY CONTRACT, for the reason `resolve-page.ts` is: the template-spec
30
+ * imports it by relative path, which is what makes these claims a table of
31
+ * vectors rather than something only a running pod can exercise. The inputs
32
+ * are structural for the same reason — the generated query types satisfy them,
33
+ * so the scaffold's own compiler proves the assignment.
34
+ */
35
+
36
+ /** One language of a page record's sidecar, as these claims read it. */
37
+ export type PageSeoRow = SeoLanguageRow & SeoMetaRow;
38
+
39
+ /** The definition fields these claims read. */
40
+ export interface ClaimingPageGroup {
41
+ /** The merchant's name for the page — the title when the sidecar names none. */
42
+ readonly name: string;
43
+ }
44
+
45
+ /**
46
+ * The record fields these claims read.
47
+ *
48
+ * The sidecar is optional AND nullable because the generated selection is: the
49
+ * shop API answers `seo: null` for a definition that is not a page kind. A
50
+ * resolution that reached its `ok` arm has already proven the definition IS a
51
+ * page, so a null here means the API and the definition disagree — read, like a
52
+ * missing language row, as "no language has content of its own".
53
+ */
54
+ export interface ClaimingPageEntry {
55
+ readonly seo?: { readonly languages: readonly PageSeoRow[] } | null;
56
+ }
57
+
58
+ /** Where the page is being rendered, and every language its route exists in. */
59
+ export interface PageClaimsContext {
60
+ /** The locale this page is being rendered in. */
61
+ locale: string;
62
+ /** Every language the channel offers — the languages a page ROUTE exists in. */
63
+ languageCodes: readonly string[];
64
+ /** The locale-STRIPPED address of this page (`/pages/<route>`). */
65
+ pathname: string;
66
+ }
67
+
68
+ /** The content half of a page's `MetadataInput`, every key decided. */
69
+ export type PageContentClaims = Required<
70
+ Pick<
71
+ MetadataInput,
72
+ 'pathsByLocale' | 'title' | 'description' | 'socialImage' | 'contentIndexable'
73
+ >
74
+ >;
75
+
76
+ /** The sidecar a record without one is read as: no language holds its own copy. */
77
+ const NO_SIDECAR: NonNullable<ClaimingPageEntry['seo']> = { languages: [] };
78
+
79
+ /**
80
+ * A URL that is not a page claims nothing about content: no name of its own, no
81
+ * alternates, and not indexable — the refusal the product route states for a
82
+ * missing product, stated here rather than left to whatever Next does with the
83
+ * head of a page that answers 404.
84
+ */
85
+ const NOT_FOUND_CLAIMS: PageContentClaims = {
86
+ pathsByLocale: {},
87
+ title: null,
88
+ description: null,
89
+ socialImage: null,
90
+ contentIndexable: false,
91
+ };
92
+
93
+ /**
94
+ * Decide the content claims of one page URL from the resolution the route acts
95
+ * on — the same value that decides its status, so the head and the response
96
+ * can never disagree about what the URL is.
97
+ */
98
+ export function pageContentClaims(
99
+ resolution: PageResolution<ClaimingPageGroup, ClaimingPageEntry>,
100
+ { locale, languageCodes, pathname }: PageClaimsContext,
101
+ ): PageContentClaims {
102
+ if (resolution.kind === 'notFound') return NOT_FOUND_CLAIMS;
103
+
104
+ const seo = resolution.entry.seo ?? NO_SIDECAR;
105
+ const entity = entityMetadata({ name: resolution.group.name, seo }, locale);
106
+ return {
107
+ pathsByLocale: advertisedPathsByLocale(staticPathsByLocale(languageCodes, pathname), seo),
108
+ title: entity.title,
109
+ description: entity.description,
110
+ socialImage: entity.socialImage,
111
+ contentIndexable: ownContentRow(seo, locale)?.indexable ?? false,
112
+ };
113
+ }
@@ -0,0 +1,256 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ import { getAssetAlt } from '../asset-alt';
4
+ import type { PageEntry, PageFieldValue } from '../forgecart';
5
+
6
+ /**
7
+ * The generic renderer for an ACF page record (#1934, epic launch#50).
8
+ *
9
+ * A page definition is whatever the merchant built in the dashboard, so this
10
+ * file cannot know the fields it will be handed — it renders by ACF field TYPE,
11
+ * driven by the `__typename` the shop API discriminates every value with. The
12
+ * layout is deliberately plain: this is the scaffold a merchant edits, and a
13
+ * template that guessed at a design would be undone before it shipped.
14
+ *
15
+ * Order and labels come from the DEFINITIONS, not the values: the shop API
16
+ * returns field definitions in the order the editor arranged them, while the
17
+ * value list is storage order and means nothing to a reader. A definition with
18
+ * no value (a display-only field — a divider, a chart) simply has nothing to
19
+ * match and renders nothing, which is why no field-category test appears here.
20
+ *
21
+ * Repeater rows render one level deep, and the bound is a real one rather than
22
+ * an oversight: the shop operation unrolls the group tree three levels
23
+ * (fragments cannot recurse), so each level is a DIFFERENT generated type and a
24
+ * general recursion has no single shape to stand on. One level covers the
25
+ * "sections of a page" shape this route exists for; deeper trees are a
26
+ * follow-up with the operation change they need.
27
+ */
28
+
29
+ /** What this renderer needs of a field definition — nothing about its storage. */
30
+ export interface RenderableDefinition {
31
+ readonly fieldName: string;
32
+ readonly title?: string | null;
33
+ readonly hideTitle?: boolean | null;
34
+ readonly children?: readonly RenderableDefinition[] | null;
35
+ }
36
+
37
+ /** One repeater as an entry carries it: a named group of rows. */
38
+ type PageRepeater = PageEntry['groups'][number];
39
+
40
+ export function PageFields({
41
+ definitions,
42
+ entry,
43
+ }: {
44
+ definitions: readonly RenderableDefinition[];
45
+ entry: PageEntry;
46
+ }): ReactNode {
47
+ return (
48
+ <div className="space-y-8">
49
+ {definitions.map((definition) => (
50
+ <PageField
51
+ key={definition.fieldName}
52
+ definition={definition}
53
+ value={entry.fields.find((field) => field.name === definition.fieldName)}
54
+ repeater={entry.groups.find((group) => group.name === definition.fieldName)}
55
+ />
56
+ ))}
57
+ </div>
58
+ );
59
+ }
60
+
61
+ /**
62
+ * One definition's section — its label and whatever it holds.
63
+ *
64
+ * A field with neither a value nor rows renders NOTHING, not an empty section:
65
+ * an unpublished column reads as null (the shop API's live-column gating) and a
66
+ * merchant who left a field blank meant to leave it blank. Either way a bare
67
+ * heading over nothing is noise on a public page.
68
+ */
69
+ function PageField({
70
+ definition,
71
+ value,
72
+ repeater,
73
+ }: {
74
+ definition: RenderableDefinition;
75
+ value: PageFieldValue | undefined;
76
+ repeater: PageRepeater | undefined;
77
+ }): ReactNode {
78
+ const body = repeater ? renderRepeater(repeater, definition.children ?? []) : renderValue(value);
79
+ if (body === null) return null;
80
+
81
+ return (
82
+ // `data-fc-field` carries the field's identity into the DOM: it is what a
83
+ // merchant styles a single field by, and the stable hook the storefront
84
+ // e2e reads.
85
+ <section className="space-y-2" data-fc-field={definition.fieldName}>
86
+ {definition.hideTitle !== true && definition.title ? (
87
+ <h2 className="text-xl font-semibold tracking-tight">{definition.title}</h2>
88
+ ) : null}
89
+ {body}
90
+ </section>
91
+ );
92
+ }
93
+
94
+ /** A repeater's rows, each row rendering its own members through the same law. */
95
+ function renderRepeater(
96
+ repeater: PageRepeater,
97
+ members: readonly RenderableDefinition[],
98
+ ): ReactNode {
99
+ if (repeater.entries.length === 0) return null;
100
+ return (
101
+ <div className="space-y-6">
102
+ {repeater.entries.map((row) => (
103
+ <div key={row.id} className="space-y-2">
104
+ {members.map((member) => {
105
+ const body = renderValue(row.fields.find((field) => field.name === member.fieldName));
106
+ if (body === null) return null;
107
+ return (
108
+ <div key={member.fieldName} data-fc-field={member.fieldName}>
109
+ {member.hideTitle !== true && member.title ? (
110
+ <h3 className="text-sm font-semibold text-base-content/70">{member.title}</h3>
111
+ ) : null}
112
+ {body}
113
+ </div>
114
+ );
115
+ })}
116
+ </div>
117
+ ))}
118
+ </div>
119
+ );
120
+ }
121
+
122
+ /**
123
+ * One stored value as markup, or `null` when it holds nothing to show.
124
+ *
125
+ * An exhaustive `switch` over the discriminated union rather than a lookup
126
+ * table, and the `default` arm is the reason: it assigns the value to `never`,
127
+ * so adding an ACF field type without teaching this file about it is a COMPILE
128
+ * error in the merchant's scaffold instead of a section that silently renders
129
+ * blank on a live page.
130
+ */
131
+ function renderValue(value: PageFieldValue | undefined): ReactNode {
132
+ if (value === undefined) return null;
133
+
134
+ switch (value.__typename) {
135
+ case 'AcfStringValue':
136
+ return text(value.stringValue);
137
+ case 'AcfIntValue':
138
+ return absent(value.intValue) ? null : text(String(value.intValue));
139
+ case 'AcfFloatValue':
140
+ return absent(value.floatValue) ? null : text(String(value.floatValue));
141
+ case 'AcfBoolValue':
142
+ return absent(value.boolValue) ? null : text(value.boolValue ? 'Yes' : 'No');
143
+ case 'AcfDateValue':
144
+ // The API serializes DateTime as ISO 8601; rendered as `<time>` with the
145
+ // machine-readable value intact, and NOT locale-formatted here — a
146
+ // formatted date is a merchant presentation choice, and guessing one in
147
+ // the scaffold would be a decision nobody made.
148
+ return absent(value.dateValue) ? null : (
149
+ <time dateTime={value.dateValue} className="text-base-content/80">
150
+ {value.dateValue}
151
+ </time>
152
+ );
153
+ case 'AcfStringListValue':
154
+ return list(value.stringListValue);
155
+ case 'AcfJsonValue':
156
+ // Shown verbatim in a code block: JSON on a page is structured data the
157
+ // merchant's own components consume, and pretty-printing it here would be
158
+ // this file inventing a shape for someone else's payload.
159
+ return absent(value.jsonValue) ? null : (
160
+ <pre className="overflow-x-auto rounded-box bg-base-200 p-4 text-xs">{value.jsonValue}</pre>
161
+ );
162
+ case 'AcfRichTextValue':
163
+ // The server-rendered projection, never the raw delta: `html` is produced
164
+ // by the API's own sanitizing renderer, which is the one place that
165
+ // decision belongs.
166
+ //
167
+ // The guard is also what satisfies the seam: `dangerouslySetInnerHTML`
168
+ // takes `string | TrustedHTML`, so the projection is rendered only once
169
+ // it IS a string — never coerced past an absence with a `?? ''`, which
170
+ // would publish an empty rich-text section as if the merchant had
171
+ // written one.
172
+ return absent(value.html) ? null : (
173
+ <div
174
+ className="max-w-none text-base leading-relaxed text-base-content/80"
175
+ dangerouslySetInnerHTML={{ __html: value.html }}
176
+ />
177
+ );
178
+ case 'AcfAssetValue':
179
+ return value.asset ? <AssetImage asset={value.asset} /> : null;
180
+ case 'AcfAssetListValue':
181
+ return value.assets.length === 0 ? null : (
182
+ <div className="grid grid-cols-2 gap-4 sm:grid-cols-3">
183
+ {value.assets.map((asset) => (
184
+ <AssetImage key={asset.id} asset={asset} />
185
+ ))}
186
+ </div>
187
+ );
188
+ case 'AcfRelationValue':
189
+ // The API resolves the related row's display summary; the id alone would
190
+ // be meaningless on a storefront, and a dangling reference resolves to
191
+ // no summary at all, which renders as nothing rather than as a raw uuid.
192
+ return value.entity ? text(value.entity.label) : null;
193
+ case 'AcfRelationListValue':
194
+ return list(value.entities.map((entity) => entity.label));
195
+ default:
196
+ return exhausted(value);
197
+ }
198
+ }
199
+
200
+ /** One asset as an image — decorative unless the merchant described it. */
201
+ function AssetImage({ asset }: { asset: { preview: string; altText?: string | null } }): ReactNode {
202
+ return (
203
+ // eslint-disable-next-line @next/next/no-img-element
204
+ <img
205
+ src={asset.preview}
206
+ alt={getAssetAlt(asset)}
207
+ className="w-full rounded-box border border-base-300 object-cover"
208
+ />
209
+ );
210
+ }
211
+
212
+ function text(content: string | null | undefined): ReactNode {
213
+ if (absent(content) || content.trim() === '') return null;
214
+ return <p className="text-base leading-relaxed text-base-content/80">{content}</p>;
215
+ }
216
+
217
+ function list(items: readonly string[] | null | undefined): ReactNode {
218
+ if (absent(items) || items.length === 0) return null;
219
+ return (
220
+ <ul className="list-inside list-disc space-y-1 text-base-content/80">
221
+ {items.map((item, index) => (
222
+ <li key={`${item}-${String(index)}`}>{item}</li>
223
+ ))}
224
+ </ul>
225
+ );
226
+ }
227
+
228
+ /**
229
+ * Is there nothing here?
230
+ *
231
+ * The generated SDK marks every nullable selection OPTIONAL as well as
232
+ * nullable (`stringValue?: Maybe<string>`), so "the merchant left this empty"
233
+ * reaches this file as `null` from the API answering today and as `undefined`
234
+ * from any pod whose SDK does not carry the selection at all. Both mean the
235
+ * same thing to a reader, and collapsing them in ONE predicate is what keeps a
236
+ * `String(undefined)`, an empty `<time>` or a bare `No` off a live page — each
237
+ * of which a `=== null` check alone would have rendered.
238
+ */
239
+ function absent<T>(value: T | null | undefined): value is null | undefined {
240
+ return value === null || value === undefined;
241
+ }
242
+
243
+ /**
244
+ * The compile-time exhaustiveness check for the switch above: the parameter is
245
+ * `never`, so a new ACF field type stops this file from building until it is
246
+ * handled.
247
+ *
248
+ * At RUNTIME it renders nothing rather than throwing, and the asymmetry is
249
+ * deliberate. The only way to reach it is a pod whose generated SDK is older
250
+ * than the API answering it, and in that state the honest behaviour is to show
251
+ * the fields this build understands — a throw would blank the merchant's whole
252
+ * live page over one unknown section.
253
+ */
254
+ function exhausted(_value: never): null {
255
+ return null;
256
+ }