@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,678 +0,0 @@
1
- import { useEffect, useId, useRef, useState, type FormEvent } from 'react'
2
- import { z } from 'zod'
3
- import type {
4
- CustomerAddressUpsertInput,
5
- CustomerSavedAddress,
6
- } from '@ecomiq/core/models/customer-address'
7
- import { Loader2, MapPin, Pencil, Plus, Star, Trash2 } from 'lucide-react'
8
- import { Button, cn } from '../../ui'
9
- import {
10
- CheckoutSelectField,
11
- CheckoutTextField,
12
- FieldError,
13
- fieldClass,
14
- } from '../checkout/checkout-form-controls'
15
- import { useCheckoutGeo } from '../checkout/use-checkout-geo'
16
-
17
- const DEFAULT_COUNTRY_CODE = 'PE'
18
-
19
- const addressFormSchema = z.object({
20
- addressKey: z.string().trim().min(1, 'Nombre de la dirección requerido').max(48),
21
- addressLine: z.string().trim().min(1, 'Calle o avenida requerida'),
22
- addressNumber: z.string().trim().min(1, 'Número requerido'),
23
- postalCode: z.string().trim().min(1, 'Código postal requerido'),
24
- reference: z.string().trim(),
25
- phone: z.string().trim(),
26
- cellPhone: z.string().trim(),
27
- departmentCode: z.string().min(1, 'Selecciona departamento'),
28
- departmentName: z.string().min(1, 'Selecciona departamento'),
29
- provinceCode: z.string().min(1, 'Selecciona provincia'),
30
- provinceName: z.string().min(1, 'Selecciona provincia'),
31
- districtCode: z.string().min(1, 'Selecciona distrito'),
32
- districtName: z.string().min(1, 'Selecciona distrito'),
33
- isDefault: z.boolean(),
34
- })
35
-
36
- type AddressFormValues = z.infer<typeof addressFormSchema>
37
- type AddressFormErrors = Partial<Record<keyof AddressFormValues, string>>
38
-
39
- const emptyDefaults: AddressFormValues = {
40
- addressKey: 'Casa',
41
- addressLine: '',
42
- addressNumber: '',
43
- postalCode: '',
44
- reference: '',
45
- phone: '',
46
- cellPhone: '',
47
- departmentCode: '',
48
- departmentName: '',
49
- provinceCode: '',
50
- provinceName: '',
51
- districtCode: '',
52
- districtName: '',
53
- isDefault: false,
54
- }
55
-
56
- function toUpsertPayload(values: AddressFormValues): CustomerAddressUpsertInput {
57
- return {
58
- addressKey: values.addressKey.trim(),
59
- countryIsoCode: DEFAULT_COUNTRY_CODE,
60
- addressLine: values.addressLine.trim(),
61
- addressNumber: values.addressNumber.trim(),
62
- department: values.departmentName.trim(),
63
- province: values.provinceName.trim(),
64
- district: values.districtName.trim(),
65
- phone: values.phone.trim(),
66
- cellPhone: values.cellPhone.trim() || values.phone.trim(),
67
- postalCode: values.postalCode.trim(),
68
- reference: values.reference.trim(),
69
- latitude: null,
70
- longitude: null,
71
- isDefault: values.isDefault,
72
- }
73
- }
74
-
75
- function findCodeByName(
76
- items: { code: string; name: string }[],
77
- name: string,
78
- ): string {
79
- const target = name.trim().toLowerCase()
80
- if (!target) return ''
81
- return (
82
- items.find((item) => item.name.trim().toLowerCase() === target)?.code ?? ''
83
- )
84
- }
85
-
86
- export type CustomerAddressesPanelProps = {
87
- addresses?: CustomerSavedAddress[]
88
- isLoading: boolean
89
- errorMessage?: string | null
90
- onCreateAddress: (payload: CustomerAddressUpsertInput) => Promise<void>
91
- onUpdateAddress: (
92
- addressId: string,
93
- payload: CustomerAddressUpsertInput,
94
- ) => Promise<void>
95
- onDeleteAddress: (addressId: string) => Promise<void>
96
- }
97
-
98
- export function CustomerAddressesPanel({
99
- addresses,
100
- isLoading,
101
- errorMessage,
102
- onCreateAddress,
103
- onUpdateAddress,
104
- onDeleteAddress,
105
- }: CustomerAddressesPanelProps) {
106
- const formId = useId()
107
- const dialogRef = useRef<HTMLDialogElement>(null)
108
- const deleteDialogRef = useRef<HTMLDialogElement>(null)
109
-
110
- const [editing, setEditing] = useState<CustomerSavedAddress | null>(null)
111
- const [form, setForm] = useState<AddressFormValues>(emptyDefaults)
112
- const [fieldErrors, setFieldErrors] = useState<AddressFormErrors>({})
113
- const [formError, setFormError] = useState<string | null>(null)
114
- const [saving, setSaving] = useState(false)
115
- const [deleteTarget, setDeleteTarget] = useState<CustomerSavedAddress | null>(
116
- null,
117
- )
118
- const [deleting, setDeleting] = useState(false)
119
- const [actionError, setActionError] = useState<string | null>(null)
120
-
121
- const geo = useCheckoutGeo(
122
- DEFAULT_COUNTRY_CODE,
123
- form.departmentCode,
124
- form.provinceCode,
125
- )
126
-
127
- const patchForm = (patch: Partial<AddressFormValues>) => {
128
- setForm((prev) => ({ ...prev, ...patch }))
129
- }
130
-
131
- const clearFieldError = (key: keyof AddressFormValues) => {
132
- setFieldErrors((prev) => {
133
- if (!prev[key]) return prev
134
- const next = { ...prev }
135
- delete next[key]
136
- return next
137
- })
138
- }
139
-
140
- const openCreate = () => {
141
- setEditing(null)
142
- setForm(emptyDefaults)
143
- setFieldErrors({})
144
- setFormError(null)
145
- dialogRef.current?.showModal()
146
- }
147
-
148
- const openEdit = (addr: CustomerSavedAddress) => {
149
- setEditing(addr)
150
- setForm({
151
- addressKey: addr.addressKey,
152
- addressLine: addr.addressLine,
153
- addressNumber: addr.addressNumber,
154
- postalCode: addr.postalCode,
155
- reference: addr.reference ?? '',
156
- phone: addr.phone ?? '',
157
- cellPhone: addr.cellPhone ?? '',
158
- departmentCode: '',
159
- departmentName: addr.department,
160
- provinceCode: '',
161
- provinceName: addr.province,
162
- districtCode: '',
163
- districtName: addr.district,
164
- isDefault: addr.isDefault,
165
- })
166
- setFieldErrors({})
167
- setFormError(null)
168
- dialogRef.current?.showModal()
169
- }
170
-
171
- const closeDialog = () => {
172
- dialogRef.current?.close()
173
- setEditing(null)
174
- setForm(emptyDefaults)
175
- setFieldErrors({})
176
- setFormError(null)
177
- }
178
-
179
- useEffect(() => {
180
- if (!editing || !form.departmentName || form.departmentCode) return
181
- const code = findCodeByName(geo.departments, form.departmentName)
182
- if (code) patchForm({ departmentCode: code })
183
- }, [editing, form.departmentName, form.departmentCode, geo.departments])
184
-
185
- useEffect(() => {
186
- if (!editing || !form.provinceName || !form.departmentCode || form.provinceCode)
187
- return
188
- const code = findCodeByName(geo.provinces, form.provinceName)
189
- if (code) patchForm({ provinceCode: code })
190
- }, [
191
- editing,
192
- form.provinceName,
193
- form.departmentCode,
194
- form.provinceCode,
195
- geo.provinces,
196
- ])
197
-
198
- useEffect(() => {
199
- if (!editing || !form.districtName || !form.provinceCode || form.districtCode)
200
- return
201
- const code = findCodeByName(geo.districts, form.districtName)
202
- if (code) patchForm({ districtCode: code })
203
- }, [
204
- editing,
205
- form.districtName,
206
- form.provinceCode,
207
- form.districtCode,
208
- geo.districts,
209
- ])
210
-
211
- const handleSubmit = async (event: FormEvent) => {
212
- event.preventDefault()
213
- const parsed = addressFormSchema.safeParse(form)
214
- if (!parsed.success) {
215
- const next: AddressFormErrors = {}
216
- for (const issue of parsed.error.issues) {
217
- const key = issue.path[0]
218
- if (typeof key === 'string' && !(key in next)) {
219
- next[key as keyof AddressFormValues] = issue.message
220
- }
221
- }
222
- setFieldErrors(next)
223
- return
224
- }
225
-
226
- try {
227
- setSaving(true)
228
- setFormError(null)
229
- const payload = toUpsertPayload(parsed.data)
230
- if (editing?.id) {
231
- await onUpdateAddress(editing.id, payload)
232
- } else {
233
- await onCreateAddress(payload)
234
- }
235
- closeDialog()
236
- } catch (error) {
237
- setFormError(
238
- error instanceof Error ? error.message : 'No se pudo guardar la dirección',
239
- )
240
- } finally {
241
- setSaving(false)
242
- }
243
- }
244
-
245
- const handleSetDefault = async (addr: CustomerSavedAddress) => {
246
- if (!addr.id || addr.isDefault) return
247
- const payload: CustomerAddressUpsertInput = {
248
- addressKey: addr.addressKey,
249
- countryIsoCode: addr.countryIsoCode,
250
- addressLine: addr.addressLine,
251
- addressNumber: addr.addressNumber,
252
- department: addr.department,
253
- province: addr.province,
254
- district: addr.district,
255
- phone: addr.phone,
256
- cellPhone: addr.cellPhone || addr.phone,
257
- postalCode: addr.postalCode,
258
- reference: addr.reference,
259
- latitude: addr.latitude,
260
- longitude: addr.longitude,
261
- isDefault: true,
262
- }
263
- try {
264
- setActionError(null)
265
- await onUpdateAddress(addr.id, payload)
266
- } catch (error) {
267
- setActionError(
268
- error instanceof Error ? error.message : 'No se pudo actualizar',
269
- )
270
- }
271
- }
272
-
273
- const openDelete = (addr: CustomerSavedAddress) => {
274
- setDeleteTarget(addr)
275
- setActionError(null)
276
- deleteDialogRef.current?.showModal()
277
- }
278
-
279
- const closeDelete = () => {
280
- deleteDialogRef.current?.close()
281
- setDeleteTarget(null)
282
- }
283
-
284
- const confirmDelete = async () => {
285
- if (!deleteTarget?.id) return
286
- try {
287
- setDeleting(true)
288
- setActionError(null)
289
- await onDeleteAddress(deleteTarget.id)
290
- closeDelete()
291
- } catch (error) {
292
- setActionError(
293
- error instanceof Error ? error.message : 'No se pudo eliminar',
294
- )
295
- } finally {
296
- setDeleting(false)
297
- }
298
- }
299
-
300
- const list = addresses ?? []
301
-
302
- return (
303
- <div className="space-y-6">
304
- <div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
305
- <div>
306
- <h2 className="text-lg font-medium text-foreground">Tus direcciones</h2>
307
- <p className="mt-1 text-sm text-muted-foreground">
308
- Envío a domicilio y datos de contacto para tus pedidos.
309
- </p>
310
- </div>
311
- <Button type="button" onClick={openCreate} className="shrink-0 gap-2">
312
- <Plus className="size-4" />
313
- Añadir dirección
314
- </Button>
315
- </div>
316
-
317
- {actionError ? (
318
- <p className="rounded-md border border-destructive/30 bg-destructive/5 px-4 py-3 text-sm text-destructive">
319
- {actionError}
320
- </p>
321
- ) : null}
322
-
323
- {isLoading ? (
324
- <div className="flex items-center gap-2 rounded-lg border border-border bg-background px-4 py-8 text-sm text-muted-foreground">
325
- <Loader2 className="size-4 animate-spin" />
326
- Cargando direcciones…
327
- </div>
328
- ) : null}
329
-
330
- {errorMessage && !isLoading ? (
331
- <p className="rounded-md border border-destructive/30 bg-destructive/5 px-4 py-3 text-sm text-destructive">
332
- {errorMessage}
333
- </p>
334
- ) : null}
335
-
336
- {!isLoading && !errorMessage && list.length === 0 ? (
337
- <div className="rounded-lg border border-dashed border-border bg-muted/30 px-6 py-10 text-center text-sm text-muted-foreground">
338
- <MapPin className="mx-auto mb-3 size-10 text-muted-foreground/70" />
339
- Todavía no tienes direcciones guardadas.
340
- </div>
341
- ) : null}
342
-
343
- {!isLoading && list.length > 0 ? (
344
- <ul className="grid gap-4 lg:grid-cols-2">
345
- {list.map((addr) => (
346
- <li
347
- key={addr.id || `${addr.addressKey}-${addr.postalCode}`}
348
- className={cn(
349
- 'relative overflow-hidden rounded-lg border border-border bg-background p-5',
350
- addr.isDefault && 'ring-1 ring-primary/40',
351
- )}
352
- >
353
- {addr.isDefault ? (
354
- <span className="absolute right-0 top-0 rounded-bl-md bg-primary/10 px-3 py-1 text-xs font-medium text-primary">
355
- Predeterminada
356
- </span>
357
- ) : null}
358
- <div className="flex items-start gap-3 pr-24">
359
- <span className="mt-0.5 flex size-10 shrink-0 items-center justify-center rounded-full bg-muted text-muted-foreground">
360
- <MapPin className="size-5" />
361
- </span>
362
- <div className="min-w-0 flex-1">
363
- <p className="font-medium text-foreground">{addr.addressKey}</p>
364
- <p className="mt-1 text-sm leading-relaxed text-muted-foreground">
365
- {[addr.addressLine, addr.addressNumber].filter(Boolean).join(' ')}
366
- <br />
367
- {addr.district}, {addr.province}, {addr.department}
368
- </p>
369
- <p className="mt-1 text-xs text-muted-foreground">
370
- {addr.countryIsoCode} · CP {addr.postalCode}
371
- </p>
372
- {addr.reference ? (
373
- <p className="mt-1 text-xs text-muted-foreground">
374
- Ref: {addr.reference}
375
- </p>
376
- ) : null}
377
- {(addr.phone || addr.cellPhone) && (
378
- <p className="mt-2 text-xs text-muted-foreground">
379
- {addr.phone}
380
- {addr.cellPhone && addr.cellPhone !== addr.phone
381
- ? ` · ${addr.cellPhone}`
382
- : ''}
383
- </p>
384
- )}
385
- </div>
386
- </div>
387
- <div className="mt-4 flex flex-wrap gap-2 border-t border-border pt-4">
388
- <Button
389
- type="button"
390
- variant="outline"
391
- size="sm"
392
- className="gap-1"
393
- onClick={() => openEdit(addr)}
394
- disabled={!addr.id}
395
- >
396
- <Pencil className="size-3.5" />
397
- Editar
398
- </Button>
399
- <Button
400
- type="button"
401
- variant="outline"
402
- size="sm"
403
- className="gap-1"
404
- disabled={!addr.id || addr.isDefault}
405
- onClick={() => void handleSetDefault(addr)}
406
- >
407
- <Star className="size-3.5" />
408
- Predeterminada
409
- </Button>
410
- <Button
411
- type="button"
412
- variant="ghost"
413
- size="sm"
414
- className="ml-auto gap-1 text-destructive hover:bg-destructive/10 hover:text-destructive"
415
- disabled={!addr.id}
416
- onClick={() => openDelete(addr)}
417
- >
418
- <Trash2 className="size-3.5" />
419
- Eliminar
420
- </Button>
421
- </div>
422
- </li>
423
- ))}
424
- </ul>
425
- ) : null}
426
-
427
- <dialog
428
- ref={dialogRef}
429
- className="fixed left-1/2 top-1/2 z-50 m-0 max-h-[90vh] w-[min(100%-2rem,36rem)] -translate-x-1/2 -translate-y-1/2 overflow-y-auto rounded-lg border border-border bg-background p-0 shadow-lg open:flex open:flex-col backdrop:bg-black/40"
430
- onClose={() => {
431
- setEditing(null)
432
- setForm(emptyDefaults)
433
- setFieldErrors({})
434
- setFormError(null)
435
- }}
436
- >
437
- <form className="space-y-4 p-6" onSubmit={(e) => void handleSubmit(e)}>
438
- <h3 className="text-base font-medium text-foreground">
439
- {editing ? 'Editar dirección' : 'Nueva dirección'}
440
- </h3>
441
-
442
- <div className="grid gap-4 sm:grid-cols-2">
443
- <CheckoutTextField
444
- id={`${formId}-addressKey`}
445
- label="Identificador"
446
- value={form.addressKey}
447
- error={fieldErrors.addressKey}
448
- placeholder="Casa, Oficina…"
449
- onChange={(v) => {
450
- clearFieldError('addressKey')
451
- patchForm({ addressKey: v })
452
- }}
453
- />
454
- <CheckoutTextField
455
- id={`${formId}-postalCode`}
456
- label="Código postal"
457
- value={form.postalCode}
458
- error={fieldErrors.postalCode}
459
- placeholder="15074"
460
- onChange={(v) => {
461
- clearFieldError('postalCode')
462
- patchForm({ postalCode: v })
463
- }}
464
- />
465
- </div>
466
-
467
- <CheckoutTextField
468
- id={`${formId}-addressLine`}
469
- label="Dirección (calle / av.)"
470
- value={form.addressLine}
471
- error={fieldErrors.addressLine}
472
- placeholder="Av. Principal"
473
- onChange={(v) => {
474
- clearFieldError('addressLine')
475
- patchForm({ addressLine: v })
476
- }}
477
- />
478
-
479
- <CheckoutTextField
480
- id={`${formId}-addressNumber`}
481
- label="Número / interior"
482
- value={form.addressNumber}
483
- error={fieldErrors.addressNumber}
484
- placeholder="123, Dpto. 4"
485
- onChange={(v) => {
486
- clearFieldError('addressNumber')
487
- patchForm({ addressNumber: v })
488
- }}
489
- />
490
-
491
- <div>
492
- <label
493
- htmlFor={`${formId}-country`}
494
- className="text-sm font-medium text-foreground"
495
- >
496
- País
497
- </label>
498
- <select
499
- id={`${formId}-country`}
500
- className={fieldClass()}
501
- value={DEFAULT_COUNTRY_CODE}
502
- disabled
503
- >
504
- <option value={DEFAULT_COUNTRY_CODE}>Perú</option>
505
- </select>
506
- </div>
507
-
508
- <div className="grid gap-4 sm:grid-cols-3">
509
- <CheckoutSelectField
510
- id={`${formId}-department`}
511
- label="Departamento"
512
- value={form.departmentCode}
513
- error={fieldErrors.departmentCode || fieldErrors.departmentName}
514
- options={geo.departments.map((d) => ({
515
- value: d.code,
516
- label: d.name,
517
- }))}
518
- onChange={(code) => {
519
- clearFieldError('departmentCode')
520
- clearFieldError('departmentName')
521
- const name =
522
- geo.departments.find((d) => d.code === code)?.name ?? ''
523
- patchForm({
524
- departmentCode: code,
525
- departmentName: name,
526
- provinceCode: '',
527
- provinceName: '',
528
- districtCode: '',
529
- districtName: '',
530
- })
531
- }}
532
- />
533
- <CheckoutSelectField
534
- id={`${formId}-province`}
535
- label="Provincia"
536
- value={form.provinceCode}
537
- error={fieldErrors.provinceCode || fieldErrors.provinceName}
538
- options={geo.provinces.map((p) => ({
539
- value: p.code,
540
- label: p.name,
541
- }))}
542
- onChange={(code) => {
543
- clearFieldError('provinceCode')
544
- clearFieldError('provinceName')
545
- const name =
546
- geo.provinces.find((p) => p.code === code)?.name ?? ''
547
- patchForm({
548
- provinceCode: code,
549
- provinceName: name,
550
- districtCode: '',
551
- districtName: '',
552
- })
553
- }}
554
- />
555
- <CheckoutSelectField
556
- id={`${formId}-district`}
557
- label="Distrito"
558
- value={form.districtCode}
559
- error={fieldErrors.districtCode || fieldErrors.districtName}
560
- options={geo.districts.map((d) => ({
561
- value: d.code,
562
- label: d.name,
563
- }))}
564
- onChange={(code) => {
565
- clearFieldError('districtCode')
566
- clearFieldError('districtName')
567
- const name =
568
- geo.districts.find((d) => d.code === code)?.name ?? ''
569
- patchForm({
570
- districtCode: code,
571
- districtName: name,
572
- })
573
- }}
574
- />
575
- </div>
576
-
577
- <div>
578
- <label
579
- htmlFor={`${formId}-reference`}
580
- className="text-sm font-medium text-foreground"
581
- >
582
- Referencia (opcional)
583
- </label>
584
- <textarea
585
- id={`${formId}-reference`}
586
- rows={2}
587
- className={cn(fieldClass(), 'h-auto py-2')}
588
- value={form.reference}
589
- placeholder="Puerta azul, portón negro…"
590
- onChange={(e) => patchForm({ reference: e.target.value })}
591
- />
592
- </div>
593
-
594
- <div className="grid gap-4 sm:grid-cols-2">
595
- <CheckoutTextField
596
- id={`${formId}-phone`}
597
- label="Teléfono"
598
- value={form.phone}
599
- error={fieldErrors.phone}
600
- placeholder="+51999999999"
601
- onChange={(v) => {
602
- clearFieldError('phone')
603
- patchForm({ phone: v })
604
- }}
605
- />
606
- <CheckoutTextField
607
- id={`${formId}-cellPhone`}
608
- label="Celular (opcional)"
609
- value={form.cellPhone}
610
- error={fieldErrors.cellPhone}
611
- placeholder="+51999999999"
612
- onChange={(v) => {
613
- clearFieldError('cellPhone')
614
- patchForm({ cellPhone: v })
615
- }}
616
- />
617
- </div>
618
-
619
- <label className="flex items-center gap-2 text-sm text-foreground">
620
- <input
621
- type="checkbox"
622
- className="size-4 rounded border-border"
623
- checked={form.isDefault}
624
- onChange={(e) => patchForm({ isDefault: e.target.checked })}
625
- />
626
- Usar como dirección predeterminada
627
- </label>
628
-
629
- {formError ? <FieldError message={formError} /> : null}
630
-
631
- <div className="flex flex-wrap justify-end gap-2 pt-2">
632
- <Button type="button" variant="outline" onClick={closeDialog}>
633
- Cancelar
634
- </Button>
635
- <Button type="submit" disabled={saving}>
636
- {saving ? <Loader2 className="size-4 animate-spin" /> : 'Guardar'}
637
- </Button>
638
- </div>
639
- </form>
640
- </dialog>
641
-
642
- <dialog
643
- ref={deleteDialogRef}
644
- className="fixed left-1/2 top-1/2 z-50 m-0 w-[min(100%-2rem,24rem)] -translate-x-1/2 -translate-y-1/2 rounded-lg border border-border bg-background p-6 shadow-lg backdrop:bg-black/40"
645
- onClose={() => setDeleteTarget(null)}
646
- >
647
- <h3 className="text-base font-medium text-foreground">
648
- ¿Eliminar esta dirección?
649
- </h3>
650
- <p className="mt-2 text-sm text-muted-foreground">
651
- Esta acción no se puede deshacer. La dirección dejará de aparecer en tu
652
- cuenta.
653
- </p>
654
- {actionError ? (
655
- <p className="mt-3 text-sm text-destructive">{actionError}</p>
656
- ) : null}
657
- <div className="mt-6 flex flex-wrap justify-end gap-2">
658
- <Button
659
- type="button"
660
- variant="outline"
661
- disabled={deleting}
662
- onClick={closeDelete}
663
- >
664
- Cancelar
665
- </Button>
666
- <Button
667
- type="button"
668
- variant="destructive"
669
- disabled={deleting}
670
- onClick={() => void confirmDelete()}
671
- >
672
- {deleting ? <Loader2 className="size-4 animate-spin" /> : 'Eliminar'}
673
- </Button>
674
- </div>
675
- </dialog>
676
- </div>
677
- )
678
- }