@ecomiq/storefront 2.1.4 → 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 (310) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +150 -103
  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
@@ -1,379 +0,0 @@
1
- import { useId, useRef, useState, type FormEvent } from 'react'
2
- import { z } from 'zod'
3
- import {
4
- customerProfileToPatch,
5
- type CustomerProfile,
6
- type CustomerProfilePatch,
7
- } from '@ecomiq/core'
8
- import { Loader2, Pencil } from 'lucide-react'
9
- import { Button, cn } from '../../ui'
10
- import {
11
- CheckoutSelectField,
12
- CheckoutTextField,
13
- FieldError,
14
- } from '../checkout/checkout-form-controls'
15
-
16
- const IDENTIFICATION_TYPES = ['DNI', 'CE', 'Pasaporte', 'RUC'] as const
17
-
18
- const nameSchema = z.object({
19
- firstName: z.string().trim().min(1, 'Nombre requerido').max(50),
20
- lastName: z.string().trim().min(1, 'Apellido requerido').max(50),
21
- })
22
-
23
- const phoneSchema = z.object({
24
- phone: z.string().trim().min(6, 'Ingresa un número válido'),
25
- })
26
-
27
- const documentSchema = z.object({
28
- identificationType: z.string().min(1, 'Selecciona el tipo'),
29
- identificationNumber: z.string().trim().min(1, 'Número de documento requerido'),
30
- })
31
-
32
- type EditSection = 'name' | 'phone' | 'document' | null
33
-
34
- function formatDocument(profile: CustomerProfile): string {
35
- if (profile.identificationType && profile.identificationNumber) {
36
- return `${profile.identificationType} · ${profile.identificationNumber}`
37
- }
38
- if (profile.identificationNumber) return profile.identificationNumber
39
- return 'Sin registrar'
40
- }
41
-
42
- export type CustomerProfilePanelProps = {
43
- profile?: CustomerProfile | null
44
- isLoading: boolean
45
- errorMessage?: string | null
46
- onUpdateProfile: (payload: CustomerProfilePatch) => Promise<void>
47
- }
48
-
49
- export function CustomerProfilePanel({
50
- profile,
51
- isLoading,
52
- errorMessage,
53
- onUpdateProfile,
54
- }: CustomerProfilePanelProps) {
55
- const formId = useId()
56
- const dialogRef = useRef<HTMLDialogElement>(null)
57
- const [editSection, setEditSection] = useState<EditSection>(null)
58
- const [saving, setSaving] = useState(false)
59
- const [formError, setFormError] = useState<string | null>(null)
60
-
61
- const [firstName, setFirstName] = useState('')
62
- const [lastName, setLastName] = useState('')
63
- const [phone, setPhone] = useState('')
64
- const [identificationType, setIdentificationType] = useState('DNI')
65
- const [identificationNumber, setIdentificationNumber] = useState('')
66
- const [fieldErrors, setFieldErrors] = useState<Record<string, string>>({})
67
-
68
- const openEdit = (section: Exclude<EditSection, null>) => {
69
- if (!profile) return
70
- setEditSection(section)
71
- setFormError(null)
72
- setFieldErrors({})
73
- if (section === 'name') {
74
- setFirstName(profile.firstName)
75
- setLastName(profile.lastName)
76
- } else if (section === 'phone') {
77
- setPhone(profile.phone || '')
78
- } else {
79
- setIdentificationType(profile.identificationType || 'DNI')
80
- setIdentificationNumber(profile.identificationNumber || '')
81
- }
82
- dialogRef.current?.showModal()
83
- }
84
-
85
- const closeEdit = () => {
86
- dialogRef.current?.close()
87
- setEditSection(null)
88
- setFormError(null)
89
- setFieldErrors({})
90
- }
91
-
92
- const savePatch = async (partial: Partial<CustomerProfilePatch>) => {
93
- if (!profile) return
94
- try {
95
- setSaving(true)
96
- setFormError(null)
97
- await onUpdateProfile({
98
- ...customerProfileToPatch(profile),
99
- ...partial,
100
- })
101
- closeEdit()
102
- } catch (error) {
103
- setFormError(
104
- error instanceof Error
105
- ? error.message
106
- : 'No se pudieron guardar los cambios',
107
- )
108
- } finally {
109
- setSaving(false)
110
- }
111
- }
112
-
113
- const handleSubmit = (event: FormEvent) => {
114
- event.preventDefault()
115
- if (editSection === 'name') {
116
- const parsed = nameSchema.safeParse({ firstName, lastName })
117
- if (!parsed.success) {
118
- const next: Record<string, string> = {}
119
- for (const issue of parsed.error.issues) {
120
- const key = issue.path[0]
121
- if (typeof key === 'string' && !(key in next)) next[key] = issue.message
122
- }
123
- setFieldErrors(next)
124
- return
125
- }
126
- void savePatch(parsed.data)
127
- return
128
- }
129
- if (editSection === 'phone') {
130
- const parsed = phoneSchema.safeParse({ phone })
131
- if (!parsed.success) {
132
- setFieldErrors({ phone: parsed.error.issues[0]?.message ?? 'Inválido' })
133
- return
134
- }
135
- void savePatch(parsed.data)
136
- return
137
- }
138
- if (editSection === 'document') {
139
- const parsed = documentSchema.safeParse({
140
- identificationType,
141
- identificationNumber,
142
- })
143
- if (!parsed.success) {
144
- const next: Record<string, string> = {}
145
- for (const issue of parsed.error.issues) {
146
- const key = issue.path[0]
147
- if (typeof key === 'string' && !(key in next)) next[key] = issue.message
148
- }
149
- setFieldErrors(next)
150
- return
151
- }
152
- void savePatch(parsed.data)
153
- }
154
- }
155
-
156
- const fullName = profile
157
- ? `${profile.firstName} ${profile.lastName}`.trim() || 'Cliente'
158
- : '—'
159
-
160
- const dialogTitle =
161
- editSection === 'name'
162
- ? 'Editar nombre'
163
- : editSection === 'phone'
164
- ? 'Editar celular'
165
- : 'Documento de identidad'
166
-
167
- if (isLoading) {
168
- return (
169
- <div className="flex items-center gap-2 rounded-lg border border-border bg-background px-4 py-8 text-sm text-muted-foreground">
170
- <Loader2 className="size-4 shrink-0 animate-spin" />
171
- Cargando datos personales…
172
- </div>
173
- )
174
- }
175
-
176
- return (
177
- <div className="space-y-6">
178
- <div>
179
- <h2 className="text-lg font-medium text-foreground">Datos personales</h2>
180
- <p className="mt-1 text-sm text-muted-foreground">
181
- Administra tu nombre, documento y contacto para pedidos y facturación.
182
- </p>
183
- </div>
184
-
185
- {errorMessage ? (
186
- <p className="rounded-md border border-destructive/30 bg-destructive/5 px-4 py-3 text-sm text-destructive">
187
- {errorMessage}
188
- </p>
189
- ) : null}
190
-
191
- <div className="overflow-hidden rounded-lg border border-border bg-background">
192
- <ProfileRow
193
- label="Nombre y apellidos"
194
- value={fullName}
195
- onEdit={() => openEdit('name')}
196
- disabled={!profile}
197
- />
198
- <ProfileRow
199
- label="Tipo de documento"
200
- value={profile ? formatDocument(profile) : 'Sin registrar'}
201
- onEdit={() => openEdit('document')}
202
- disabled={!profile}
203
- />
204
- <ProfileRow
205
- label="Celular"
206
- value={profile?.phone?.trim() ? profile.phone : 'Sin registrar'}
207
- onEdit={() => openEdit('phone')}
208
- disabled={!profile}
209
- />
210
- <ProfileRow
211
- label="Correo"
212
- value={profile?.email ?? '—'}
213
- editable={false}
214
- isLast
215
- />
216
- </div>
217
-
218
- <dialog
219
- ref={dialogRef}
220
- className="fixed left-1/2 top-1/2 z-50 m-0 w-[min(100%-2rem,28rem)] -translate-x-1/2 -translate-y-1/2 rounded-lg border border-border bg-background p-0 shadow-lg backdrop:bg-black/40"
221
- onClose={() => {
222
- setEditSection(null)
223
- setFormError(null)
224
- setFieldErrors({})
225
- }}
226
- >
227
- <form className="space-y-4 p-6" onSubmit={handleSubmit}>
228
- <h3 className="text-base font-medium text-foreground">{dialogTitle}</h3>
229
-
230
- {editSection === 'name' ? (
231
- <div className="grid gap-4 sm:grid-cols-2">
232
- <CheckoutTextField
233
- id={`${formId}-firstName`}
234
- label="Nombre"
235
- value={firstName}
236
- error={fieldErrors.firstName}
237
- autoComplete="given-name"
238
- onChange={(v) => {
239
- setFieldErrors((prev) => {
240
- const next = { ...prev }
241
- delete next.firstName
242
- return next
243
- })
244
- setFirstName(v)
245
- }}
246
- />
247
- <CheckoutTextField
248
- id={`${formId}-lastName`}
249
- label="Apellidos"
250
- value={lastName}
251
- error={fieldErrors.lastName}
252
- autoComplete="family-name"
253
- onChange={(v) => {
254
- setFieldErrors((prev) => {
255
- const next = { ...prev }
256
- delete next.lastName
257
- return next
258
- })
259
- setLastName(v)
260
- }}
261
- />
262
- </div>
263
- ) : null}
264
-
265
- {editSection === 'phone' ? (
266
- <CheckoutTextField
267
- id={`${formId}-phone`}
268
- label="Celular"
269
- value={phone}
270
- error={fieldErrors.phone}
271
- placeholder="+51999999999"
272
- autoComplete="tel"
273
- onChange={(v) => {
274
- setFieldErrors((prev) => {
275
- const next = { ...prev }
276
- delete next.phone
277
- return next
278
- })
279
- setPhone(v)
280
- }}
281
- />
282
- ) : null}
283
-
284
- {editSection === 'document' ? (
285
- <div className="grid gap-4">
286
- <CheckoutSelectField
287
- id={`${formId}-identificationType`}
288
- label="Tipo"
289
- value={identificationType}
290
- error={fieldErrors.identificationType}
291
- options={IDENTIFICATION_TYPES.map((t) => ({
292
- value: t,
293
- label: t,
294
- }))}
295
- onChange={(v) => {
296
- setFieldErrors((prev) => {
297
- const next = { ...prev }
298
- delete next.identificationType
299
- return next
300
- })
301
- setIdentificationType(v)
302
- }}
303
- />
304
- <CheckoutTextField
305
- id={`${formId}-identificationNumber`}
306
- label="Número"
307
- value={identificationNumber}
308
- error={fieldErrors.identificationNumber}
309
- placeholder="12345678"
310
- onChange={(v) => {
311
- setFieldErrors((prev) => {
312
- const next = { ...prev }
313
- delete next.identificationNumber
314
- return next
315
- })
316
- setIdentificationNumber(v)
317
- }}
318
- />
319
- </div>
320
- ) : null}
321
-
322
- {formError ? <FieldError message={formError} /> : null}
323
-
324
- <div className="flex flex-wrap justify-end gap-2 pt-2">
325
- <Button type="button" variant="outline" onClick={closeEdit}>
326
- Cancelar
327
- </Button>
328
- <Button type="submit" disabled={saving}>
329
- {saving ? <Loader2 className="size-4 animate-spin" /> : 'Guardar'}
330
- </Button>
331
- </div>
332
- </form>
333
- </dialog>
334
- </div>
335
- )
336
- }
337
-
338
- function ProfileRow({
339
- label,
340
- value,
341
- onEdit,
342
- editable = true,
343
- disabled = false,
344
- isLast = false,
345
- }: {
346
- label: string
347
- value: string
348
- onEdit?: () => void
349
- editable?: boolean
350
- disabled?: boolean
351
- isLast?: boolean
352
- }) {
353
- return (
354
- <div
355
- className={cn(
356
- 'flex items-start justify-between gap-4 px-5 py-5',
357
- !isLast && 'border-b border-border',
358
- )}
359
- >
360
- <div className="min-w-0">
361
- <p className="text-sm font-medium text-foreground">{label}</p>
362
- <p className="mt-1 break-words text-sm text-muted-foreground">{value}</p>
363
- </div>
364
- {editable && onEdit ? (
365
- <Button
366
- type="button"
367
- variant="ghost"
368
- size="sm"
369
- className="shrink-0 gap-1"
370
- onClick={onEdit}
371
- disabled={disabled}
372
- >
373
- <Pencil className="size-3.5" />
374
- Editar
375
- </Button>
376
- ) : null}
377
- </div>
378
- )
379
- }
@@ -1,143 +0,0 @@
1
- import type { StorefrontCart, StorefrontCartItem } from '@ecomiq/core'
2
-
3
- export type CartLineSignatureItem = {
4
- id: string
5
- variantId: string
6
- quantity: number
7
- }
8
-
9
- export type CartReconcileAction =
10
- | { action: 'add'; variantId: string; quantity: number }
11
- | { action: 'update'; itemId: string; quantity: number }
12
- | { action: 'remove'; itemId: string }
13
-
14
- export function cartItemsSignature(items: CartLineSignatureItem[]): string {
15
- return items
16
- .map((i) => `${i.variantId}:${i.quantity}`)
17
- .sort()
18
- .join('|')
19
- }
20
-
21
- export function toSignatureItems(
22
- items: StorefrontCartItem[] | undefined,
23
- ): CartLineSignatureItem[] {
24
- return (items ?? []).map((item) => ({
25
- id: item.id,
26
- variantId: String(item.variantId),
27
- quantity: item.quantity,
28
- }))
29
- }
30
-
31
- export function areCartSignaturesEqual(
32
- a: CartLineSignatureItem[],
33
- b: CartLineSignatureItem[],
34
- ): boolean {
35
- return cartItemsSignature(a) === cartItemsSignature(b)
36
- }
37
-
38
- export function cartSessionSignature(
39
- cartTokenId: string | null | undefined,
40
- items: CartLineSignatureItem[],
41
- ): string {
42
- return `${cartTokenId ?? ''}|${cartItemsSignature(items)}`
43
- }
44
-
45
- function groupQuantitiesByVariant(items: CartLineSignatureItem[]): Map<string, number> {
46
- const map = new Map<string, number>()
47
- for (const item of items) {
48
- map.set(item.variantId, (map.get(item.variantId) ?? 0) + Math.max(0, item.quantity))
49
- }
50
- return map
51
- }
52
-
53
- function groupLinesByVariant(
54
- items: CartLineSignatureItem[],
55
- ): Map<string, CartLineSignatureItem[]> {
56
- const map = new Map<string, CartLineSignatureItem[]>()
57
- for (const item of items) {
58
- const lines = map.get(item.variantId) ?? []
59
- lines.push(item)
60
- map.set(item.variantId, lines)
61
- }
62
- return map
63
- }
64
-
65
- /** Acciones para alinear el API con el carrito local (fuente de verdad). */
66
- export function findCartReconciliationActions(
67
- local: CartLineSignatureItem[],
68
- api: CartLineSignatureItem[],
69
- ): CartReconcileAction[] {
70
- const actions: CartReconcileAction[] = []
71
- const localQty = groupQuantitiesByVariant(local)
72
- const apiLinesByVariant = groupLinesByVariant(api)
73
- const variantIds = new Set([...localQty.keys(), ...apiLinesByVariant.keys()])
74
-
75
- for (const variantId of variantIds) {
76
- const targetQty = localQty.get(variantId) ?? 0
77
- const apiLines = apiLinesByVariant.get(variantId) ?? []
78
- const apiQty = apiLines.reduce((sum, line) => sum + line.quantity, 0)
79
-
80
- if (targetQty > apiQty) {
81
- actions.push({
82
- action: 'add',
83
- variantId,
84
- quantity: targetQty - apiQty,
85
- })
86
- continue
87
- }
88
-
89
- if (targetQty >= apiQty) continue
90
-
91
- let excess = apiQty - targetQty
92
- for (const line of [...apiLines].reverse()) {
93
- if (excess <= 0) break
94
- if (line.quantity <= excess) {
95
- actions.push({ action: 'remove', itemId: line.id })
96
- excess -= line.quantity
97
- } else {
98
- actions.push({
99
- action: 'update',
100
- itemId: line.id,
101
- quantity: line.quantity - excess,
102
- })
103
- excess = 0
104
- }
105
- }
106
- }
107
-
108
- return actions
109
- }
110
-
111
- export function isCheckoutCartAlreadyPurchased(
112
- cart: StorefrontCart | undefined,
113
- ): boolean {
114
- if (!cart) return false
115
- if (cart.completedAt) return true
116
- return cart.status === 'completed'
117
- }
118
-
119
- const PROVIDER_CHECKOUT_UI = new Set(['stripe', 'mercadopago', 'mercado_pago'])
120
-
121
- function normalizeProviderKey(raw: string): string {
122
- return raw.trim().toLowerCase().replace(/[\s-]+/g, '_')
123
- }
124
-
125
- export function methodUsesProviderCheckoutUi(method: {
126
- isManual: boolean
127
- kind: string
128
- code: string
129
- providerMethodCode?: string | null
130
- }): boolean {
131
- if (method.isManual) return false
132
- const keys = [method.providerMethodCode, method.kind, method.code].filter(
133
- Boolean,
134
- ) as string[]
135
- return keys.some((key) => {
136
- const normalized = normalizeProviderKey(key)
137
- return (
138
- PROVIDER_CHECKOUT_UI.has(normalized) ||
139
- normalized.includes('stripe') ||
140
- normalized.includes('mercado')
141
- )
142
- })
143
- }
@@ -1,76 +0,0 @@
1
- import { Link } from '../../link'
2
- import { Button, buttonVariants, cn } from '../../ui'
3
- import type { CheckoutFieldId } from './form-schema'
4
- import { CheckoutTextField } from './checkout-form-controls'
5
-
6
- function checkoutAuthRedirect(): string {
7
- if (typeof window === 'undefined') return '/checkout'
8
- return `${window.location.pathname}${window.location.search}`
9
- }
10
-
11
- /** Paso 1: Mis datos (email). */
12
- export function CheckoutContactSection({
13
- email,
14
- fieldError,
15
- sessionLabel,
16
- isSaving,
17
- onEmailChange,
18
- onClearError,
19
- onContinueAsGuest,
20
- }: {
21
- email: string
22
- fieldError?: string
23
- sessionLabel?: string | null
24
- isSaving?: boolean
25
- onEmailChange: (value: string) => void
26
- onClearError: (id: CheckoutFieldId) => void
27
- onContinueAsGuest: () => void | Promise<void>
28
- }) {
29
- const authRedirect = checkoutAuthRedirect()
30
-
31
- return (
32
- <div className="space-y-5">
33
- {sessionLabel ? (
34
- <p className="text-sm text-muted-foreground">
35
- Sesión como <span className="font-medium text-foreground">{sessionLabel}</span>
36
- </p>
37
- ) : null}
38
-
39
- <CheckoutTextField
40
- id="email"
41
- label="Correo electrónico"
42
- error={fieldError}
43
- value={email}
44
- onChange={(v) => {
45
- onClearError('email')
46
- onEmailChange(v)
47
- }}
48
- type="email"
49
- autoComplete="email"
50
- />
51
-
52
- <div className="flex flex-col gap-3 sm:flex-row sm:flex-wrap sm:items-center">
53
- {!sessionLabel ? (
54
- <>
55
- <Link
56
- to="/login"
57
- search={{ redirect: authRedirect }}
58
- className={cn(buttonVariants({ variant: 'default' }), 'uppercase tracking-wide')}
59
- >
60
- ¿Ya tienes una cuenta?
61
- </Link>
62
- </>
63
- ) : null}
64
- <Button
65
- type="button"
66
- variant={sessionLabel ? 'default' : 'outline'}
67
- className="uppercase tracking-wide"
68
- disabled={isSaving}
69
- onClick={() => void onContinueAsGuest()}
70
- >
71
- {sessionLabel ? 'Continuar' : 'Continuar como invitado'}
72
- </Button>
73
- </div>
74
- </div>
75
- )
76
- }
@@ -1,15 +0,0 @@
1
- import { Button } from '../../ui'
2
-
3
- export function CheckoutExternalUpdateBanner({ onReload }: { onReload: () => void }) {
4
- return (
5
- <div
6
- className="mb-6 flex flex-wrap items-center justify-between gap-3 rounded-lg border border-amber-300/80 bg-amber-50 px-4 py-3 text-sm text-amber-950"
7
- role="alert"
8
- >
9
- <p>Tu carrito se acaba de actualizar</p>
10
- <Button type="button" size="sm" onClick={onReload}>
11
- Recargar checkout
12
- </Button>
13
- </div>
14
- )
15
- }