@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.
- package/LICENSE +21 -0
- package/README.md +150 -103
- package/dist/account-Dx4e3syi.js +258 -0
- package/dist/account-forms-AVk71ko7.js +39 -0
- package/dist/account-forms.d.ts +13 -0
- package/dist/account.d.ts +240 -0
- package/dist/address-defaults-BqjRhS82.js +14 -0
- package/dist/address-defaults.d.ts +11 -0
- package/dist/analytics/analytics-CG1Qagwl.js +148 -0
- package/dist/analytics/analytics.d.ts +1 -0
- package/dist/analytics/config-DJr-GRhs.js +62 -0
- package/dist/analytics/config.d.ts +22 -0
- package/dist/analytics/consent-B13rBpBs.js +57 -0
- package/dist/analytics/consent-banner-9QvRRy8v.js +239 -0
- package/dist/analytics/consent-banner.d.ts +20 -0
- package/dist/analytics/cookies-pLozllyB.js +100 -0
- package/dist/analytics/cookies.d.ts +6 -0
- package/dist/analytics/destinations-iECrlLIr.js +144 -0
- package/dist/analytics/events-Dt81tIp1.js +102 -0
- package/dist/analytics/events.d.ts +51 -0
- package/dist/analytics/tracker-p4yaoOyk.js +51 -0
- package/dist/api-cache-DsuIn5hX.js +159 -0
- package/dist/bank-accounts-DlEczQQ5.js +81 -0
- package/dist/bank-accounts.d.ts +4 -0
- package/dist/captcha-DAqaIx8m.js +117 -0
- package/dist/captcha.d.ts +34 -0
- package/dist/cart-D7DxZ2lD.js +30 -0
- package/dist/cart-actions-Bsub_FFG.js +116 -0
- package/dist/cart-actions.d.ts +15 -0
- package/dist/cart-drawer-B7PPsR69.js +22 -0
- package/dist/cart-drawer.d.ts +9 -0
- package/dist/cart-form-y9P1r_EX.js +81 -0
- package/dist/cart-form.d.ts +24 -0
- package/dist/cart-summary-ur2VWKr-.js +89 -0
- package/dist/cart-summary.d.ts +31 -0
- package/dist/cart.d.ts +4 -0
- package/dist/catalog-7gJ-l5hW.js +67 -0
- package/dist/catalog-hooks-B7ZP4wYY.js +312 -0
- package/dist/catalog-hooks.d.ts +111 -0
- package/dist/catalog.d.ts +5 -0
- package/dist/checkout-DVWSXANi.js +474 -0
- package/dist/checkout-form-CbOIdENC.js +131 -0
- package/dist/checkout-form.d.ts +20 -0
- package/dist/checkout.d.ts +86 -0
- package/dist/complaint-book-BvwLvfx0.js +117 -0
- package/dist/complaint-book.d.ts +62 -0
- package/dist/culqi-authentication-BGr20vAJ.js +204 -0
- package/dist/culqi-authentication.d.ts +15 -0
- package/dist/culqi-checkout-CTXpKgp_.js +55 -0
- package/dist/culqi-three-ds-HP3mfrvV.js +33 -0
- package/dist/culqi-three-ds.d.ts +29 -0
- package/dist/customer-avatar-CJXSDroK.js +12 -0
- package/dist/customer-avatar.d.ts +5 -0
- package/dist/customer-cookies-DHOgFAMV.js +45 -0
- package/dist/customer-csrf-C1oDX-Py.js +17 -0
- package/dist/customer-csrf.d.ts +3 -0
- package/dist/customer-document-fields-C1K-_DAD.js +70 -0
- package/dist/customer-document-fields.d.ts +9 -0
- package/dist/customer-login-form-C7AC6vXd.js +144 -0
- package/dist/customer-login-form.d.ts +17 -0
- package/dist/customer-registration-form-BEm_NEfP.js +93 -0
- package/dist/customer-registration-form.d.ts +8 -0
- package/dist/document-url-CGsAl5Tf.js +9 -0
- package/dist/documents-T1mzc_j8.js +38 -0
- package/dist/documents.d.ts +16 -0
- package/dist/entries/account.d.ts +11 -0
- package/dist/entries/account.js +10 -0
- package/dist/entries/analytics.d.ts +6 -0
- package/dist/entries/analytics.js +5 -0
- package/dist/entries/cart.d.ts +8 -0
- package/dist/entries/cart.js +7 -0
- package/dist/entries/checkout.d.ts +12 -0
- package/dist/entries/checkout.js +11 -0
- package/dist/entries/order.d.ts +11 -0
- package/dist/entries/order.js +11 -0
- package/dist/favicon-YpyhVJ9H.js +28 -0
- package/dist/favicon.d.ts +4 -0
- package/dist/form-BtQcbXlJ.js +18 -0
- package/dist/form-contracts-BNpHaTGJ.js +166 -0
- package/dist/form-contracts.d.ts +42 -0
- package/dist/form.d.ts +3 -0
- package/dist/hidden-CCWtOWKR.js +3 -0
- package/dist/hidden.d.ts +4 -0
- package/dist/index.d.ts +50 -0
- package/dist/index.js +50 -0
- package/dist/invoice-fields-BG0HjL6T.js +103 -0
- package/dist/invoice-fields.d.ts +39 -0
- package/dist/links-kFxBZfdb.js +96 -0
- package/dist/links.d.ts +21 -0
- package/dist/mercadopago-checkout-DC7RXa8k.js +27 -0
- package/dist/mercadopago-dialog-UTQev0MG.js +180 -0
- package/dist/mercadopago-dialog.d.ts +21 -0
- package/dist/money-DtGOtDri.js +32 -0
- package/dist/money.d.ts +1 -0
- package/dist/optimistic-cart-BveVOHuh.js +53 -0
- package/dist/optimistic-cart.d.ts +16 -0
- package/dist/order-display-DTt5WBbc.js +112 -0
- package/dist/order-display.d.ts +20 -0
- package/dist/order-draft-DxV1Uhm3.js +38 -0
- package/dist/order-draft.d.ts +16 -0
- package/dist/order-pages-Dm6l5m0m.js +38 -0
- package/dist/order-pages.d.ts +12 -0
- package/dist/order-payment-continuation-D_d5PGA4.js +212 -0
- package/dist/order-payment-continuation.d.ts +10 -0
- package/dist/order-payment-in-review-x4gdLFHZ.js +65 -0
- package/dist/order-payment-in-review.d.ts +5 -0
- package/dist/order-status-BJXLJwxq.js +162 -0
- package/dist/order-status.d.ts +89 -0
- package/dist/order-view-9IlBg5AF.js +62 -0
- package/dist/order-view.d.ts +12 -0
- package/dist/page-action-Ctkf4xC6.js +7 -0
- package/dist/page-seo-a5FJl8x5.js +262 -0
- package/dist/page-seo.d.ts +34 -0
- package/dist/pages-Ca_Gt6mq.js +150 -0
- package/dist/pages.d.ts +45 -0
- package/dist/payment-dialog-frame-B-Q_xQ94.js +57 -0
- package/dist/payment-logo-BoPaGJc_.js +46 -0
- package/dist/payment-logo-svgs-By9lDTLK.js +20 -0
- package/dist/payment-logo.d.ts +11 -0
- package/dist/payment-logos-BYvdwYyJ.js +23 -0
- package/dist/payment-methods-B8ocTgFZ.js +73 -0
- package/dist/payment-methods.d.ts +58 -0
- package/dist/payment-return-5cjypwIw.js +7 -0
- package/dist/place-CmW4hBWY.js +13 -0
- package/dist/place.d.ts +4 -0
- package/dist/preview-BiIBif3C.js +427 -0
- package/dist/preview-address-Cf0sJ-9p.js +21 -0
- package/dist/preview-cart-Dg2CVktS.js +376 -0
- package/dist/product-preview-BspMAecJ.js +51 -0
- package/dist/product-preview.d.ts +5 -0
- package/dist/product-variants-BNDHvs1y.js +46 -0
- package/dist/product-variants.d.ts +12 -0
- package/dist/public-pages-XtwlaLwt.js +194 -0
- package/dist/public-pages.d.ts +67 -0
- package/dist/registration-CpDcv8ql.js +91 -0
- package/dist/registration.d.ts +12 -0
- package/dist/request-security-CRsRMgwj.js +76 -0
- package/dist/rich-text-M-Ju8RL4.js +9 -0
- package/dist/rich-text.d.ts +5 -0
- package/dist/route-text-DcJw2SsK.js +9 -0
- package/dist/route-text.d.ts +4 -0
- package/dist/section-preview-CmsMl3yu.js +83 -0
- package/dist/section-preview.d.ts +12 -0
- package/dist/sections/account-addresses.d.ts +18 -0
- package/dist/sections/account-addresses.js +375 -0
- package/dist/sections/account-order.d.ts +12 -0
- package/dist/sections/account-order.js +592 -0
- package/dist/sections/account-orders.d.ts +8 -0
- package/dist/sections/account-orders.js +359 -0
- package/dist/sections/account-profile.d.ts +16 -0
- package/dist/sections/account-profile.js +123 -0
- package/dist/sections/checkout.d.ts +24 -0
- package/dist/sections/checkout.js +963 -0
- package/dist/sections/complaint-book.d.ts +8 -0
- package/dist/sections/complaint-book.js +538 -0
- package/dist/sections/design-system.d.ts +1 -0
- package/dist/sections/design-system.js +310 -0
- package/dist/sections/draft-invoice.d.ts +20 -0
- package/dist/sections/draft-invoice.js +158 -0
- package/dist/sections/order-tracking.d.ts +19 -0
- package/dist/sections/order-tracking.js +141 -0
- package/dist/sections-CNUMWTF_.js +77 -0
- package/dist/sections.d.ts +19 -0
- package/dist/seo-GpDRhAAA.js +78 -0
- package/dist/session-CKvmFJyg.js +379 -0
- package/dist/session.d.ts +24 -0
- package/dist/settings-B0NO2A6Y.js +78 -0
- package/dist/settings.d.ts +51 -0
- package/dist/source-marker-DhqSkW7n.js +4 -0
- package/dist/sources-B-3JFJfb.js +46 -0
- package/dist/sources.d.ts +7 -0
- package/dist/store-8Q-_LFrQ.js +152 -0
- package/dist/store-contact--hY6CoFb.js +19 -0
- package/dist/store-contact.d.ts +6 -0
- package/dist/store.d.ts +17 -0
- package/dist/storefront-language-DrMjVbmk.js +29 -0
- package/dist/storefront-language.d.ts +14 -0
- package/dist/storefront-text-D25ae1qf.js +11 -0
- package/dist/storefront-text-en-ljjjSS4q.js +1030 -0
- package/dist/storefront-text-pt-BtHaJLcp.js +1030 -0
- package/dist/storefront-text.d.ts +4 -0
- package/dist/stripe-dialog-Ev9s3Z62.js +396 -0
- package/dist/stripe-dialog.d.ts +30 -0
- package/dist/telemetry-HuZXWBPi.js +114 -0
- package/dist/telemetry.d.ts +10 -0
- package/dist/templates-CAqoNyTW.js +40 -0
- package/dist/templates.d.ts +15 -0
- package/dist/urls-CpGQPxvf.js +71 -0
- package/dist/urls.d.ts +18 -0
- package/dist/use-checkout-CZcxXJXi.js +42 -0
- package/dist/use-checkout-payment-9R-zacrJ.js +80 -0
- package/dist/use-checkout-payment.d.ts +18 -0
- package/dist/use-checkout.d.ts +36 -0
- package/dist/use-culqi-checkout-CrjLAipU.js +109 -0
- package/dist/use-order-payment-BKIz_IO0.js +45 -0
- package/dist/use-order-payment.d.ts +18 -0
- package/dist/use-payment-token-CErx-tN-.js +105 -0
- package/dist/vite-payment-logos-kziu7wKb.js +27 -0
- package/dist/vite-payment-logos.d.ts +24 -0
- package/dist/vite-public-assets-DW2fgtVb.js +209 -0
- package/dist/vite-public-assets.d.ts +29 -0
- package/dist/vite-sections-DTBr0-Kg.js +464 -0
- package/dist/vite-sections.d.ts +21 -0
- package/dist/vite-theme-ui-BMO-CDi3.js +41 -0
- package/dist/vite-theme-ui.d.ts +10 -0
- package/dist/vite.d.ts +36 -0
- package/dist/vite.js +127 -0
- package/dist/voucher-payment-CVFDZ1L8.js +66 -0
- package/dist/worker-CNi-gzCu.js +86 -0
- package/dist/worker.d.ts +27 -0
- package/package.json +100 -76
- package/src/sections/account-addresses.tsx +215 -0
- package/src/sections/account-order.tsx +424 -0
- package/src/sections/account-orders.tsx +259 -0
- package/src/sections/account-profile.tsx +70 -0
- package/src/sections/checkout.tsx +586 -0
- package/src/sections/complaint-book.tsx +325 -0
- package/src/sections/design-system.tsx +146 -0
- package/src/sections/draft-invoice.tsx +105 -0
- package/src/sections/order-tracking.tsx +81 -0
- package/src/vite-client.d.ts +4 -0
- package/bin/ecomiq.mjs +0 -179
- package/public/design-bridge.js +0 -318
- package/src/api-proxy.ts +0 -194
- package/src/auth/create-storefront-auth.ts +0 -94
- package/src/auth/ecomiq-oauth-password-client.ts +0 -26
- package/src/auth/ecomiq-oauth-password-constants.ts +0 -24
- package/src/auth/ecomiq-oauth-password-plugin.ts +0 -294
- package/src/auth/ecomiq-oauth-password-types.ts +0 -24
- package/src/auth/ecomiq-storefront-sign-in.ts +0 -44
- package/src/auth/env.ts +0 -7
- package/src/auth/secure-cookies.ts +0 -23
- package/src/auth/storefront-auth-base-url.ts +0 -117
- package/src/auth/storefront-auth.ts +0 -7
- package/src/cart-session.ts +0 -239
- package/src/client.ts +0 -48
- package/src/codegen.mjs +0 -801
- package/src/components/account/customer-addresses-panel.tsx +0 -678
- package/src/components/account/customer-order-detail-panel.tsx +0 -285
- package/src/components/account/customer-orders-panel.tsx +0 -192
- package/src/components/account/customer-profile-panel.tsx +0 -379
- package/src/components/checkout/cart-sync.ts +0 -143
- package/src/components/checkout/checkout-contact-section.tsx +0 -76
- package/src/components/checkout/checkout-external-update-banner.tsx +0 -15
- package/src/components/checkout/checkout-form-controls.tsx +0 -139
- package/src/components/checkout/checkout-line-items.tsx +0 -43
- package/src/components/checkout/checkout-order-summary.tsx +0 -165
- package/src/components/checkout/checkout-payment-section.tsx +0 -212
- package/src/components/checkout/checkout-session-view.tsx +0 -564
- package/src/components/checkout/checkout-shipping-address-section.tsx +0 -400
- package/src/components/checkout/checkout-shipping-section.tsx +0 -98
- package/src/components/checkout/checkout-states.tsx +0 -73
- package/src/components/checkout/checkout-step-card.tsx +0 -101
- package/src/components/checkout/checkout-steps.ts +0 -111
- package/src/components/checkout/form-schema.ts +0 -281
- package/src/components/checkout/format.ts +0 -15
- package/src/components/checkout/index.ts +0 -5
- package/src/components/checkout/map-customer-to-checkout.ts +0 -188
- package/src/components/checkout/types.ts +0 -41
- package/src/components/checkout/use-checkout-customer-prefill.ts +0 -409
- package/src/components/checkout/use-checkout-geo.ts +0 -50
- package/src/components/checkout/use-checkout-session.ts +0 -640
- package/src/components/hero.tsx +0 -291
- package/src/config.d.ts +0 -94
- package/src/config.mjs +0 -146
- package/src/format-money.ts +0 -31
- package/src/health.ts +0 -90
- package/src/hooks/index.ts +0 -9
- package/src/hooks/recently-viewed.ts +0 -125
- package/src/index.ts +0 -349
- package/src/link.tsx +0 -2
- package/src/pages/account/account-page-skeleton.tsx +0 -69
- package/src/pages/account/account-page-view.tsx +0 -270
- package/src/pages/account/account-shell.tsx +0 -101
- package/src/pages/account/parse-account-route.ts +0 -36
- package/src/pages/account.tsx +0 -16
- package/src/pages/api.$.ts +0 -20
- package/src/pages/api.auth.$.ts +0 -16
- package/src/pages/checkout.$checkoutId.tsx +0 -60
- package/src/pages/checkout.tsx +0 -31
- package/src/pages/pages.$pageSlug.tsx +0 -74
- package/src/pages/thank-you.tsx +0 -53
- package/src/providers/auth.tsx +0 -96
- package/src/providers/cart.tsx +0 -275
- package/src/providers/ecomiq.ts +0 -137
- package/src/providers/index.ts +0 -26
- package/src/proxy-api.ts +0 -20
- package/src/public-config.ts +0 -24
- package/src/review-submission.ts +0 -89
- package/src/runtime/context.ts +0 -2
- package/src/runtime/default-layout.tsx +0 -64
- package/src/runtime/head.ts +0 -149
- package/src/runtime/page.tsx +0 -143
- package/src/runtime/postcss-browser-stub.mjs +0 -5
- package/src/server-config-store.ts +0 -35
- package/src/server.ts +0 -286
- package/src/styles.css +0 -76
- package/src/ui/badge.tsx +0 -32
- package/src/ui/button.tsx +0 -31
- package/src/ui/card.tsx +0 -31
- package/src/ui/carousel.tsx +0 -218
- package/src/ui/cn.ts +0 -6
- package/src/ui/index.ts +0 -8
- package/src/ui/separator.tsx +0 -6
- package/src/ui/skeleton.tsx +0 -6
- package/src/ui/storefront-error.tsx +0 -49
- package/src/utils.ts +0 -36
- package/src/vite-inline.mjs +0 -263
- package/templates/__root.tsx +0 -83
- package/templates/router.tsx +0 -19
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ecomiq
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,113 +1,160 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Login de clientes
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
configurar TanStack Start, Vite ni Cloudflare a mano.
|
|
3
|
+
La tienda elige el modo de acceso en Admin, en **Configuración → Compra → Cuentas de clientes**. El SDK consulta esa configuración: `hosted` redirige a Identity y `storefront` muestra el formulario del tema. Ambos modos usan las mismas cuentas y sesión de cliente.
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
```bash
|
|
9
|
-
npm install @ecomiq/storefront
|
|
10
|
-
# o
|
|
11
|
-
yarn add @ecomiq/storefront
|
|
12
|
-
# o
|
|
13
|
-
pnpm add @ecomiq/storefront
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
Peer deps: `react` y `react-dom` ^19.
|
|
17
|
-
|
|
18
|
-
## Uso
|
|
19
|
-
|
|
20
|
-
```js
|
|
21
|
-
// ecomiq.config.js
|
|
22
|
-
import { defineConfig } from '@ecomiq/storefront/config'
|
|
23
|
-
|
|
24
|
-
export default defineConfig({
|
|
25
|
-
domain: 'mi-tienda',
|
|
26
|
-
// Server-only. Obligatorio en producción para usar una publicación de Cloud.
|
|
27
|
-
cloud: { baseUrl: 'https://cloud.internal.example' },
|
|
28
|
-
})
|
|
29
|
-
```
|
|
5
|
+
La ruta `/account/login` integra dos helpers:
|
|
30
6
|
|
|
31
7
|
```tsx
|
|
32
|
-
|
|
33
|
-
import {
|
|
8
|
+
import { CustomerLoginForm, startCustomerLogin, submitCustomerLogin } from "@ecomiq/storefront";
|
|
9
|
+
import type { Route } from "./+types/login";
|
|
34
10
|
|
|
35
|
-
export
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
})
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
CLI:
|
|
42
|
-
|
|
43
|
-
```bash
|
|
44
|
-
npx ecomiq dev
|
|
45
|
-
npx ecomiq build
|
|
46
|
-
npx ecomiq deploy
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
Si actualizas el package y `.ecomiq` quedó de una versión anterior:
|
|
50
|
-
|
|
51
|
-
```bash
|
|
52
|
-
npx ecomiq clean
|
|
53
|
-
npx ecomiq dev
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
### Runtime de desarrollo
|
|
57
|
-
|
|
58
|
-
`ecomiq dev` usa **Node** por defecto. Para emular Workers en local:
|
|
11
|
+
export function loader({ request, context }: Route.LoaderArgs) {
|
|
12
|
+
return startCustomerLogin(request, context);
|
|
13
|
+
}
|
|
59
14
|
|
|
60
|
-
|
|
61
|
-
|
|
15
|
+
export function action({ request, context }: Route.ActionArgs) {
|
|
16
|
+
return submitCustomerLogin(request, context);
|
|
17
|
+
}
|
|
62
18
|
```
|
|
63
19
|
|
|
64
|
-
`
|
|
65
|
-
|
|
66
|
-
Durante `ecomiq dev`, si `cloud.baseUrl` se omite, el framework conecta a
|
|
67
|
-
`http://127.0.0.1:8020`. En producción no se inventa un hostname: si falta la
|
|
68
|
-
URL o Cloud no tiene una publicación activa, el template conserva su home
|
|
69
|
-
clásica.
|
|
70
|
-
|
|
71
|
-
Ver el monorepo [ecomiq-storefront](https://github.com/ecomiq/ecomiq-storefront) para la app de referencia y más documentación.
|
|
72
|
-
|
|
73
|
-
## Storefront declarativo
|
|
20
|
+
El componente de la ruta puede usar `CustomerLoginForm` con `data={actionData ?? loaderData}`. Su prop `classNames` define `form`, `field`, `label`, `input`, `button`, `secondary`, `error` y `hint`; la página, tipografía, colores y controles conservan el diseño del tema. Los 13 temas incluidos tienen esta integración.
|
|
74
21
|
|
|
75
|
-
|
|
22
|
+
También se puede crear una interfaz propia con `<Form method="post">` de React Router, manteniendo esos helpers y este contrato:
|
|
76
23
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
24
|
+
| `intent` | Campos del formulario |
|
|
25
|
+
| --- | --- |
|
|
26
|
+
| `send` | `email` y token CAPTCHA si está habilitado |
|
|
27
|
+
| `verify` | `code` de seis dígitos; en el paso `secondFactor`, `secondFactor` de autenticación o recuperación |
|
|
28
|
+
| `resend` | Token CAPTCHA si está habilitado; respeta `retryAt` |
|
|
29
|
+
| `reset` | Ninguno; vuelve al ingreso del correo |
|
|
30
|
+
|
|
31
|
+
Los datos públicos incluyen `step`, `email`, `captcha`, `retryAt` y `error`. La transacción OAuth, PKCE y las claves de verificación permanecen en una cookie firmada HttpOnly; el formulario propio no debe enviar esos valores.
|
|
32
|
+
|
|
33
|
+
CAPTCHA es independiente del modo y opcional por tienda: `off`, Turnstile o Google reCAPTCHA v2. `CustomerLoginForm` carga y reinicia el proveedor configurado. Una UI propia debe enviar `cf-turnstile-response` o `g-recaptcha-response`; Turnstile usa la acción `customer_login_code`.
|
|
34
|
+
|
|
35
|
+
## Datos del registro
|
|
36
|
+
|
|
37
|
+
La tienda configura nombre, apellido, teléfono y fecha de nacimiento como `hidden`, `optional` o `required`. Después del código, ambos modos de login crean la sesión y consultan `GET /v1/customer/registration`. Si falta completar el alta, redirigen a `/account/register?next=...`; el destino se limita a una ruta interna segura.
|
|
38
|
+
|
|
39
|
+
La ruta de registro usa `loadCustomerRegistration` en su loader, `submitCustomerRegistration` en su action y `CustomerRegistrationForm` con las mismas clases personalizables del login. Los datos del formulario incluyen `registration`, `storeName`, `next`, `today` (fecha local de la tienda) y `error`. También puede implementarse una UI propia que envíe `firstName`, `lastName`, `phone` y `dateOfBirth` (ISO `YYYY-MM-DD`) con `<CustomerCsrfField />`.
|
|
40
|
+
|
|
41
|
+
`intent=complete` guarda los campos visibles; `intent=skip` permite omitir todos los opcionales cuando no hay obligatorios. Ambos llaman `POST /v1/customer/registration`. El backend valida los requisitos vigentes y recuerda que el cliente completó este paso. Los clientes existentes sólo vuelven si faltan datos que ahora son obligatorios.
|
|
42
|
+
|
|
43
|
+
Las rutas de cuenta usan `requireCustomer`; checkout permite invitados y llama `requireCustomerRegistration(request, context, current)` cuando hay sesión. La propia ruta de registro usa `customer` para no redirigirse a sí misma. La API también exige completar el registro antes de las operaciones protegidas.
|
|
44
|
+
|
|
45
|
+
## Desarrollo y temas publicados
|
|
46
|
+
|
|
47
|
+
En local, `ECOMIQ_AUTH_URL=http://localhost:8000` configura el transporte del BFF hacia Identity sin cambiar la autoridad OAuth pública ni desactivar la validación TLS. Aspire ya proporciona esta dirección al deployer local, que la propaga a los sitios instalados. Los ejemplos `.dev.vars.example` de cada tema la incluyen.
|
|
48
|
+
|
|
49
|
+
Los temas publicados son artifacts compilados. Para habilitar el modo dentro de la tienda en un tema existente, hay que **recompilar y publicar una versión que incluya los loaders, actions y formularios de login y registro**. Cambiar solamente la configuración de la tienda no modifica artifacts anteriores.
|
|
50
|
+
|
|
51
|
+
## Enlaces de catálogo
|
|
52
|
+
|
|
53
|
+
Los temas incluidos mantienen los enlaces a productos y categorías al cambiar
|
|
54
|
+
su patrón, slug o URL personalizada. Los menús se resuelven en la configuración
|
|
55
|
+
cacheada del servidor. Para botones y otros enlaces editables, el plugin
|
|
56
|
+
`catalogLinks()` de `@ecomiq/storefront/vite` genera `virtual:ecomiq-links`
|
|
57
|
+
durante la compilación; no analiza código durante las visitas.
|
|
58
|
+
|
|
59
|
+
El loader raíz pasa ese manifiesto a `readStorefrontLinks(client, manifestLinks)`
|
|
60
|
+
y devuelve el resultado como `storefrontLinks`. `StorefrontLink` y
|
|
61
|
+
`StorefrontAnchor` consumen ese mapa sin hacer peticiones. El SDK deduplica los
|
|
62
|
+
destinos, agrupa hasta 100 por petición y comparte los resultados durante
|
|
63
|
+
30 segundos. Los cambios se reflejan al renovar esa caché.
|
|
64
|
+
|
|
65
|
+
El loader también devuelve `publishedPages`, con las rutas
|
|
66
|
+
``settings.cms.pages.map((page) => `/pages/${page.slug}`)`` del snapshot público.
|
|
67
|
+
Ambos componentes ocultan los enlaces locales `/pages/...` que aún no tienen
|
|
68
|
+
publicación. Al publicar la página, el siguiente snapshot recupera su enlace;
|
|
69
|
+
no se consulta la API por cada enlace ni se modifica la configuración guardada.
|
|
70
|
+
|
|
71
|
+
El selector del editor conserva la identidad con
|
|
72
|
+
`setting.link("/products/cuaderno", "Comprar", { type: "product", id: "42" })`.
|
|
73
|
+
Con referencia devuelve un `CatalogLink`; sin referencia conserva el texto de
|
|
74
|
+
la URL. Ambos se pueden pasar a `StorefrontLink` y `StorefrontAnchor`, que
|
|
75
|
+
mantienen separados los destinos aunque otro producto reutilice la URL antigua.
|
|
76
|
+
También se reconocen los enlaces anteriores que resuelve el historial del
|
|
77
|
+
catálogo. Los enlaces externos, las rutas ajenas al catálogo y los parámetros
|
|
78
|
+
o anclas se conservan. En código personalizado, usar los componentes anteriores
|
|
79
|
+
para los enlaces editables; las tarjetas que ya reciben `product.url` o
|
|
80
|
+
`category.url` pueden utilizar directamente esos valores.
|
|
81
|
+
|
|
82
|
+
La instalación inicial de este soporte requiere actualizar y publicar el tema.
|
|
83
|
+
Los cambios posteriores de URLs de catálogo no requieren publicar otra vez.
|
|
84
|
+
|
|
85
|
+
## Logos de medios de pago
|
|
86
|
+
|
|
87
|
+
`PaymentLogo` pinta un `<img>` de 38×24 que apunta a
|
|
88
|
+
`/assets/payment-logos/<código>.svg`; el JavaScript de la tienda solo lleva los
|
|
89
|
+
nombres. El plugin `paymentLogos()` de `@ecomiq/storefront/vite` escribe esos
|
|
90
|
+
SVG en el build del navegador y los sirve en desarrollo.
|
|
91
|
+
|
|
92
|
+
## Medición y consentimiento
|
|
93
|
+
|
|
94
|
+
`@ecomiq/storefront/analytics` carga GA4, Google Tag Manager, Meta Pixel y el
|
|
95
|
+
código propio que la tienda configura en Admin (Tienda online → Ajustes →
|
|
96
|
+
Medición y píxeles), y solo después de que el cliente los acepta. Cada
|
|
97
|
+
herramienta pide su finalidad: GA4, «Medición»; Meta Pixel, «Publicidad»;
|
|
98
|
+
Google Tag Manager y el código propio, las dos (pueden llevar cualquier
|
|
99
|
+
etiqueta).
|
|
100
|
+
|
|
101
|
+
El loader raíz devuelve `analytics: readAnalytics(context, settings, cart)`;
|
|
102
|
+
es `null` sin IDs ni código propio, y en el lienzo. El `Layout` pinta
|
|
103
|
+
`<Analytics />` y `<ConsentBanner />`. El aviso no trae estilos: el tema le pasa
|
|
104
|
+
`className` y `classNames` (`text`, `link`, `configure`, `options`, `option`,
|
|
105
|
+
`actions`, `accept`, `reject`; sin `configure` usa la clase de `link`).
|
|
106
|
+
Nombra los proveedores que la tienda usa, avisa que los datos pueden tratarse
|
|
107
|
+
fuera del país y enlaza `/pages/privacidad` si existe. «Rechazar» y «Aceptar»
|
|
108
|
+
van en la primera capa; «Configurar» muestra una casilla sin marcar por
|
|
109
|
+
finalidad y «Guardar». Se pinta solo en el navegador.
|
|
110
|
+
|
|
111
|
+
La decisión se guarda 12 meses en `__Host-ecomiq-consent` como
|
|
112
|
+
`<elección>|<versión>|<fecha ISO>` (`analytics.marketing|2.gm.4|2026-10-12T15:30:00.000Z`,
|
|
113
|
+
`none|…`). La versión junta la del aviso, las herramientas activas y la versión
|
|
114
|
+
publicada de la página de privacidad: si cambia, el aviso vuelve a preguntar y
|
|
115
|
+
nada se carga hasta la nueva decisión. Una cookie sin versión no cuenta como
|
|
116
|
+
decisión. Las cookies del SDK usan el prefijo `__Host-` y `Secure`, así que otra
|
|
117
|
+
tienda del mismo dominio no puede escribirlas.
|
|
118
|
+
`window.ecomiq.consent.get()` devuelve `{ analytics, marketing, version, date }`
|
|
119
|
+
o `null`; `window.ecomiq.consent.set({ analytics, marketing })` y el evento
|
|
120
|
+
`ecomiq:consent` sirven a otros scripts.
|
|
121
|
+
|
|
122
|
+
Para cambiar o retirar la decisión, el pie del tema pinta `<ConsentLink />`
|
|
123
|
+
(«Cookies», con las clases de los enlaces vecinos): vuelve a abrir el aviso y
|
|
124
|
+
no pinta nada cuando la tienda no mide. Retirar una finalidad borra sus cookies
|
|
125
|
+
(`_ga*`, `_gid`, `_gat*`, `_fbp`, `_fbc`, `_gcl_*`), avisa a Google y Meta y
|
|
126
|
+
recarga la página si alguna herramienta ya estaba cargada.
|
|
127
|
+
|
|
128
|
+
Los loaders del SDK agregan `analytics` a sus datos (`productPage`,
|
|
129
|
+
`resolvePage`, `collectionPage`, `loadCheckout`, `loadOrderStatus`) y la ruta
|
|
130
|
+
`/products` usa `catalogAnalytics(q, result.products)`; las rutas devuelven esa
|
|
131
|
+
clave tal cual. `<Analytics />` envía `page_view`, `view_item`,
|
|
132
|
+
`view_item_list`, `search`, `add_to_cart` y `remove_from_cart` (comparando el
|
|
133
|
+
carrito de la raíz), `begin_checkout` una vez por carrito y `purchase`, que lee
|
|
134
|
+
la cookie `__Host-ecomiq-purchase` de 10 minutos que deja el pago cuando hay
|
|
135
|
+
consentimiento.
|
|
136
|
+
|
|
137
|
+
## Secciones por defecto
|
|
138
|
+
|
|
139
|
+
`@ecomiq/storefront/sections/<id>` trae secciones de página listas:
|
|
140
|
+
`order-tracking` («Seguimiento», `<OrderTracking loaderData={…} />` con el
|
|
141
|
+
loader `loadTracking`), `draft-invoice` («Pedido por pagar»,
|
|
142
|
+
`<DraftInvoice loaderData={…} actionData={…} />` con `loadOrderDraft` y
|
|
143
|
+
`orderDraftAction`) y `complaint-book` («Libro de Reclamaciones»,
|
|
144
|
+
`<ComplaintBook loaderData={…} actionData={…} />` con `loadComplaintBook` y
|
|
145
|
+
`complaintBookAction`, en la ruta `libro-de-reclamaciones`). Cada una exporta
|
|
146
|
+
`catalog` y `settings` como las secciones del tema; `complaint-book` no tiene
|
|
147
|
+
ajustes. `complaintBookLinks(settings)` devuelve el enlace del pie solo para
|
|
148
|
+
tiendas en soles (como la API, que responde 404 al resto), y el formulario usa `<Captcha>` con la acción Turnstile
|
|
149
|
+
`complaint_book`. El formulario va en multipart con hasta 3 adjuntos (JPG, PNG,
|
|
150
|
+
WebP o PDF de 5 MB); `complaintBookAttachmentError(files)` los valida en el
|
|
151
|
+
navegador y en la action.
|
|
152
|
+
|
|
153
|
+
Se pintan con el kit del tema: importan `@theme/ui/*` (`button`, `input`,
|
|
154
|
+
`label`, `textarea`, `checkbox`, `radio-group`, `select`),
|
|
155
|
+
que el plugin `themeUi()` de `@ecomiq/storefront/vite` lleva a
|
|
156
|
+
`app/components/ui/*`, y solo usan clases con los tokens del tema. Si al tema le
|
|
157
|
+
falta un componente, el build falla con el archivo que falta y la sección que lo
|
|
158
|
+
usa. El código fuente viaja en `src/sections` del paquete: «Personalizar» en el
|
|
159
|
+
editor lo copia a `app/components/sections/<id>.tsx` y apunta la ruta a esa
|
|
160
|
+
copia.
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { optional, text } from "./form-BtQcbXlJ.js";
|
|
2
|
+
import { identityDocumentError, isIdentityDocumentType } from "./documents-T1mzc_j8.js";
|
|
3
|
+
import { ADDRESS_INTENTS, ORDER_REQUEST_INTENTS, isIntent } from "./form-contracts-BNpHaTGJ.js";
|
|
4
|
+
import { readPlace } from "./place-CmW4hBWY.js";
|
|
5
|
+
import { customerHeaders, requireCustomer, requireCustomerWith } from "./session-CKvmFJyg.js";
|
|
6
|
+
import { found, readCountries, readDocumentCountries, store } from "./store-8Q-_LFrQ.js";
|
|
7
|
+
import { requestUrl } from "./urls-CpGQPxvf.js";
|
|
8
|
+
import { data } from "react-router";
|
|
9
|
+
import { ApiError } from "@ecomiq/core";
|
|
10
|
+
// src/account.ts
|
|
11
|
+
const DEFAULT_ADDRESS_LABEL = "Casa";
|
|
12
|
+
function placeName(form, name) {
|
|
13
|
+
const value = text(form, name);
|
|
14
|
+
if (value.includes("|")) return readPlace(value).name;
|
|
15
|
+
return value;
|
|
16
|
+
}
|
|
17
|
+
function readCustomerAddress(form, countryCode) {
|
|
18
|
+
return {
|
|
19
|
+
addressKey: text(form, "label") || DEFAULT_ADDRESS_LABEL,
|
|
20
|
+
countryIsoCode: countryCode,
|
|
21
|
+
addressLine: text(form, "line"),
|
|
22
|
+
department: placeName(form, "department") || null,
|
|
23
|
+
province: placeName(form, "province") || null,
|
|
24
|
+
district: placeName(form, "district") || null,
|
|
25
|
+
cellPhone: optional(form, "phone"),
|
|
26
|
+
reference: optional(form, "reference"),
|
|
27
|
+
isDefault: form.get("default") === "on"
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
async function loadAccount(request, context) {
|
|
31
|
+
return { name: (await requireCustomer(request, context)).name };
|
|
32
|
+
}
|
|
33
|
+
const ORDER_STATUSES = [
|
|
34
|
+
"open",
|
|
35
|
+
"delivered",
|
|
36
|
+
"cancelled"
|
|
37
|
+
];
|
|
38
|
+
function readOrderFilters(url) {
|
|
39
|
+
const status = ORDER_STATUSES.find((value) => value === url.searchParams.get("status")) ?? null;
|
|
40
|
+
const sort = url.searchParams.get("sort") === "oldest" ? "oldest" : "newest";
|
|
41
|
+
const view = url.searchParams.get("view") === "grid" ? "grid" : "list";
|
|
42
|
+
return {
|
|
43
|
+
q: (url.searchParams.get("q") ?? "").trim().slice(0, 100),
|
|
44
|
+
status,
|
|
45
|
+
sort,
|
|
46
|
+
view
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/** The query string of the orders list for these filters, without the default values. */
|
|
50
|
+
function orderFiltersQuery(filters) {
|
|
51
|
+
const query = new URLSearchParams();
|
|
52
|
+
if (filters.q !== "") query.set("q", filters.q);
|
|
53
|
+
if (filters.status !== null) query.set("status", filters.status);
|
|
54
|
+
if (filters.sort === "oldest") query.set("sort", "oldest");
|
|
55
|
+
if (filters.view === "grid") query.set("view", "grid");
|
|
56
|
+
return query;
|
|
57
|
+
}
|
|
58
|
+
async function loadOrders(request, context) {
|
|
59
|
+
const url = requestUrl(request);
|
|
60
|
+
const filters = readOrderFilters(url);
|
|
61
|
+
const page = await requireCustomerWith(request, context, (headers) => store(context).getCustomerOrders({
|
|
62
|
+
...headers,
|
|
63
|
+
cursor: url.searchParams.get("cursor") ?? void 0,
|
|
64
|
+
search: filters.q || void 0,
|
|
65
|
+
status: filters.status ?? void 0,
|
|
66
|
+
sortDirection: filters.sort === "oldest" ? "asc" : "desc"
|
|
67
|
+
}));
|
|
68
|
+
let next = null;
|
|
69
|
+
if (page.hasNextPage && page.nextCursor) {
|
|
70
|
+
const query = orderFiltersQuery(filters);
|
|
71
|
+
query.set("cursor", page.nextCursor);
|
|
72
|
+
next = `${url.pathname}?${query}`;
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
orders: page.data,
|
|
76
|
+
next,
|
|
77
|
+
filters
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
async function loadOrder(request, context, id) {
|
|
81
|
+
return { order: await found(requireCustomerWith(request, context, (headers) => store(context).getCustomerOrder(id, headers))) };
|
|
82
|
+
}
|
|
83
|
+
const MAX_RETURN_PHOTOS = 3;
|
|
84
|
+
function orderRequestError(error, intent) {
|
|
85
|
+
const code = error instanceof ApiError ? error.code ?? "" : "";
|
|
86
|
+
if (code.includes("Receipt")) return "Las fotos deben ser JPG, PNG o WebP de hasta 5 MB.";
|
|
87
|
+
if (code === "Orders.ReturnPhotosLimit") return "Puedes adjuntar hasta 3 fotos.";
|
|
88
|
+
if (code === "Orders.ReturnQuantityInvalid") return "Revisa las cantidades a devolver.";
|
|
89
|
+
if (code === "Orders.ReturnUnavailable") return "Este pedido ya no admite devoluciones.";
|
|
90
|
+
if (code === "Orders.CancellationUnavailable") return "Este pedido ya no se puede cancelar.";
|
|
91
|
+
return intent === "return" ? "No pudimos enviar tu solicitud de devolución." : "No pudimos enviar tu solicitud de cancelación.";
|
|
92
|
+
}
|
|
93
|
+
function returnLines(form) {
|
|
94
|
+
const quantities = form.getAll("quantity");
|
|
95
|
+
return form.getAll("itemId").map((itemId, index) => ({
|
|
96
|
+
itemId: String(itemId),
|
|
97
|
+
quantity: Number(quantities[index] ?? 0)
|
|
98
|
+
})).filter((line) => line.itemId !== "" && Number.isInteger(line.quantity) && line.quantity > 0);
|
|
99
|
+
}
|
|
100
|
+
/** Return or cancellation request the customer sends from an order of their account. */
|
|
101
|
+
async function orderRequestAction(request, context, orderId) {
|
|
102
|
+
const current = await requireCustomer(request, context);
|
|
103
|
+
const client = store(context);
|
|
104
|
+
const headers = customerHeaders(current);
|
|
105
|
+
const form = await request.formData();
|
|
106
|
+
const intent = text(form, "intent");
|
|
107
|
+
if (!isIntent(ORDER_REQUEST_INTENTS, intent)) return data({
|
|
108
|
+
sent: null,
|
|
109
|
+
error: "Acción no válida."
|
|
110
|
+
}, { status: 400 });
|
|
111
|
+
const reason = text(form, "reason");
|
|
112
|
+
const comment = text(form, "comment") || void 0;
|
|
113
|
+
if (reason === "") return data({
|
|
114
|
+
sent: null,
|
|
115
|
+
error: "Elige un motivo."
|
|
116
|
+
}, { status: 400 });
|
|
117
|
+
try {
|
|
118
|
+
if (intent === "cancel") {
|
|
119
|
+
await client.requestCustomerOrderCancellation(orderId, {
|
|
120
|
+
reason,
|
|
121
|
+
comment
|
|
122
|
+
}, headers);
|
|
123
|
+
return {
|
|
124
|
+
sent: "cancel",
|
|
125
|
+
error: null
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
const items = returnLines(form);
|
|
129
|
+
if (items.length === 0) return data({
|
|
130
|
+
sent: null,
|
|
131
|
+
error: "Elige qué productos quieres devolver."
|
|
132
|
+
}, { status: 400 });
|
|
133
|
+
const photos = form.getAll("photos").filter((photo) => photo instanceof File && photo.size > 0);
|
|
134
|
+
if (photos.length > MAX_RETURN_PHOTOS) return data({
|
|
135
|
+
sent: null,
|
|
136
|
+
error: "Puedes adjuntar hasta 3 fotos."
|
|
137
|
+
}, { status: 400 });
|
|
138
|
+
await client.requestCustomerOrderReturn(orderId, {
|
|
139
|
+
reason,
|
|
140
|
+
comment,
|
|
141
|
+
items,
|
|
142
|
+
photos
|
|
143
|
+
}, headers);
|
|
144
|
+
return {
|
|
145
|
+
sent: "return",
|
|
146
|
+
error: null
|
|
147
|
+
};
|
|
148
|
+
} catch (error) {
|
|
149
|
+
return data({
|
|
150
|
+
sent: null,
|
|
151
|
+
error: orderRequestError(error, intent)
|
|
152
|
+
}, { status: 400 });
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
async function loadAddresses(request, context) {
|
|
156
|
+
const client = store(context);
|
|
157
|
+
const [addresses, countries] = await requireCustomerWith(request, context, (headers) => Promise.all([client.getCustomerAddresses(headers), readCountries(client)]));
|
|
158
|
+
return {
|
|
159
|
+
addresses,
|
|
160
|
+
countries
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
async function addressAction(request, context) {
|
|
164
|
+
const current = await requireCustomer(request, context);
|
|
165
|
+
const client = store(context);
|
|
166
|
+
const headers = customerHeaders(current);
|
|
167
|
+
const form = await request.formData();
|
|
168
|
+
const intent = text(form, "intent") || "save";
|
|
169
|
+
if (!isIntent(ADDRESS_INTENTS, intent)) return data({ error: "Acción no válida." }, { status: 400 });
|
|
170
|
+
try {
|
|
171
|
+
if (intent === "delete") {
|
|
172
|
+
await client.deleteCustomerAddress(text(form, "id"), headers);
|
|
173
|
+
return { error: null };
|
|
174
|
+
}
|
|
175
|
+
if (intent === "default") {
|
|
176
|
+
const address = (await client.getCustomerAddresses(headers)).find((item) => item.id === text(form, "id"));
|
|
177
|
+
if (address) await client.updateCustomerAddress(address.id, {
|
|
178
|
+
...address,
|
|
179
|
+
isDefault: true
|
|
180
|
+
}, headers);
|
|
181
|
+
return { error: null };
|
|
182
|
+
}
|
|
183
|
+
const { store: settings } = await client.getSettings();
|
|
184
|
+
const requestedCountry = text(form, "country");
|
|
185
|
+
if (requestedCountry !== "" && requestedCountry !== settings.countryCode) return data({ error: "La dirección debe ser dentro del país de la tienda." }, { status: 400 });
|
|
186
|
+
const input = readCustomerAddress(form, settings.countryCode);
|
|
187
|
+
if (!input.addressLine || !input.district || !input.cellPhone) return data({ error: "Completa la dirección, el distrito y el celular." }, { status: 400 });
|
|
188
|
+
const id = text(form, "id");
|
|
189
|
+
if (id) await client.updateCustomerAddress(id, input, headers);
|
|
190
|
+
else await client.createCustomerAddress(input, headers);
|
|
191
|
+
return { error: null };
|
|
192
|
+
} catch (error) {
|
|
193
|
+
if (error instanceof ApiError && error.status < 500) return data({ error: "Revisa los datos de la dirección." }, { status: 400 });
|
|
194
|
+
return data({ error: "No pudimos guardar la dirección." }, { status: 400 });
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
async function loadProfile(request, context) {
|
|
198
|
+
const client = store(context);
|
|
199
|
+
const [me, countries] = await requireCustomerWith(request, context, (headers) => Promise.all([client.getCustomerMe(headers), readDocumentCountries(client)]));
|
|
200
|
+
return {
|
|
201
|
+
me,
|
|
202
|
+
countries
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
async function profileAction(request, context) {
|
|
206
|
+
const current = await requireCustomer(request, context);
|
|
207
|
+
const client = store(context);
|
|
208
|
+
const headers = customerHeaders(current);
|
|
209
|
+
const form = await request.formData();
|
|
210
|
+
const firstName = text(form, "firstName");
|
|
211
|
+
const lastName = text(form, "lastName");
|
|
212
|
+
try {
|
|
213
|
+
const me = await client.getCustomerMe(headers);
|
|
214
|
+
const { store: settings } = await client.getSettings();
|
|
215
|
+
const number = text(form, "document");
|
|
216
|
+
const type = text(form, "identificationType");
|
|
217
|
+
const country = text(form, "identificationCountryCode");
|
|
218
|
+
if (number !== "" && !isIdentityDocumentType(type)) return data({
|
|
219
|
+
saved: false,
|
|
220
|
+
error: "Elige el tipo de documento."
|
|
221
|
+
}, { status: 400 });
|
|
222
|
+
const documentError = isIdentityDocumentType(type) ? identityDocumentError(type, country, number) : null;
|
|
223
|
+
if (documentError) return data({
|
|
224
|
+
saved: false,
|
|
225
|
+
error: documentError
|
|
226
|
+
}, { status: 400 });
|
|
227
|
+
if (me.entityIdentificationNumber && !/^[A-Z]{2}$/.test(country)) return data({
|
|
228
|
+
saved: false,
|
|
229
|
+
error: "Elige el país emisor del documento."
|
|
230
|
+
}, { status: 400 });
|
|
231
|
+
await client.patchCustomer({
|
|
232
|
+
firstName,
|
|
233
|
+
lastName,
|
|
234
|
+
phone: text(form, "phone"),
|
|
235
|
+
identificationNumber: number || null,
|
|
236
|
+
identificationType: number ? type : null,
|
|
237
|
+
identificationCountryCode: number || me.entityIdentificationNumber ? country : null,
|
|
238
|
+
entityName: me.entityName,
|
|
239
|
+
entityIdentificationNumber: me.entityIdentificationNumber,
|
|
240
|
+
gender: me.gender || null,
|
|
241
|
+
role: me.role,
|
|
242
|
+
companyName: me.companyName,
|
|
243
|
+
dateOfBirth: me.dateOfBirth,
|
|
244
|
+
consent: me.consent,
|
|
245
|
+
countryIsoCode: me.countryIsoCode ?? settings.countryCode
|
|
246
|
+
}, headers);
|
|
247
|
+
return {
|
|
248
|
+
saved: true,
|
|
249
|
+
error: null
|
|
250
|
+
};
|
|
251
|
+
} catch {
|
|
252
|
+
return data({
|
|
253
|
+
saved: false,
|
|
254
|
+
error: "No pudimos guardar tus datos."
|
|
255
|
+
}, { status: 400 });
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
export { addressAction, loadAccount, loadAddresses, loadOrder, loadOrders, loadProfile, orderFiltersQuery, orderRequestAction, profileAction, readCustomerAddress, readOrderFilters };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { CustomerCsrfField } from "./customer-csrf-C1oDX-Py.js";
|
|
2
|
+
import { usePageAction } from "./page-action-Ctkf4xC6.js";
|
|
3
|
+
import { Form } from "react-router";
|
|
4
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
// src/account-forms.tsx
|
|
6
|
+
const ADDRESS_ACTION = "/account/addresses";
|
|
7
|
+
const PROFILE_ACTION = "/account/profile";
|
|
8
|
+
function AddressForm({ intent = "save", addressId, children, ...props }) {
|
|
9
|
+
const action = usePageAction(ADDRESS_ACTION);
|
|
10
|
+
return /* @__PURE__ */ jsxs(Form, {
|
|
11
|
+
method: "post",
|
|
12
|
+
action,
|
|
13
|
+
...props,
|
|
14
|
+
children: [
|
|
15
|
+
/* @__PURE__ */ jsx(CustomerCsrfField, {}),
|
|
16
|
+
/* @__PURE__ */ jsx("input", {
|
|
17
|
+
type: "hidden",
|
|
18
|
+
name: "intent",
|
|
19
|
+
value: intent
|
|
20
|
+
}),
|
|
21
|
+
/* @__PURE__ */ jsx("input", {
|
|
22
|
+
type: "hidden",
|
|
23
|
+
name: "id",
|
|
24
|
+
value: addressId ?? ""
|
|
25
|
+
}),
|
|
26
|
+
children
|
|
27
|
+
]
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
function ProfileForm({ children, ...props }) {
|
|
31
|
+
const action = usePageAction(PROFILE_ACTION);
|
|
32
|
+
return /* @__PURE__ */ jsxs(Form, {
|
|
33
|
+
method: "post",
|
|
34
|
+
action,
|
|
35
|
+
...props,
|
|
36
|
+
children: [/* @__PURE__ */ jsx(CustomerCsrfField, {}), children]
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
export { ADDRESS_ACTION, AddressForm, PROFILE_ACTION, ProfileForm };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { FetcherFormProps } from "react-router";
|
|
2
|
+
type FormProps = Omit<FetcherFormProps, "action" | "method" | "encType">;
|
|
3
|
+
export type AddressFormProps = FormProps & ({
|
|
4
|
+
intent?: "save";
|
|
5
|
+
addressId?: string | null;
|
|
6
|
+
} | {
|
|
7
|
+
intent: "default" | "delete";
|
|
8
|
+
addressId: string;
|
|
9
|
+
});
|
|
10
|
+
export type ProfileFormProps = FormProps;
|
|
11
|
+
export declare function AddressForm({ intent, addressId, children, ...props }: AddressFormProps): import("react").JSX.Element;
|
|
12
|
+
export declare function ProfileForm({ children, ...props }: ProfileFormProps): import("react").JSX.Element;
|
|
13
|
+
export {};
|