@ecomiq/storefront 2.1.3 → 3.0.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 (317) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +150 -123
  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 -81
  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/cloud-runtime-client.ts +0 -378
  238. package/src/codegen.mjs +0 -789
  239. package/src/components/account/customer-addresses-panel.tsx +0 -678
  240. package/src/components/account/customer-order-detail-panel.tsx +0 -285
  241. package/src/components/account/customer-orders-panel.tsx +0 -192
  242. package/src/components/account/customer-profile-panel.tsx +0 -379
  243. package/src/components/checkout/cart-sync.ts +0 -143
  244. package/src/components/checkout/checkout-contact-section.tsx +0 -76
  245. package/src/components/checkout/checkout-external-update-banner.tsx +0 -15
  246. package/src/components/checkout/checkout-form-controls.tsx +0 -139
  247. package/src/components/checkout/checkout-line-items.tsx +0 -43
  248. package/src/components/checkout/checkout-order-summary.tsx +0 -165
  249. package/src/components/checkout/checkout-payment-section.tsx +0 -212
  250. package/src/components/checkout/checkout-session-view.tsx +0 -564
  251. package/src/components/checkout/checkout-shipping-address-section.tsx +0 -400
  252. package/src/components/checkout/checkout-shipping-section.tsx +0 -98
  253. package/src/components/checkout/checkout-states.tsx +0 -73
  254. package/src/components/checkout/checkout-step-card.tsx +0 -101
  255. package/src/components/checkout/checkout-steps.ts +0 -111
  256. package/src/components/checkout/form-schema.ts +0 -281
  257. package/src/components/checkout/format.ts +0 -15
  258. package/src/components/checkout/index.ts +0 -5
  259. package/src/components/checkout/map-customer-to-checkout.ts +0 -188
  260. package/src/components/checkout/types.ts +0 -41
  261. package/src/components/checkout/use-checkout-customer-prefill.ts +0 -409
  262. package/src/components/checkout/use-checkout-geo.ts +0 -50
  263. package/src/components/checkout/use-checkout-session.ts +0 -640
  264. package/src/components/hero.tsx +0 -291
  265. package/src/config.d.ts +0 -94
  266. package/src/config.mjs +0 -146
  267. package/src/format-money.ts +0 -31
  268. package/src/health.ts +0 -90
  269. package/src/hooks/index.ts +0 -9
  270. package/src/hooks/recently-viewed.ts +0 -125
  271. package/src/index.ts +0 -354
  272. package/src/link.tsx +0 -2
  273. package/src/pages/account/account-page-skeleton.tsx +0 -69
  274. package/src/pages/account/account-page-view.tsx +0 -270
  275. package/src/pages/account/account-shell.tsx +0 -101
  276. package/src/pages/account/parse-account-route.ts +0 -36
  277. package/src/pages/account.tsx +0 -16
  278. package/src/pages/api.$.ts +0 -20
  279. package/src/pages/api.auth.$.ts +0 -16
  280. package/src/pages/checkout.$checkoutId.tsx +0 -60
  281. package/src/pages/checkout.tsx +0 -31
  282. package/src/pages/pages.$pageSlug.tsx +0 -111
  283. package/src/pages/thank-you.tsx +0 -53
  284. package/src/providers/auth.tsx +0 -96
  285. package/src/providers/cart.tsx +0 -275
  286. package/src/providers/ecomiq.ts +0 -137
  287. package/src/providers/index.ts +0 -26
  288. package/src/proxy-api.ts +0 -20
  289. package/src/public-config.ts +0 -24
  290. package/src/review-submission.ts +0 -89
  291. package/src/runtime/context.ts +0 -2
  292. package/src/runtime/default-layout.tsx +0 -64
  293. package/src/runtime/head.ts +0 -149
  294. package/src/runtime/page.tsx +0 -143
  295. package/src/runtime/postcss-browser-stub.mjs +0 -5
  296. package/src/server-config-store.ts +0 -35
  297. package/src/server.ts +0 -301
  298. package/src/storefront-definition/contract.ts +0 -1
  299. package/src/storefront-definition/data.ts +0 -188
  300. package/src/storefront-definition/index.ts +0 -2
  301. package/src/storefront-definition/renderer.tsx +0 -1481
  302. package/src/storefront-layout.ts +0 -59
  303. package/src/storefront-runtime.ts +0 -173
  304. package/src/styles.css +0 -97
  305. package/src/ui/badge.tsx +0 -32
  306. package/src/ui/button.tsx +0 -31
  307. package/src/ui/card.tsx +0 -31
  308. package/src/ui/carousel.tsx +0 -218
  309. package/src/ui/cn.ts +0 -6
  310. package/src/ui/index.ts +0 -8
  311. package/src/ui/separator.tsx +0 -6
  312. package/src/ui/skeleton.tsx +0 -6
  313. package/src/ui/storefront-error.tsx +0 -49
  314. package/src/utils.ts +0 -36
  315. package/src/vite-inline.mjs +0 -263
  316. package/templates/__root.tsx +0 -83
  317. package/templates/router.tsx +0 -19
