@ecomiq/storefront 2.1.4 → 3.0.1

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 (310) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +377 -76
  3. package/dist/account-Dx4e3syi.js +258 -0
  4. package/dist/account-forms-AVk71ko7.js +39 -0
  5. package/dist/account-forms.d.ts +13 -0
  6. package/dist/account.d.ts +240 -0
  7. package/dist/address-defaults-BqjRhS82.js +14 -0
  8. package/dist/address-defaults.d.ts +11 -0
  9. package/dist/analytics/analytics-CG1Qagwl.js +148 -0
  10. package/dist/analytics/analytics.d.ts +1 -0
  11. package/dist/analytics/config-DJr-GRhs.js +62 -0
  12. package/dist/analytics/config.d.ts +22 -0
  13. package/dist/analytics/consent-B13rBpBs.js +57 -0
  14. package/dist/analytics/consent-banner-9QvRRy8v.js +239 -0
  15. package/dist/analytics/consent-banner.d.ts +20 -0
  16. package/dist/analytics/cookies-pLozllyB.js +100 -0
  17. package/dist/analytics/cookies.d.ts +6 -0
  18. package/dist/analytics/destinations-iECrlLIr.js +144 -0
  19. package/dist/analytics/events-Dt81tIp1.js +102 -0
  20. package/dist/analytics/events.d.ts +51 -0
  21. package/dist/analytics/tracker-p4yaoOyk.js +51 -0
  22. package/dist/api-cache-DsuIn5hX.js +159 -0
  23. package/dist/bank-accounts-DlEczQQ5.js +81 -0
  24. package/dist/bank-accounts.d.ts +4 -0
  25. package/dist/captcha-DAqaIx8m.js +117 -0
  26. package/dist/captcha.d.ts +34 -0
  27. package/dist/cart-D7DxZ2lD.js +30 -0
  28. package/dist/cart-actions-Bsub_FFG.js +116 -0
  29. package/dist/cart-actions.d.ts +15 -0
  30. package/dist/cart-drawer-B7PPsR69.js +22 -0
  31. package/dist/cart-drawer.d.ts +9 -0
  32. package/dist/cart-form-y9P1r_EX.js +81 -0
  33. package/dist/cart-form.d.ts +24 -0
  34. package/dist/cart-summary-ur2VWKr-.js +89 -0
  35. package/dist/cart-summary.d.ts +31 -0
  36. package/dist/cart.d.ts +4 -0
  37. package/dist/catalog-7gJ-l5hW.js +67 -0
  38. package/dist/catalog-hooks-B7ZP4wYY.js +312 -0
  39. package/dist/catalog-hooks.d.ts +111 -0
  40. package/dist/catalog.d.ts +5 -0
  41. package/dist/checkout-DVWSXANi.js +474 -0
  42. package/dist/checkout-form-CbOIdENC.js +131 -0
  43. package/dist/checkout-form.d.ts +20 -0
  44. package/dist/checkout.d.ts +86 -0
  45. package/dist/complaint-book-BvwLvfx0.js +117 -0
  46. package/dist/complaint-book.d.ts +62 -0
  47. package/dist/culqi-authentication-BGr20vAJ.js +204 -0
  48. package/dist/culqi-authentication.d.ts +15 -0
  49. package/dist/culqi-checkout-CTXpKgp_.js +55 -0
  50. package/dist/culqi-three-ds-HP3mfrvV.js +33 -0
  51. package/dist/culqi-three-ds.d.ts +29 -0
  52. package/dist/customer-avatar-CJXSDroK.js +12 -0
  53. package/dist/customer-avatar.d.ts +5 -0
  54. package/dist/customer-cookies-DHOgFAMV.js +45 -0
  55. package/dist/customer-csrf-C1oDX-Py.js +17 -0
  56. package/dist/customer-csrf.d.ts +3 -0
  57. package/dist/customer-document-fields-C1K-_DAD.js +70 -0
  58. package/dist/customer-document-fields.d.ts +9 -0
  59. package/dist/customer-login-form-C7AC6vXd.js +144 -0
  60. package/dist/customer-login-form.d.ts +17 -0
  61. package/dist/customer-registration-form-BEm_NEfP.js +93 -0
  62. package/dist/customer-registration-form.d.ts +8 -0
  63. package/dist/document-url-CGsAl5Tf.js +9 -0
  64. package/dist/documents-T1mzc_j8.js +38 -0
  65. package/dist/documents.d.ts +16 -0
  66. package/dist/entries/account.d.ts +11 -0
  67. package/dist/entries/account.js +10 -0
  68. package/dist/entries/analytics.d.ts +6 -0
  69. package/dist/entries/analytics.js +5 -0
  70. package/dist/entries/cart.d.ts +8 -0
  71. package/dist/entries/cart.js +7 -0
  72. package/dist/entries/checkout.d.ts +12 -0
  73. package/dist/entries/checkout.js +11 -0
  74. package/dist/entries/order.d.ts +11 -0
  75. package/dist/entries/order.js +11 -0
  76. package/dist/favicon-YpyhVJ9H.js +28 -0
  77. package/dist/favicon.d.ts +4 -0
  78. package/dist/form-BtQcbXlJ.js +18 -0
  79. package/dist/form-contracts-BNpHaTGJ.js +166 -0
  80. package/dist/form-contracts.d.ts +42 -0
  81. package/dist/form.d.ts +3 -0
  82. package/dist/hidden-CCWtOWKR.js +3 -0
  83. package/dist/hidden.d.ts +4 -0
  84. package/dist/index.d.ts +50 -0
  85. package/dist/index.js +50 -0
  86. package/dist/invoice-fields-BG0HjL6T.js +103 -0
  87. package/dist/invoice-fields.d.ts +39 -0
  88. package/dist/links-kFxBZfdb.js +96 -0
  89. package/dist/links.d.ts +21 -0
  90. package/dist/mercadopago-checkout-DC7RXa8k.js +27 -0
  91. package/dist/mercadopago-dialog-UTQev0MG.js +180 -0
  92. package/dist/mercadopago-dialog.d.ts +21 -0
  93. package/dist/money-DtGOtDri.js +32 -0
  94. package/dist/money.d.ts +1 -0
  95. package/dist/optimistic-cart-BveVOHuh.js +53 -0
  96. package/dist/optimistic-cart.d.ts +16 -0
  97. package/dist/order-display-DTt5WBbc.js +112 -0
  98. package/dist/order-display.d.ts +20 -0
  99. package/dist/order-draft-DxV1Uhm3.js +38 -0
  100. package/dist/order-draft.d.ts +16 -0
  101. package/dist/order-pages-Dm6l5m0m.js +38 -0
  102. package/dist/order-pages.d.ts +12 -0
  103. package/dist/order-payment-continuation-D_d5PGA4.js +212 -0
  104. package/dist/order-payment-continuation.d.ts +10 -0
  105. package/dist/order-payment-in-review-x4gdLFHZ.js +65 -0
  106. package/dist/order-payment-in-review.d.ts +5 -0
  107. package/dist/order-status-BJXLJwxq.js +162 -0
  108. package/dist/order-status.d.ts +89 -0
  109. package/dist/order-view-9IlBg5AF.js +62 -0
  110. package/dist/order-view.d.ts +12 -0
  111. package/dist/page-action-Ctkf4xC6.js +7 -0
  112. package/dist/page-seo-a5FJl8x5.js +262 -0
  113. package/dist/page-seo.d.ts +34 -0
  114. package/dist/pages-Ca_Gt6mq.js +150 -0
  115. package/dist/pages.d.ts +45 -0
  116. package/dist/payment-dialog-frame-B-Q_xQ94.js +57 -0
  117. package/dist/payment-logo-BoPaGJc_.js +46 -0
  118. package/dist/payment-logo-svgs-By9lDTLK.js +20 -0
  119. package/dist/payment-logo.d.ts +11 -0
  120. package/dist/payment-logos-BYvdwYyJ.js +23 -0
  121. package/dist/payment-methods-B8ocTgFZ.js +73 -0
  122. package/dist/payment-methods.d.ts +58 -0
  123. package/dist/payment-return-5cjypwIw.js +7 -0
  124. package/dist/place-CmW4hBWY.js +13 -0
  125. package/dist/place.d.ts +4 -0
  126. package/dist/preview-BiIBif3C.js +427 -0
  127. package/dist/preview-address-Cf0sJ-9p.js +21 -0
  128. package/dist/preview-cart-Dg2CVktS.js +376 -0
  129. package/dist/product-preview-BspMAecJ.js +51 -0
  130. package/dist/product-preview.d.ts +5 -0
  131. package/dist/product-variants-BNDHvs1y.js +46 -0
  132. package/dist/product-variants.d.ts +12 -0
  133. package/dist/public-pages-XtwlaLwt.js +194 -0
  134. package/dist/public-pages.d.ts +67 -0
  135. package/dist/registration-CpDcv8ql.js +91 -0
  136. package/dist/registration.d.ts +12 -0
  137. package/dist/request-security-CRsRMgwj.js +76 -0
  138. package/dist/rich-text-M-Ju8RL4.js +9 -0
  139. package/dist/rich-text.d.ts +5 -0
  140. package/dist/route-text-DcJw2SsK.js +9 -0
  141. package/dist/route-text.d.ts +4 -0
  142. package/dist/section-preview-CmsMl3yu.js +83 -0
  143. package/dist/section-preview.d.ts +12 -0
  144. package/dist/sections/account-addresses.d.ts +18 -0
  145. package/dist/sections/account-addresses.js +375 -0
  146. package/dist/sections/account-order.d.ts +12 -0
  147. package/dist/sections/account-order.js +592 -0
  148. package/dist/sections/account-orders.d.ts +8 -0
  149. package/dist/sections/account-orders.js +359 -0
  150. package/dist/sections/account-profile.d.ts +16 -0
  151. package/dist/sections/account-profile.js +123 -0
  152. package/dist/sections/checkout.d.ts +24 -0
  153. package/dist/sections/checkout.js +963 -0
  154. package/dist/sections/complaint-book.d.ts +8 -0
  155. package/dist/sections/complaint-book.js +538 -0
  156. package/dist/sections/design-system.d.ts +1 -0
  157. package/dist/sections/design-system.js +310 -0
  158. package/dist/sections/draft-invoice.d.ts +20 -0
  159. package/dist/sections/draft-invoice.js +158 -0
  160. package/dist/sections/order-tracking.d.ts +19 -0
  161. package/dist/sections/order-tracking.js +141 -0
  162. package/dist/sections-CNUMWTF_.js +77 -0
  163. package/dist/sections.d.ts +19 -0
  164. package/dist/seo-GpDRhAAA.js +78 -0
  165. package/dist/session-CKvmFJyg.js +379 -0
  166. package/dist/session.d.ts +24 -0
  167. package/dist/settings-B0NO2A6Y.js +78 -0
  168. package/dist/settings.d.ts +51 -0
  169. package/dist/source-marker-DhqSkW7n.js +4 -0
  170. package/dist/sources-B-3JFJfb.js +46 -0
  171. package/dist/sources.d.ts +7 -0
  172. package/dist/store-8Q-_LFrQ.js +152 -0
  173. package/dist/store-contact--hY6CoFb.js +19 -0
  174. package/dist/store-contact.d.ts +6 -0
  175. package/dist/store.d.ts +17 -0
  176. package/dist/storefront-language-DrMjVbmk.js +29 -0
  177. package/dist/storefront-language.d.ts +14 -0
  178. package/dist/storefront-text-D25ae1qf.js +11 -0
  179. package/dist/storefront-text-en-ljjjSS4q.js +1030 -0
  180. package/dist/storefront-text-pt-BtHaJLcp.js +1030 -0
  181. package/dist/storefront-text.d.ts +4 -0
  182. package/dist/stripe-dialog-Ev9s3Z62.js +396 -0
  183. package/dist/stripe-dialog.d.ts +30 -0
  184. package/dist/telemetry-HuZXWBPi.js +114 -0
  185. package/dist/telemetry.d.ts +10 -0
  186. package/dist/templates-CAqoNyTW.js +40 -0
  187. package/dist/templates.d.ts +15 -0
  188. package/dist/urls-CpGQPxvf.js +71 -0
  189. package/dist/urls.d.ts +18 -0
  190. package/dist/use-checkout-CZcxXJXi.js +42 -0
  191. package/dist/use-checkout-payment-9R-zacrJ.js +80 -0
  192. package/dist/use-checkout-payment.d.ts +18 -0
  193. package/dist/use-checkout.d.ts +36 -0
  194. package/dist/use-culqi-checkout-CrjLAipU.js +109 -0
  195. package/dist/use-order-payment-BKIz_IO0.js +45 -0
  196. package/dist/use-order-payment.d.ts +18 -0
  197. package/dist/use-payment-token-CErx-tN-.js +105 -0
  198. package/dist/vite-payment-logos-kziu7wKb.js +27 -0
  199. package/dist/vite-payment-logos.d.ts +24 -0
  200. package/dist/vite-public-assets-DW2fgtVb.js +209 -0
  201. package/dist/vite-public-assets.d.ts +29 -0
  202. package/dist/vite-sections-DTBr0-Kg.js +464 -0
  203. package/dist/vite-sections.d.ts +21 -0
  204. package/dist/vite-theme-ui-BMO-CDi3.js +41 -0
  205. package/dist/vite-theme-ui.d.ts +10 -0
  206. package/dist/vite.d.ts +36 -0
  207. package/dist/vite.js +127 -0
  208. package/dist/voucher-payment-CVFDZ1L8.js +66 -0
  209. package/dist/worker-CNi-gzCu.js +86 -0
  210. package/dist/worker.d.ts +27 -0
  211. package/package.json +100 -76
  212. package/src/sections/account-addresses.tsx +215 -0
  213. package/src/sections/account-order.tsx +424 -0
  214. package/src/sections/account-orders.tsx +259 -0
  215. package/src/sections/account-profile.tsx +70 -0
  216. package/src/sections/checkout.tsx +586 -0
  217. package/src/sections/complaint-book.tsx +325 -0
  218. package/src/sections/design-system.tsx +146 -0
  219. package/src/sections/draft-invoice.tsx +105 -0
  220. package/src/sections/order-tracking.tsx +81 -0
  221. package/src/vite-client.d.ts +4 -0
  222. package/bin/ecomiq.mjs +0 -179
  223. package/public/design-bridge.js +0 -318
  224. package/src/api-proxy.ts +0 -194
  225. package/src/auth/create-storefront-auth.ts +0 -94
  226. package/src/auth/ecomiq-oauth-password-client.ts +0 -26
  227. package/src/auth/ecomiq-oauth-password-constants.ts +0 -24
  228. package/src/auth/ecomiq-oauth-password-plugin.ts +0 -294
  229. package/src/auth/ecomiq-oauth-password-types.ts +0 -24
  230. package/src/auth/ecomiq-storefront-sign-in.ts +0 -44
  231. package/src/auth/env.ts +0 -7
  232. package/src/auth/secure-cookies.ts +0 -23
  233. package/src/auth/storefront-auth-base-url.ts +0 -117
  234. package/src/auth/storefront-auth.ts +0 -7
  235. package/src/cart-session.ts +0 -239
  236. package/src/client.ts +0 -48
  237. package/src/codegen.mjs +0 -801
  238. package/src/components/account/customer-addresses-panel.tsx +0 -678
  239. package/src/components/account/customer-order-detail-panel.tsx +0 -285
  240. package/src/components/account/customer-orders-panel.tsx +0 -192
  241. package/src/components/account/customer-profile-panel.tsx +0 -379
  242. package/src/components/checkout/cart-sync.ts +0 -143
  243. package/src/components/checkout/checkout-contact-section.tsx +0 -76
  244. package/src/components/checkout/checkout-external-update-banner.tsx +0 -15
  245. package/src/components/checkout/checkout-form-controls.tsx +0 -139
  246. package/src/components/checkout/checkout-line-items.tsx +0 -43
  247. package/src/components/checkout/checkout-order-summary.tsx +0 -165
  248. package/src/components/checkout/checkout-payment-section.tsx +0 -212
  249. package/src/components/checkout/checkout-session-view.tsx +0 -564
  250. package/src/components/checkout/checkout-shipping-address-section.tsx +0 -400
  251. package/src/components/checkout/checkout-shipping-section.tsx +0 -98
  252. package/src/components/checkout/checkout-states.tsx +0 -73
  253. package/src/components/checkout/checkout-step-card.tsx +0 -101
  254. package/src/components/checkout/checkout-steps.ts +0 -111
  255. package/src/components/checkout/form-schema.ts +0 -281
  256. package/src/components/checkout/format.ts +0 -15
  257. package/src/components/checkout/index.ts +0 -5
  258. package/src/components/checkout/map-customer-to-checkout.ts +0 -188
  259. package/src/components/checkout/types.ts +0 -41
  260. package/src/components/checkout/use-checkout-customer-prefill.ts +0 -409
  261. package/src/components/checkout/use-checkout-geo.ts +0 -50
  262. package/src/components/checkout/use-checkout-session.ts +0 -640
  263. package/src/components/hero.tsx +0 -291
  264. package/src/config.d.ts +0 -94
  265. package/src/config.mjs +0 -146
  266. package/src/format-money.ts +0 -31
  267. package/src/health.ts +0 -90
  268. package/src/hooks/index.ts +0 -9
  269. package/src/hooks/recently-viewed.ts +0 -125
  270. package/src/index.ts +0 -349
  271. package/src/link.tsx +0 -2
  272. package/src/pages/account/account-page-skeleton.tsx +0 -69
  273. package/src/pages/account/account-page-view.tsx +0 -270
  274. package/src/pages/account/account-shell.tsx +0 -101
  275. package/src/pages/account/parse-account-route.ts +0 -36
  276. package/src/pages/account.tsx +0 -16
  277. package/src/pages/api.$.ts +0 -20
  278. package/src/pages/api.auth.$.ts +0 -16
  279. package/src/pages/checkout.$checkoutId.tsx +0 -60
  280. package/src/pages/checkout.tsx +0 -31
  281. package/src/pages/pages.$pageSlug.tsx +0 -74
  282. package/src/pages/thank-you.tsx +0 -53
  283. package/src/providers/auth.tsx +0 -96
  284. package/src/providers/cart.tsx +0 -275
  285. package/src/providers/ecomiq.ts +0 -137
  286. package/src/providers/index.ts +0 -26
  287. package/src/proxy-api.ts +0 -20
  288. package/src/public-config.ts +0 -24
  289. package/src/review-submission.ts +0 -89
  290. package/src/runtime/context.ts +0 -2
  291. package/src/runtime/default-layout.tsx +0 -64
  292. package/src/runtime/head.ts +0 -149
  293. package/src/runtime/page.tsx +0 -143
  294. package/src/runtime/postcss-browser-stub.mjs +0 -5
  295. package/src/server-config-store.ts +0 -35
  296. package/src/server.ts +0 -286
  297. package/src/styles.css +0 -76
  298. package/src/ui/badge.tsx +0 -32
  299. package/src/ui/button.tsx +0 -31
  300. package/src/ui/card.tsx +0 -31
  301. package/src/ui/carousel.tsx +0 -218
  302. package/src/ui/cn.ts +0 -6
  303. package/src/ui/index.ts +0 -8
  304. package/src/ui/separator.tsx +0 -6
  305. package/src/ui/skeleton.tsx +0 -6
  306. package/src/ui/storefront-error.tsx +0 -49
  307. package/src/utils.ts +0 -36
  308. package/src/vite-inline.mjs +0 -263
  309. package/templates/__root.tsx +0 -83
  310. package/templates/router.tsx +0 -19