@@ -1,89 +0,0 @@
1
- import type {
2
- CreateProductReviewParams,
3
- StorefrontReview,
4
- } from '@ecomiq/core'
5
- import { ApiError } from '@ecomiq/core'
6
- import { z } from 'zod'
7
-
8
- const ReviewProblemSchema = z.object({
9
- code: z.string().max(120).regex(/^[A-Za-z0-9._-]+$/).optional(),
10
- })
11
-
12
- interface ProductReviewClient {
13
- createProductReview(
14
- params: CreateProductReviewParams,
15
- ): Promise<StorefrontReview>
16
- }
17
-
18
- export interface ProductReviewSubmissionError {
19
- status: 400 | 409 | 429 | 500
20
- code?: string
21
- message: string
22
- }
23
-
24
- export type ProductReviewSubmissionResult =
25
- | {
26
- ok: true
27
- review: StorefrontReview
28
- }
29
- | {
30
- ok: false
31
- error: ProductReviewSubmissionError
32
- }
33
-
34
- export async function submitProductReview(
35
- client: ProductReviewClient,
36
- input: CreateProductReviewParams,
37
- ): Promise<ProductReviewSubmissionResult> {
38
- try {
39
- const review = await client.createProductReview(input)
40
- return { ok: true, review }
41
- } catch (error) {
42
- if (!(error instanceof ApiError)) throw error
43
-
44
- let code: string | undefined
45
- const problem = ReviewProblemSchema.safeParse(error.body)
46
- if (problem.success) code = problem.data.code
47
-
48
- if (error.status === 400) {
49
- let message = 'Revisa los datos de la reseña e inténtalo nuevamente.'
50
- if (
51
- code === 'ProductReview.OrderInvalid' ||
52
- code === 'ProductReview.OrderOwnerMismatch' ||
53
- code === 'ProductReview.ProductNotPurchased'
54
- ) {
55
- message = 'No pudimos verificar la compra con los datos ingresados.'
56
- }
57
- return { ok: false, error: { status: 400, code, message } }
58
- }
59
-
60
- if (error.status === 409) {
61
- let message = 'No pudimos registrar esta reseña porque ya existe una similar.'
62
- if (code === 'ProductReview.AlreadyExists') {
63
- message = 'Ya enviaste una reseña para este producto.'
64
- }
65
- return { ok: false, error: { status: 409, code, message } }
66
- }
67
-
68
- if (error.status === 429) {
69
- return {
70
- ok: false,
71
- error: {
72
- status: 429,
73
- code,
74
- message:
75
- 'Enviaste varias reseñas en poco tiempo. Espera un momento e inténtalo nuevamente.',
76
- },
77
- }
78
- }
79
-
80
- return {
81
- ok: false,
82
- error: {
83
- status: 500,
84
- code,
85
- message: 'No pudimos enviar la reseña. Inténtalo nuevamente.',
86
- },
87
- }
88
- }
89
- }
@@ -1,2 +0,0 @@
1
- export { AppEcomiqContext, AppEcomiqProvider, type AppEcomiqValue } from '../providers/ecomiq'
2
- export { AuthProvider } from '../providers/auth'
@@ -1,64 +0,0 @@
1
- /**
2
- * Layout por defecto de Ecomiq.
3
- * Nav y footer salen de settings.cms.menus (main / footer).
4
- */
5
- import { Link } from '@tanstack/react-router'
6
- import { getMenuItems } from '@ecomiq/core'
7
- import type { LayoutProps } from '../index'
8
-
9
- export default function DefaultLayout({ children, settings }: LayoutProps) {
10
- const mainItems = getMenuItems(settings, 'main')
11
- const footerItems = getMenuItems(settings, 'footer')
12
- const nav =
13
- mainItems.length > 0
14
- ? mainItems.map((item) => ({ label: item.label, to: item.url, target: item.target }))
15
- : [{ label: 'Productos', to: '/', target: null as string | null }]
16
-
17
- const storeName = settings?.store.name ?? 'Tienda'
18
-
19
- return (
20
- <div className="flex min-h-dvh flex-col">
21
- <header className="sticky top-0 z-40 border-b bg-background/90 backdrop-blur supports-[backdrop-filter]:bg-background/75">
22
- <div className="mx-auto flex h-14 w-full max-w-6xl items-center justify-between px-4">
23
- <Link to="/" className="font-semibold tracking-tight text-lg">
24
- {storeName}
25
- </Link>
26
- <nav className="flex items-center gap-5 text-sm text-muted-foreground">
27
- {nav.map((item) =>
28
- item.target === '_blank' ? (
29
- <a
30
- key={item.to}
31
- href={item.to}
32
- target="_blank"
33
- rel="noreferrer"
34
- className="transition-colors hover:text-foreground"
35
- >
36
- {item.label}
37
- </a>
38
- ) : (
39
- <Link key={item.to} to={item.to} className="transition-colors hover:text-foreground">
40
- {item.label}
41
- </Link>
42
- ),
43
- )}
44
- </nav>
45
- </div>
46
- </header>
47
- <main className="mx-auto w-full max-w-6xl flex-1 px-4 py-8">{children}</main>
48
- <footer className="border-t">
49
- <div className="mx-auto flex w-full max-w-6xl flex-col gap-4 px-4 py-6 text-sm text-muted-foreground sm:flex-row sm:items-center sm:justify-between">
50
- <p>{settings?.store.description || `${storeName} — creado con Ecomiq`}</p>
51
- {footerItems.length > 0 && (
52
- <nav className="flex flex-wrap gap-4">
53
- {footerItems.map((item) => (
54
- <Link key={item.url} to={item.url} className="transition-colors hover:text-foreground">
55
- {item.label}
56
- </Link>
57
- ))}
58
- </nav>
59
- )}
60
- </div>
61
- </footer>
62
- </div>
63
- )
64
- }
@@ -1,149 +0,0 @@
1
- /**
2
- * Runtime interno: construcción del <head>.
3
- * Mezcla SEO/branding/tracking de `/v1/settings` con el `meta` de cada página
4
- * (la página siempre gana) y genera los scripts de analytics del CMS.
5
- */
6
- import type { StorefrontSettings } from '@ecomiq/core'
7
- import type { PageMeta } from '../index'
8
- import {
9
- cmsBranding,
10
- cmsSeo,
11
- storeDisplayName,
12
- storeSeoDescription,
13
- } from '../utils'
14
-
15
- type MetaTag = Record<string, string>
16
- type LinkTag = Record<string, string>
17
- type ScriptTag = Record<string, string | boolean> & { children?: string }
18
-
19
- interface HeadResult {
20
- meta?: MetaTag[]
21
- links?: LinkTag[]
22
- scripts?: ScriptTag[]
23
- }
24
-
25
- function serializeJsonLd(value: Record<string, unknown>) {
26
- return JSON.stringify(value)
27
- .replace(/</g, '\\u003c')
28
- .replace(/\u2028/g, '\\u2028')
29
- .replace(/\u2029/g, '\\u2029')
30
- }
31
-
32
- function siteName(settings: StorefrontSettings | null | undefined): string {
33
- return storeDisplayName(settings) ?? 'Tienda'
34
- }
35
-
36
- /** Aplica titleTemplate del CMS (`{page} | {site}` o `%s · Tienda`). */
37
- export function resolveTitle(
38
- pageTitle: string | undefined,
39
- settings: StorefrontSettings | null | undefined,
40
- ): string {
41
- const name = siteName(settings)
42
- const seo = cmsSeo(settings)
43
- if (!pageTitle) return seo?.metaTitle ?? name
44
-
45
- const template = seo?.titleTemplate?.trim()
46
- if (!template) return `${pageTitle} · ${name}`
47
-
48
- return template
49
- .replaceAll('{page}', pageTitle)
50
- .replaceAll('{site}', name)
51
- .replace('%s', pageTitle)
52
- }
53
-
54
- /** Head global: charset, viewport, favicon, tracking. Va en el root. */
55
- export function buildRootHead(
56
- settings: StorefrontSettings | null | undefined,
57
- ): HeadResult {
58
- const branding = cmsBranding(settings)
59
- const seo = cmsSeo(settings)
60
-
61
- const meta: MetaTag[] = [
62
- { charSet: 'utf-8' },
63
- { name: 'viewport', content: 'width=device-width, initial-scale=1' },
64
- ]
65
- if (seo?.robotsIndex === false) {
66
- meta.push({ name: 'robots', content: 'noindex, nofollow' })
67
- }
68
-
69
- const links: LinkTag[] = [
70
- { rel: 'icon', href: branding?.faviconUrl ?? '/favicon.ico' },
71
- ]
72
-
73
- return { meta, links, scripts: buildAnalyticsScripts(settings) }
74
- }
75
-
76
- /** Head por página: title (con plantilla), description, OG/Twitter, robots, metas extra y JSON-LD. */
77
- export function buildPageHead(
78
- pageMeta: PageMeta | undefined,
79
- settings: StorefrontSettings | null | undefined,
80
- ): HeadResult {
81
- const branding = cmsBranding(settings)
82
- const seo = cmsSeo(settings)
83
- const title = resolveTitle(pageMeta?.title, settings)
84
- const description = pageMeta?.description ?? storeSeoDescription(settings) ?? undefined
85
- const ogImage = pageMeta?.ogImage ?? branding?.socialImageUrl ?? undefined
86
- const name = siteName(settings)
87
-
88
- const meta: MetaTag[] = [{ title }]
89
- if (description) meta.push({ name: 'description', content: description })
90
- if (pageMeta?.noIndex || seo?.robotsIndex === false) {
91
- meta.push({ name: 'robots', content: 'noindex, nofollow' })
92
- }
93
-
94
- meta.push({ property: 'og:title', content: title })
95
- meta.push({ property: 'og:type', content: 'website' })
96
- meta.push({ property: 'og:site_name', content: name })
97
- if (description) meta.push({ property: 'og:description', content: description })
98
- if (ogImage) {
99
- meta.push({ property: 'og:image', content: ogImage })
100
- meta.push({ name: 'twitter:card', content: 'summary_large_image' })
101
- meta.push({ name: 'twitter:image', content: ogImage })
102
- }
103
-
104
- for (const extra of pageMeta?.meta ?? []) meta.push(extra as MetaTag)
105
-
106
- const scripts: ScriptTag[] = []
107
- if (pageMeta?.jsonLd) {
108
- scripts.push({ type: 'application/ld+json', children: serializeJsonLd(pageMeta.jsonLd) })
109
- }
110
-
111
- return { meta, ...(scripts.length ? { scripts } : {}) }
112
- }
113
-
114
- /** Traduce tracking + customScripts del CMS a etiquetas <script>. */
115
- export function buildAnalyticsScripts(
116
- settings: StorefrontSettings | null | undefined,
117
- ): ScriptTag[] {
118
- const tracking = settings?.cms?.settings?.tracking
119
- const custom = settings?.cms?.settings?.customScripts
120
- const scripts: ScriptTag[] = []
121
-
122
- if (tracking?.googleAnalyticsMeasurementId) {
123
- const id = tracking.googleAnalyticsMeasurementId
124
- scripts.push({ src: `https://www.googletagmanager.com/gtag/js?id=${id}`, async: true })
125
- scripts.push({
126
- children: `window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments)};gtag('js',new Date());gtag('config','${id}');`,
127
- })
128
- }
129
-
130
- if (tracking?.googleTagManagerContainerId) {
131
- const id = tracking.googleTagManagerContainerId
132
- scripts.push({
133
- children: `(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src='https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);})(window,document,'script','dataLayer','${id}');`,
134
- })
135
- }
136
-
137
- if (tracking?.metaPixelId) {
138
- const id = tracking.metaPixelId
139
- scripts.push({
140
- children: `!function(f,b,e,v,n,t,s){if(f.fbq)return;n=f.fbq=function(){n.callMethod?n.callMethod.apply(n,arguments):n.queue.push(arguments)};if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';n.queue=[];t=b.createElement(e);t.async=!0;t.src=v;s=b.getElementsByTagName(e)[0];s.parentNode.insertBefore(t,s)}(window,document,'script','https://connect.facebook.net/en_US/fbevents.js');fbq('init','${id}');fbq('track','PageView');`,
141
- })
142
- }
143
-
144
- if (custom?.enabled && custom.head) {
145
- scripts.push({ children: custom.head })
146
- }
147
-
148
- return scripts
149
- }
@@ -1,143 +0,0 @@
1
- /**
2
- * Runtime interno de Ecomiq (solo lo importa el código generado en `.ecomiq`).
3
- * Traduce el contrato público `definePage` a las opciones del router interno.
4
- */
5
- import { useContext } from 'react'
6
- import { useRouterState } from '@tanstack/react-router'
7
- import type { StorefrontSettings } from '@ecomiq/core'
8
- import type { EcomiqPage, PublicEcomiqConfig } from '../index'
9
- import { AppEcomiqContext } from '../providers/ecomiq'
10
- import { serverApi } from '../server'
11
- import { StorefrontError } from '../ui/storefront-error'
12
- import { buildPageHead } from './head'
13
-
14
- function isSettings(value: unknown): value is StorefrontSettings {
15
- return Boolean(
16
- value &&
17
- typeof value === 'object' &&
18
- value !== null &&
19
- 'store' in value,
20
- )
21
- }
22
-
23
- async function resolveSettingsForMeta(
24
- matches?: Array<{ loaderData?: unknown }>,
25
- ): Promise<StorefrontSettings | undefined> {
26
- // Preferir el loader del root si ya está en el match (hidratación cliente).
27
- // En SSR `matches` suele venir stale (updateMatch no muta el array), así que
28
- // caemos a serverApi: el cliente core cachea settings y no hay HTTP extra.
29
- const fromRoot = matches?.[0]?.loaderData
30
- if (isSettings(fromRoot)) {
31
- return fromRoot
32
- }
33
- try {
34
- return await serverApi.getSettings()
35
- } catch {
36
- return undefined
37
- }
38
- }
39
-
40
- function usePageSettings(): StorefrontSettings | undefined {
41
- // Mismo módulo que AppEcomiqProvider (no el barrel) para evitar contextos duplicados.
42
- const fromProvider = useContext(AppEcomiqContext)?.settings
43
- const fromRootLoader = useRouterState({
44
- select: (state) => {
45
- const data = state.matches[0]?.loaderData
46
- return isSettings(data) ? data : undefined
47
- },
48
- })
49
- return fromProvider ?? fromRootLoader
50
- }
51
-
52
- export function pageToRouteOptions<
53
- TData,
54
- TParams extends Record<string, string>,
55
- TSearch extends Record<string, unknown>,
56
- >(
57
- page: EcomiqPage<TData, TParams, TSearch>,
58
- config: PublicEcomiqConfig,
59
- ) {
60
- const readSearch = page.validateSearch ?? ((raw: Record<string, unknown>) => raw as TSearch)
61
- const reloadOnSearch = page.reloadOnSearch !== false
62
- const stableLoaderDeps: Record<string, never> = {}
63
-
64
- return {
65
- preload: page.preload,
66
- shouldReload: reloadOnSearch ? undefined : false,
67
- validateSearch: readSearch,
68
- loaderDeps: ({ search }: { search: TSearch }) => reloadOnSearch ? search : stableLoaderDeps,
69
- loader: page.loader
70
- ? ({ params, deps, location }: {
71
- params: TParams
72
- deps: TSearch | Record<string, never>
73
- location?: { search?: Record<string, unknown> }
74
- }) => {
75
- let search = readSearch(location?.search ?? {})
76
- if (reloadOnSearch) {
77
- search = deps as TSearch
78
- }
79
- return page.loader!({
80
- params,
81
- search,
82
- config,
83
- })
84
- }
85
- : undefined,
86
- head: async ({ loaderData, params, match, matches }: {
87
- loaderData: TData
88
- params: TParams
89
- match: { search: TSearch }
90
- matches?: Array<{ loaderData?: unknown }>
91
- }) => {
92
- // Con loader: si TanStack calcula head antes de tener datos, degrada al head global.
93
- // Sin loader: loaderData es siempre undefined — aún así hay que llamar meta.
94
- const waitingForLoader = Boolean(page.loader) && loaderData === undefined
95
- if (waitingForLoader) return buildPageHead(undefined, undefined)
96
-
97
- const settings = page.meta
98
- ? await resolveSettingsForMeta(matches)
99
- : undefined
100
-
101
- return buildPageHead(
102
- page.meta?.({
103
- data: loaderData,
104
- params,
105
- search: match.search,
106
- config,
107
- settings,
108
- }),
109
- settings,
110
- )
111
- },
112
- pendingComponent: page.loading,
113
- errorComponent: page.error
114
- ? ({ error, reset }: { error: Error; reset: () => void }) => page.error!({ error, reset })
115
- : ({ reset }: { error: Error; reset: () => void }) => <StorefrontError onRetry={reset} />,
116
- }
117
- }
118
-
119
- export function PageBoundary<
120
- TData,
121
- TParams extends Record<string, string>,
122
- TSearch extends Record<string, unknown>,
123
- >({
124
- page,
125
- data,
126
- params,
127
- search,
128
- }: {
129
- page: EcomiqPage<TData, TParams, TSearch>
130
- data: TData
131
- params: TParams
132
- search: TSearch
133
- }) {
134
- const settings = usePageSettings()
135
- // Concurrent render / contexto duplicado: no tumbar el root.
136
- // El re-render sync con provider o el loader del root suelen recuperar.
137
- if (!settings) {
138
- const Loading = page.loading
139
- return Loading ? <Loading /> : null
140
- }
141
- const Component = page.component
142
- return <Component data={data} params={params} search={search} settings={settings} />
143
- }
@@ -1,5 +0,0 @@
1
- export function parse() {
2
- throw new Error('El storefront no admite estilos inline en HTML enriquecido.')
3
- }
4
-
5
- export default { parse }
@@ -1,35 +0,0 @@
1
- import type { CreateApiOptions } from '@ecomiq/core'
2
- import { createServerOnlyFn } from '@tanstack/react-start'
3
-
4
- export interface ServerEcomiqCloudConfig {
5
- baseUrl?: string
6
- artifactBaseUrl?: string
7
- storeSlug?: string
8
- timeoutMs?: number
9
- cacheTtlMs?: number
10
- }
11
-
12
- export interface ServerEcomiqConfig {
13
- domain: string
14
- api?: CreateApiOptions
15
- cloud?: ServerEcomiqCloudConfig
16
- }
17
-
18
- const serverConfigKey = Symbol.for('@ecomiq/storefront/server-config')
19
- const serverGlobal = globalThis as typeof globalThis & {
20
- [key: symbol]: ServerEcomiqConfig | undefined
21
- }
22
-
23
- export const registerServerConfig = createServerOnlyFn((config: ServerEcomiqConfig): void => {
24
- serverGlobal[serverConfigKey] = config
25
- })
26
-
27
- export const getServerConfig = createServerOnlyFn((): ServerEcomiqConfig => {
28
- const registered = serverGlobal[serverConfigKey]
29
- if (!registered) {
30
- throw new Error(
31
- 'Config server de Ecomiq no disponible. Ejecuta la app mediante `ecomiq dev|build`.',
32
- )
33
- }
34
- return registered
35
- })