@@ -0,0 +1,77 @@
1
+ import "./source-marker-DhqSkW7n.js";
2
+ import { store } from "./store-8Q-_LFrQ.js";
3
+ import { data, useLoaderData } from "react-router";
4
+ import { Suspense, createElement, use } from "react";
5
+ // src/sections.ts
6
+ const SECTIONS = "ecomiqSections";
7
+ function canonical(value) {
8
+ if (Array.isArray(value)) return value.map(canonical);
9
+ if (typeof value !== "object" || value === null) return value;
10
+ return Object.fromEntries(Object.keys(value).sort().map((key) => [key, canonical(value[key])]));
11
+ }
12
+ function sectionKey(section, props) {
13
+ const sourced = String(props["data-ecomiq-sources"] ?? "").split(" ");
14
+ const own = Object.entries(props).filter(([key, value]) => key !== "children" && !key.startsWith("data-") && !sourced.includes(key) && value !== void 0);
15
+ return `${section}:${JSON.stringify(canonical(Object.fromEntries(own)))}`;
16
+ }
17
+ function loadSections(args, entries) {
18
+ const client = store(args.context);
19
+ const unique = new Map(entries.map((entry) => [sectionKey(entry.section, entry.props), entry]));
20
+ return Object.fromEntries([...unique].map(([key, entry]) => {
21
+ return [key, Promise.resolve().then(() => entry.load(entry.props, {
22
+ ...args,
23
+ client
24
+ })).catch((error) => {
25
+ console.error("[ecomiq] section load failed", entry.section, error);
26
+ return null;
27
+ })];
28
+ }));
29
+ }
30
+ function withSectionBoundary(Component) {
31
+ return function SectionBoundary(props) {
32
+ const fallback = createElement("div", {
33
+ "aria-hidden": true,
34
+ style: { minHeight: "24rem" }
35
+ });
36
+ return createElement(Suspense, { fallback }, createElement(Component, props));
37
+ };
38
+ }
39
+ function withResponseInit(value) {
40
+ return typeof value === "object" && value !== null && value.type === "DataWithResponseInit";
41
+ }
42
+ function plain(value) {
43
+ return typeof value === "object" && value !== null && !Array.isArray(value);
44
+ }
45
+ function withSections(result, sections, extra = {}) {
46
+ if (result instanceof Response) return result;
47
+ if (withResponseInit(result)) return data(withSections(result.data, sections, extra), result.init ?? void 0);
48
+ if (result === void 0 || result === null) return {
49
+ ...extra,
50
+ [SECTIONS]: sections
51
+ };
52
+ if (!plain(result)) return result;
53
+ const own = result[SECTIONS] ?? {};
54
+ return {
55
+ ...result,
56
+ ...extra,
57
+ [SECTIONS]: {
58
+ ...own,
59
+ ...sections
60
+ }
61
+ };
62
+ }
63
+ function loadedSections(loaded) {
64
+ if (!plain(loaded)) return void 0;
65
+ const own = loaded[SECTIONS];
66
+ if (own !== void 0) return own;
67
+ const nested = loaded.data;
68
+ return plain(nested) ? nested[SECTIONS] : void 0;
69
+ }
70
+ function useSection(load, props) {
71
+ const loaded = useLoaderData();
72
+ const section = load.section;
73
+ if (section === void 0) return void 0;
74
+ const value = loadedSections(loaded)?.[sectionKey(section, props)];
75
+ return value instanceof Promise ? use(value) : value;
76
+ }
77
+ export { loadSections, sectionKey, useSection, withSectionBoundary, withSections };
@@ -0,0 +1,19 @@
1
+ import { LoaderFunctionArgs } from "react-router";
2
+ import { StorefrontClient } from "@ecomiq/core";
3
+ import { ComponentType } from "react";
4
+ export type SectionLoadArgs = LoaderFunctionArgs & {
5
+ readonly client: StorefrontClient;
6
+ };
7
+ export type SectionEntry = {
8
+ readonly section: string;
9
+ readonly load: (props: never, args: SectionLoadArgs) => Promise<unknown>;
10
+ readonly props: object;
11
+ };
12
+ export type SectionData = {
13
+ readonly [key: string]: unknown;
14
+ };
15
+ export declare function sectionKey(section: string, props: object): string;
16
+ export declare function loadSections(args: LoaderFunctionArgs, entries: readonly SectionEntry[]): SectionData;
17
+ export declare function withSectionBoundary<Props extends object>(Component: ComponentType<Props>): ComponentType<Props>;
18
+ export declare function withSections(result: unknown, sections: SectionData, extra?: object): unknown;
19
+ export declare function useSection<Props extends object, Data>(load: (props: Props, args: SectionLoadArgs) => Promise<Data>, props: Props): Data | undefined;
@@ -0,0 +1,78 @@
1
+ import { store } from "./store-8Q-_LFrQ.js";
2
+ import { absoluteUrl, canonicalOrigin } from "./urls-CpGQPxvf.js";
3
+ // src/seo.ts
4
+ const PRIVATE_PATHS = [
5
+ "/cart",
6
+ "/checkout",
7
+ "/account",
8
+ "/search"
9
+ ];
10
+ const CACHE = "public, max-age=30";
11
+ function escape(value) {
12
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
13
+ }
14
+ async function robots(request, context) {
15
+ const settings = await store(context).getSettings();
16
+ const lines = ["User-agent: *"];
17
+ if (settings.cms.settings.seo.robotsIndex) {
18
+ for (const path of PRIVATE_PATHS) lines.push(`Disallow: ${path}`);
19
+ lines.push("", `Sitemap: ${absoluteUrl(canonicalOrigin(request, settings), "/sitemap.xml")}`);
20
+ } else lines.push("Disallow: /");
21
+ return new Response(`${lines.join("\n")}\n`, { headers: {
22
+ "Content-Type": "text/plain; charset=utf-8",
23
+ "Cache-Control": CACHE
24
+ } });
25
+ }
26
+ function sitemapResponse(body) {
27
+ return new Response(`<?xml version="1.0" encoding="UTF-8"?>\n${body}\n`, { headers: {
28
+ "Content-Type": "application/xml; charset=utf-8",
29
+ "Cache-Control": CACHE
30
+ } });
31
+ }
32
+ async function sitemap(request, context) {
33
+ const pageParam = new URL(request.url).searchParams.get("page");
34
+ const pageNumber = pageParam === null ? 1 : Number(pageParam);
35
+ if (pageParam !== null && (!/^[1-9]\d*$/.test(pageParam) || !Number.isSafeInteger(pageNumber))) return new Response("Página inválida", { status: 400 });
36
+ const client = store(context);
37
+ const [settings, page] = await Promise.all([client.getSettings(), client.getSitemap(pageNumber)]);
38
+ if (pageNumber > page.totalPages) return new Response(null, { status: 404 });
39
+ const origin = canonicalOrigin(request, settings);
40
+ if (pageParam === null && page.totalPages > 1) return sitemapResponse(`<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n${Array.from({ length: page.totalPages }, (_, index) => {
41
+ const url = new URL(absoluteUrl(origin, "/sitemap.xml"));
42
+ url.searchParams.set("page", String(index + 1));
43
+ return `<sitemap><loc>${escape(url.href)}</loc></sitemap>`;
44
+ }).join("\n")}\n</sitemapindex>`);
45
+ const items = [];
46
+ if (pageNumber === 1) {
47
+ items.push({
48
+ url: "/",
49
+ updatedOn: null
50
+ });
51
+ for (const cmsPage of settings.cms.pages) {
52
+ if (cmsPage.channel !== "storefront" || !cmsPage.hasPublishedVersion || cmsPage.slug === "home") continue;
53
+ items.push({
54
+ url: `/pages/${cmsPage.slug}`,
55
+ updatedOn: cmsPage.publishedOn ?? cmsPage.updatedOn
56
+ });
57
+ }
58
+ }
59
+ items.push(...page.items);
60
+ return sitemapResponse(`<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n${items.map((item) => {
61
+ let entry = `<url><loc>${escape(absoluteUrl(origin, item.url))}</loc>`;
62
+ if (item.updatedOn !== null) entry += `<lastmod>${escape(item.updatedOn)}</lastmod>`;
63
+ return `${entry}</url>`;
64
+ }).join("\n")}\n</urlset>`);
65
+ }
66
+ async function seoResponse(request, context) {
67
+ if (request.method !== "GET" && request.method !== "HEAD") return null;
68
+ const path = new URL(request.url).pathname;
69
+ let response = null;
70
+ if (path === "/robots.txt") response = await robots(request, context);
71
+ if (path === "/sitemap.xml") response = await sitemap(request, context);
72
+ if (response === null || request.method === "GET") return response;
73
+ return new Response(null, {
74
+ status: response.status,
75
+ headers: response.headers
76
+ });
77
+ }
78
+ export { seoResponse };
@@ -0,0 +1,379 @@
1
+ import { loadStorefrontTranslations, storefrontText } from "./storefront-text-D25ae1qf.js";
2
+ import { captchaResponse } from "./captcha-DAqaIx8m.js";
3
+ import { base64Url, customerCookies, customerSessionId, equalSecret } from "./customer-cookies-DHOgFAMV.js";
4
+ import { safeNext } from "./form-BtQcbXlJ.js";
5
+ import { PREVIEW_TOKEN } from "./preview-BiIBif3C.js";
6
+ import { customerClientHeaders, customerCsrfTokenForRequest, rejectCrossOriginAction } from "./request-security-CRsRMgwj.js";
7
+ import { store } from "./store-8Q-_LFrQ.js";
8
+ import { requestUrl } from "./urls-CpGQPxvf.js";
9
+ import { worker } from "./worker-CNi-gzCu.js";
10
+ import { data, redirect } from "react-router";
11
+ import { ApiError } from "@ecomiq/core";
12
+ import { z } from "zod";
13
+ // src/session.ts
14
+ const currentRequests = /* @__PURE__ */ new WeakMap();
15
+ const registrationRequests = /* @__PURE__ */ new WeakMap();
16
+ const PRIVATE_HEADERS = {
17
+ "Cache-Control": "private, no-store",
18
+ "Referrer-Policy": "no-referrer"
19
+ };
20
+ const TransactionSchema = z.object({
21
+ state: z.string().min(1),
22
+ nonce: z.string().min(1),
23
+ verifier: z.string().min(43).max(128),
24
+ redirectUri: z.string().url(),
25
+ next: z.string(),
26
+ createdAt: z.number(),
27
+ loginMode: z.enum(["hosted", "storefront"]),
28
+ authorizationEndpoint: z.string().url(),
29
+ returnUrl: z.string(),
30
+ browserKey: z.string(),
31
+ email: z.string().default(""),
32
+ challengeId: z.string().optional(),
33
+ needsSecondFactor: z.boolean().default(false),
34
+ retryAt: z.number().default(0)
35
+ });
36
+ const ChallengeSchema = z.object({ challengeId: z.string().min(1) });
37
+ const ChallengeErrorSchema = z.object({
38
+ message: z.string(),
39
+ needsSecondFactor: z.boolean().optional()
40
+ });
41
+ function validLoginTransaction(transaction, configuration, origin) {
42
+ if (Date.now() - transaction.createdAt > 6e5 || transaction.createdAt > Date.now() || transaction.redirectUri !== new URL("/account/callback", origin).href || transaction.redirectUri !== configuration.redirectUri || transaction.authorizationEndpoint !== configuration.authorizationEndpoint || transaction.loginMode !== configuration.loginMode) return false;
43
+ const authorize = new URL(transaction.returnUrl, configuration.authority);
44
+ return authorize.origin === new URL(configuration.authorizationEndpoint).origin && authorize.pathname === new URL(configuration.authorizationEndpoint).pathname && authorize.searchParams.get("client_id") === configuration.clientId && authorize.searchParams.get("scope") === configuration.scope;
45
+ }
46
+ async function loginFormData(transaction, configuration, context, error = null, status = 200, clearSession = false) {
47
+ let step = "email";
48
+ if (transaction.challengeId) step = "code";
49
+ if (transaction.needsSecondFactor) step = "secondFactor";
50
+ const cookies = customerCookies(context.get(worker).env);
51
+ const headers = new Headers(PRIVATE_HEADERS);
52
+ headers.append("Set-Cookie", await cookies.transaction.serialize(transaction));
53
+ if (clearSession) headers.append("Set-Cookie", await cookies.session.serialize("", { maxAge: 0 }));
54
+ return data({
55
+ step,
56
+ email: transaction.email,
57
+ captcha: configuration.captcha,
58
+ retryAt: transaction.retryAt,
59
+ error
60
+ }, {
61
+ status,
62
+ headers
63
+ });
64
+ }
65
+ async function customerCsrfToken(request, context) {
66
+ return customerCsrfTokenForRequest(request, context.get(worker).env);
67
+ }
68
+ function previewing(request, env) {
69
+ return env.ECOMIQ_PREVIEW === "1" && !/^\/account\/(login|callback)(\/|$)/.test(requestUrl(request).pathname);
70
+ }
71
+ async function readCustomer(request, context) {
72
+ const env = context.get(worker).env;
73
+ if (previewing(request, env)) return {
74
+ sessionId: PREVIEW_TOKEN,
75
+ name: "Ana"
76
+ };
77
+ const sessionId = await customerSessionId(request, env);
78
+ if (!sessionId) return null;
79
+ try {
80
+ const session = await store(context).getCustomerSession({ headers: { "X-Customer-Session": sessionId } });
81
+ if (Date.parse(session.expiresAt) <= Date.now()) return null;
82
+ return {
83
+ sessionId,
84
+ name: session.customer.firstName || session.customer.email
85
+ };
86
+ } catch (error) {
87
+ if (error instanceof ApiError && (error.status === 401 || error.status === 403)) return null;
88
+ throw error;
89
+ }
90
+ }
91
+ function customer(request, context) {
92
+ let pending = currentRequests.get(request);
93
+ if (!pending) {
94
+ pending = readCustomer(request, context);
95
+ currentRequests.set(request, pending);
96
+ }
97
+ return pending;
98
+ }
99
+ async function requireCustomer(request, context) {
100
+ const current = await customer(request, context);
101
+ if (current) {
102
+ await requireCustomerRegistration(request, context, current);
103
+ return current;
104
+ }
105
+ const url = requestUrl(request);
106
+ throw redirect(`/account/login?next=${encodeURIComponent(url.pathname + url.search)}`, { headers: PRIVATE_HEADERS });
107
+ }
108
+ /** Loads customer data alongside the session and registration checks and returns it only after both pass. */
109
+ async function requireCustomerWith(request, context, load) {
110
+ const env = context.get(worker).env;
111
+ const sessionId = previewing(request, env) ? PREVIEW_TOKEN : await customerSessionId(request, env);
112
+ if (!sessionId) return load(customerHeaders(await requireCustomer(request, context)));
113
+ const headers = customerHeaders({
114
+ sessionId,
115
+ name: ""
116
+ });
117
+ if (sessionId !== "ecomiq-editor-preview" && !registrationRequests.has(request)) {
118
+ const registration = store(context).getCustomerRegistration(headers);
119
+ registration.catch(() => void 0);
120
+ registrationRequests.set(request, registration);
121
+ }
122
+ const pending = load(headers);
123
+ pending.catch(() => void 0);
124
+ await requireCustomer(request, context);
125
+ return pending;
126
+ }
127
+ /** Enforces profile completion for authenticated account and checkout requests. */
128
+ async function requireCustomerRegistration(request, context, current) {
129
+ if (current.sessionId === "ecomiq-editor-preview") return;
130
+ let pending = registrationRequests.get(request);
131
+ if (!pending) {
132
+ pending = store(context).getCustomerRegistration(customerHeaders(current));
133
+ registrationRequests.set(request, pending);
134
+ }
135
+ if ((await pending).requiresCompletion) {
136
+ const url = requestUrl(request);
137
+ throw redirect(`/account/register?next=${encodeURIComponent(safeNext(url.pathname + url.search))}`, {
138
+ status: 303,
139
+ headers: PRIVATE_HEADERS
140
+ });
141
+ }
142
+ }
143
+ function customerHeaders(current) {
144
+ return { headers: { "X-Customer-Session": current.sessionId } };
145
+ }
146
+ function identityTransportUrl(endpoint, context) {
147
+ const transportUrl = context.get(worker).env.ECOMIQ_AUTH_URL;
148
+ if (!transportUrl) return endpoint;
149
+ const transport = new URL(transportUrl);
150
+ if (!["http:", "https:"].includes(transport.protocol) || transport.username || transport.password || transport.pathname !== "/" || transport.search || transport.hash) throw new Error("ECOMIQ_AUTH_URL debe ser el origen de Identity.");
151
+ const target = new URL(endpoint);
152
+ target.protocol = transport.protocol;
153
+ target.host = transport.host;
154
+ return target.href;
155
+ }
156
+ function randomSecret() {
157
+ return base64Url(crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(32)));
158
+ }
159
+ async function loginFailure(message, status, headers, context) {
160
+ const language = (await store(context).getSettings().catch(() => null))?.store.language ?? "es";
161
+ const translations = await loadStorefrontTranslations(language);
162
+ const t = (source) => storefrontText(translations, source);
163
+ headers.set("Content-Type", "text/html; charset=utf-8");
164
+ headers.set("Content-Security-Policy", "default-src 'none'; base-uri 'none'; frame-ancestors 'none'");
165
+ return new Response(`<!doctype html><html lang="${language}"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>${t("No pudimos iniciar sesión")}</title></head><body><main><h1>${t("No pudimos iniciar sesión")}</h1><p>${t(message)}</p><p><a href="/account/login">${t("Intentar de nuevo")}</a></p><p><a href="/">${t("Volver a la tienda")}</a></p></main></body></html>`, {
166
+ status,
167
+ headers
168
+ });
169
+ }
170
+ async function startCustomerLogin(request, context) {
171
+ const url = new URL(request.url);
172
+ const next = safeNext(url.searchParams.get("next"));
173
+ const current = await customer(request, context);
174
+ if (current) {
175
+ if ((await store(context).getCustomerRegistration(customerHeaders(current))).requiresCompletion) return redirect(`/account/register?next=${encodeURIComponent(next)}`, { headers: PRIVATE_HEADERS });
176
+ return redirect(next, { headers: PRIVATE_HEADERS });
177
+ }
178
+ const clearSession = await customerSessionId(request, context.get(worker).env) !== null;
179
+ const configuration = await store(context).upsertCustomerAuthConfiguration(url.origin);
180
+ const redirectUri = new URL("/account/callback", url.origin).href;
181
+ if (configuration.redirectUri !== redirectUri) throw new Error("El dominio de acceso no corresponde a esta tienda.");
182
+ const saved = TransactionSchema.safeParse(await customerCookies(context.get(worker).env).transaction.parse(request.headers.get("Cookie")));
183
+ if (configuration.loginMode === "storefront" && saved.success && validLoginTransaction(saved.data, configuration, url.origin)) return loginFormData(saved.data, configuration, context, null, 200, clearSession);
184
+ const verifier = randomSecret();
185
+ const challenge = base64Url(new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier))));
186
+ const state = randomSecret();
187
+ const nonce = randomSecret();
188
+ const authorize = new URL(configuration.authorizationEndpoint);
189
+ authorize.search = new URLSearchParams({
190
+ client_id: configuration.clientId,
191
+ response_type: "code",
192
+ redirect_uri: redirectUri,
193
+ scope: configuration.scope,
194
+ code_challenge: challenge,
195
+ code_challenge_method: "S256",
196
+ state,
197
+ nonce
198
+ }).toString();
199
+ const transaction = {
200
+ state,
201
+ nonce,
202
+ verifier,
203
+ redirectUri,
204
+ next,
205
+ createdAt: Date.now(),
206
+ loginMode: configuration.loginMode,
207
+ authorizationEndpoint: configuration.authorizationEndpoint,
208
+ returnUrl: authorize.pathname + authorize.search,
209
+ browserKey: randomSecret(),
210
+ email: "",
211
+ needsSecondFactor: false,
212
+ retryAt: 0
213
+ };
214
+ if (configuration.loginMode === "storefront") return loginFormData(transaction, configuration, context, null, 200, clearSession);
215
+ const cookies = customerCookies(context.get(worker).env);
216
+ const headers = new Headers(PRIVATE_HEADERS);
217
+ headers.append("Set-Cookie", await cookies.transaction.serialize(transaction));
218
+ if (clearSession) headers.append("Set-Cookie", await cookies.session.serialize("", { maxAge: 0 }));
219
+ return redirect(authorize.href, { headers });
220
+ }
221
+ /** Receives only browser form fields; the signed transaction owns all OAuth values. */
222
+ async function submitCustomerLogin(request, context) {
223
+ const rejected = rejectCrossOriginAction(request);
224
+ if (rejected) return rejected;
225
+ const origin = new URL(request.url).origin;
226
+ const configuration = await store(context).upsertCustomerAuthConfiguration(origin);
227
+ const cookies = customerCookies(context.get(worker).env);
228
+ const saved = TransactionSchema.safeParse(await cookies.transaction.parse(request.headers.get("Cookie")));
229
+ if (configuration.loginMode !== "storefront" || !saved.success || !validLoginTransaction(saved.data, configuration, origin)) return redirect("/account/login", {
230
+ status: 303,
231
+ headers: {
232
+ ...PRIVATE_HEADERS,
233
+ "Set-Cookie": await cookies.transaction.serialize("", { maxAge: 0 })
234
+ }
235
+ });
236
+ const transaction = saved.data;
237
+ const form = await request.formData();
238
+ const field = (name) => {
239
+ const value = form.get(name);
240
+ return typeof value === "string" ? value.trim() : "";
241
+ };
242
+ const intent = field("intent");
243
+ if (intent === "reset") {
244
+ transaction.email = "";
245
+ transaction.challengeId = void 0;
246
+ transaction.needsSecondFactor = false;
247
+ return loginFormData(transaction, configuration, context);
248
+ }
249
+ if (intent === "send" || intent === "resend") {
250
+ if (transaction.retryAt > Date.now()) return loginFormData(transaction, configuration, context, "Espera un minuto antes de solicitar otro código.", 429);
251
+ const email = intent === "resend" ? transaction.email : field("email");
252
+ if (!z.string().email().max(254).safeParse(email).success) return loginFormData(transaction, configuration, context, "Ingresa un correo electrónico válido.", 400);
253
+ if (!configuration.captcha.isConfigured) return loginFormData(transaction, configuration, context, "La tienda debe completar la configuración de verificación. Intenta más tarde.", 503);
254
+ const captchaToken = captchaResponse(form, configuration.captcha.provider);
255
+ if (configuration.captcha.provider !== "off" && !captchaToken) return loginFormData(transaction, configuration, context, "Completa la verificación antes de continuar.", 400);
256
+ try {
257
+ const challengeUrl = identityTransportUrl(new URL("/v1/customer-auth/challenges", configuration.authority).href, context);
258
+ const client = await customerClientHeaders(request, context.get(worker).env, new URL(challengeUrl));
259
+ const response = await fetch(challengeUrl, {
260
+ method: "POST",
261
+ redirect: "manual",
262
+ headers: {
263
+ "Content-Type": "application/json",
264
+ ...client
265
+ },
266
+ body: JSON.stringify({
267
+ returnUrl: transaction.returnUrl,
268
+ browserKey: transaction.browserKey,
269
+ email,
270
+ captchaToken: captchaToken || void 0
271
+ })
272
+ });
273
+ if (!response.ok) return loginIdentityError(response, transaction, configuration, context);
274
+ const challenge = ChallengeSchema.parse(await response.json());
275
+ transaction.email = email;
276
+ transaction.challengeId = challenge.challengeId;
277
+ transaction.needsSecondFactor = false;
278
+ transaction.retryAt = Date.now() + 6e4;
279
+ return loginFormData(transaction, configuration, context);
280
+ } catch (error) {
281
+ console.error("[ecomiq] customer login code failed", error);
282
+ return loginFormData(transaction, configuration, context, "No pudimos enviar el código. Intenta de nuevo en unos minutos.", 503);
283
+ }
284
+ }
285
+ if (intent !== "verify" || !transaction.challengeId) return loginFormData(transaction, configuration, context, "Solicita un código para continuar.", 400);
286
+ const code = field("code");
287
+ const secondFactor = field("secondFactor");
288
+ if (!transaction.needsSecondFactor && !/^\d{6}$/.test(code)) return loginFormData(transaction, configuration, context, "Ingresa el código de seis dígitos que enviamos a tu correo.", 400);
289
+ if (transaction.needsSecondFactor && !secondFactor) return loginFormData(transaction, configuration, context, "Ingresa el código de tu aplicación de autenticación o uno de recuperación.", 400);
290
+ try {
291
+ const authorize = new URL(transaction.returnUrl, configuration.authority);
292
+ const authorizeUrl = identityTransportUrl(transaction.authorizationEndpoint, context);
293
+ const client = await customerClientHeaders(request, context.get(worker).env, new URL(authorizeUrl));
294
+ const response = await fetch(authorizeUrl, {
295
+ method: "POST",
296
+ redirect: "manual",
297
+ headers: {
298
+ ...client,
299
+ "Content-Type": "application/x-www-form-urlencoded",
300
+ "X-Customer-Challenge": transaction.challengeId,
301
+ "X-Customer-Browser-Key": transaction.browserKey,
302
+ "X-Customer-Code": code,
303
+ "X-Customer-Second-Factor": secondFactor
304
+ },
305
+ body: authorize.searchParams
306
+ });
307
+ if (response.status !== 302) return loginIdentityError(response, transaction, configuration, context);
308
+ const location = response.headers.get("Location");
309
+ if (!location) throw new Error("Missing customer callback");
310
+ const callback = new URL(location);
311
+ const expected = new URL(transaction.redirectUri);
312
+ if (callback.origin !== expected.origin || callback.pathname !== expected.pathname || callback.hash || !equalSecret(callback.searchParams.get("state") ?? "", transaction.state)) throw new Error("Invalid customer callback");
313
+ return completeCustomerLogin(new Request(callback, { headers: { Cookie: request.headers.get("Cookie") ?? "" } }), context);
314
+ } catch (error) {
315
+ console.error("[ecomiq] customer login verification failed", error);
316
+ return loginFormData(transaction, configuration, context, "No pudimos completar el acceso. Intenta de nuevo en unos minutos.", 503);
317
+ }
318
+ }
319
+ async function loginIdentityError(response, transaction, configuration, context) {
320
+ const error = ChallengeErrorSchema.safeParse(await response.json().catch(() => null));
321
+ let message = "No pudimos completar el acceso. Intenta de nuevo en unos minutos.";
322
+ if (error.success && response.status < 500) {
323
+ message = error.data.message;
324
+ if (error.data.needsSecondFactor) transaction.needsSecondFactor = true;
325
+ }
326
+ if (response.status === 429) transaction.retryAt = Date.now() + 6e4;
327
+ const status = response.status >= 400 && response.status < 500 ? response.status : 503;
328
+ return loginFormData(transaction, configuration, context, message, status);
329
+ }
330
+ async function completeCustomerLogin(request, context) {
331
+ const url = new URL(request.url);
332
+ const cookies = customerCookies(context.get(worker).env);
333
+ const transaction = TransactionSchema.safeParse(await cookies.transaction.parse(request.headers.get("Cookie")));
334
+ const headers = new Headers(PRIVATE_HEADERS);
335
+ headers.append("Set-Cookie", await cookies.transaction.serialize("", { maxAge: 0 }));
336
+ if (!transaction.success || !equalSecret(url.searchParams.get("state") ?? "", transaction.data.state) || Date.now() - transaction.data.createdAt > 6e5 || transaction.data.createdAt > Date.now() || transaction.data.redirectUri !== new URL("/account/callback", url.origin).href) return loginFailure("El acceso venció o no corresponde a esta tienda. Vuelve a iniciar sesión.", 400, headers, context);
337
+ const code = url.searchParams.get("code");
338
+ if (url.searchParams.has("error") || !code) return loginFailure("No se completó el acceso. Vuelve a iniciar sesión desde la tienda.", 400, headers, context);
339
+ try {
340
+ const session = await store(context).createCustomerSession({
341
+ code,
342
+ codeVerifier: transaction.data.verifier,
343
+ redirectUri: transaction.data.redirectUri,
344
+ nonce: transaction.data.nonce
345
+ });
346
+ headers.append("Set-Cookie", await cookies.session.serialize(session.sessionId, { expires: new Date(session.expiresAt) }));
347
+ const next = safeNext(transaction.data.next);
348
+ if ((await store(context).getCustomerRegistration({ headers: { "X-Customer-Session": session.sessionId } })).requiresCompletion) return redirect(`/account/register?next=${encodeURIComponent(next)}`, { headers });
349
+ return redirect(next, { headers });
350
+ } catch (error) {
351
+ if (error instanceof ApiError && error.status >= 400 && error.status < 500) return loginFailure("No pudimos habilitar tu cuenta en esta tienda. Vuelve a iniciar sesión o contacta a la tienda.", error.status, headers, context);
352
+ return loginFailure("No pudimos completar el acceso. Intenta iniciar sesión de nuevo en unos minutos.", 503, headers, context);
353
+ }
354
+ }
355
+ async function logout(request, context) {
356
+ const env = context.get(worker).env;
357
+ const origin = new URL(request.url).origin;
358
+ const configuration = await store(context).upsertCustomerAuthConfiguration(origin);
359
+ if (configuration.redirectUri !== new URL("/account/callback", origin).href) throw new Error("El dominio de salida no corresponde a esta tienda.");
360
+ const storeId = /^ecomiq-customer-(\d+)$/.exec(configuration.clientId)?.[1];
361
+ if (!storeId) throw new Error("La configuración de salida de la tienda no es válida.");
362
+ const sessionId = await customerSessionId(request, env);
363
+ if (sessionId) try {
364
+ await store(context).deleteCustomerSession({ headers: { "X-Customer-Session": sessionId } });
365
+ } catch (error) {
366
+ if (!(error instanceof ApiError) || error.status !== 401 && error.status !== 404) throw error;
367
+ }
368
+ const hostedLogout = new URL("/customer/logout", configuration.authority);
369
+ hostedLogout.searchParams.set("storeId", storeId);
370
+ hostedLogout.searchParams.set("origin", origin);
371
+ return redirect(hostedLogout.href, {
372
+ status: 303,
373
+ headers: {
374
+ ...PRIVATE_HEADERS,
375
+ "Set-Cookie": await customerCookies(env).session.serialize("", { maxAge: 0 })
376
+ }
377
+ });
378
+ }
379
+ export { completeCustomerLogin, customer, customerCsrfToken, customerHeaders, logout, requireCustomer, requireCustomerRegistration, requireCustomerWith, startCustomerLogin, submitCustomerLogin };
@@ -0,0 +1,24 @@
1
+ import { RouterContextProvider, data } from "react-router";
2
+ import { CustomerAuthConfiguration, CustomerRequestOptions } from "@ecomiq/core";
3
+ export type Customer = {
4
+ sessionId: string;
5
+ name: string;
6
+ };
7
+ export type CustomerLoginData = {
8
+ step: "email" | "code" | "secondFactor";
9
+ email: string;
10
+ captcha: CustomerAuthConfiguration["captcha"];
11
+ retryAt: number;
12
+ error: string | null;
13
+ };
14
+ export declare function customerCsrfToken(request: Request, context: Readonly<RouterContextProvider>): Promise<string>;
15
+ export declare function customer(request: Request, context: Readonly<RouterContextProvider>): Promise<Customer | null>;
16
+ export declare function requireCustomer(request: Request, context: Readonly<RouterContextProvider>): Promise<Customer>;
17
+ /** Enforces profile completion for authenticated account and checkout requests. */
18
+ export declare function requireCustomerRegistration(request: Request, context: Readonly<RouterContextProvider>, current: Customer): Promise<void>;
19
+ export declare function customerHeaders(current: Customer): CustomerRequestOptions;
20
+ export declare function startCustomerLogin(request: Request, context: Readonly<RouterContextProvider>): Promise<Response | ReturnType<typeof data<CustomerLoginData>>>;
21
+ /** Receives only browser form fields; the signed transaction owns all OAuth values. */
22
+ export declare function submitCustomerLogin(request: Request, context: Readonly<RouterContextProvider>): Promise<Response | ReturnType<typeof data<CustomerLoginData>>>;
23
+ export declare function completeCustomerLogin(request: Request, context: Readonly<RouterContextProvider>): Promise<Response>;
24
+ export declare function logout(request: Request, context: Readonly<RouterContextProvider>): Promise<Response>;
@@ -0,0 +1,78 @@
1
+ // src/settings.ts
2
+ function link(url, _label, reference) {
3
+ return reference ? {
4
+ url,
5
+ reference
6
+ } : url;
7
+ }
8
+ function select(value, _label, _options) {
9
+ return value;
10
+ }
11
+ const setting = {
12
+ text: (value, _label) => value,
13
+ richtext: (html, _label) => html,
14
+ image: (url, _label) => url,
15
+ video: (url, _label) => url,
16
+ link,
17
+ collection: (handle, _label) => handle,
18
+ category: (handle, _label) => handle,
19
+ product: (handle, _label) => handle,
20
+ products: (handles, _label) => handles,
21
+ number: (value, _label) => value,
22
+ range: (value, _label, _limits) => value,
23
+ toggle: (value, _label) => value,
24
+ color: (value, _label) => value,
25
+ select,
26
+ scheme: (id, _label) => id
27
+ };
28
+ function plain(value) {
29
+ return typeof value === "object" && value !== null && !Array.isArray(value);
30
+ }
31
+ function section(defaults, props) {
32
+ const values = { ...defaults };
33
+ for (const key of Object.keys(defaults)) {
34
+ const value = props[key];
35
+ if (value === void 0) continue;
36
+ const base = defaults[key];
37
+ values[key] = plain(base) && plain(value) ? {
38
+ ...base,
39
+ ...value
40
+ } : value;
41
+ }
42
+ return values;
43
+ }
44
+ async function pickProducts(client, source) {
45
+ const limit = source.limit ?? 8;
46
+ if (source.products && source.products.length > 0) {
47
+ const slugs = source.products.slice(0, limit);
48
+ return (await client.getProducts({
49
+ slugs,
50
+ pageSize: slugs.length,
51
+ includeFilters: false
52
+ })).products;
53
+ }
54
+ if (source.collection) {
55
+ const collections = await client.getCollections().catch(() => []);
56
+ const slug = source.collection.trim().toLowerCase();
57
+ const collection = collections.find((item) => item.slug.toLowerCase() === slug);
58
+ if (collection === void 0) return [];
59
+ return (await client.getProducts({
60
+ collectionId: collection.id,
61
+ pageSize: limit,
62
+ sort: source.sort,
63
+ includeFilters: false
64
+ })).products;
65
+ }
66
+ if (source.category) return (await client.getProducts({
67
+ categories: [source.category],
68
+ pageSize: limit,
69
+ sort: source.sort,
70
+ includeFilters: false
71
+ })).products;
72
+ return (await client.getProducts({
73
+ pageSize: limit,
74
+ sort: source.sort,
75
+ includeFilters: false
76
+ })).products;
77
+ }
78
+ export { pickProducts, section, setting };