@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.
- package/LICENSE +21 -0
- package/README.md +150 -123
- 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 -81
- 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/cloud-runtime-client.ts +0 -378
- package/src/codegen.mjs +0 -789
- 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 -354
- 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 -111
- 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 -301
- package/src/storefront-definition/contract.ts +0 -1
- package/src/storefront-definition/data.ts +0 -188
- package/src/storefront-definition/index.ts +0 -2
- package/src/storefront-definition/renderer.tsx +0 -1481
- package/src/storefront-layout.ts +0 -59
- package/src/storefront-runtime.ts +0 -173
- package/src/styles.css +0 -97
- 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/src/components/hero.tsx
DELETED
|
@@ -1,291 +0,0 @@
|
|
|
1
|
-
import { Link } from '../link'
|
|
2
|
-
import { Carousel } from '../ui/carousel'
|
|
3
|
-
import { cn } from '../ui/cn'
|
|
4
|
-
import { buttonVariants } from '../ui/button'
|
|
5
|
-
import type { CmsPageDetailsDto, CmsSectionDto } from '@ecomiq/core'
|
|
6
|
-
import { useEffect, useRef, useState } from 'react'
|
|
7
|
-
import { z } from 'zod'
|
|
8
|
-
|
|
9
|
-
/** Slide tipado para uso manual (sin CMS). */
|
|
10
|
-
export type HeroSlide = {
|
|
11
|
-
id?: string
|
|
12
|
-
title: string
|
|
13
|
-
subtitle?: string
|
|
14
|
-
highlight?: string
|
|
15
|
-
description?: string
|
|
16
|
-
ctaText?: string
|
|
17
|
-
ctaUrl?: string
|
|
18
|
-
imageUrl?: string
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export type HeroProps = {
|
|
22
|
-
/** Página CMS: busca la sección hero automáticamente. */
|
|
23
|
-
page?: Pick<CmsPageDetailsDto, 'sections'> | null
|
|
24
|
-
/** Sección CMS directa (prioridad sobre `page`). */
|
|
25
|
-
section?: CmsSectionDto | null
|
|
26
|
-
/** Slides manuales (prioridad máxima). */
|
|
27
|
-
slides?: HeroSlide[]
|
|
28
|
-
/** Autoplay en ms. Default: 6000. `false` para desactivar. */
|
|
29
|
-
autoplay?: number | false
|
|
30
|
-
/** Altura fija en px. Si no, usa `section.settings.height`. */
|
|
31
|
-
height?: number
|
|
32
|
-
className?: string
|
|
33
|
-
fallbacks?: {
|
|
34
|
-
title?: string
|
|
35
|
-
subtitle?: string
|
|
36
|
-
ctaText?: string
|
|
37
|
-
ctaUrl?: string
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const HERO_SECTION_TYPES = new Set(['hero', 'banner', 'slideshow', 'carousel'])
|
|
42
|
-
const HERO_BLOCK_TYPES = new Set(['slide'])
|
|
43
|
-
|
|
44
|
-
const HeroTextSchema = z.string().trim().min(1)
|
|
45
|
-
|
|
46
|
-
const HeroSectionSettingsSchema = z
|
|
47
|
-
.object({
|
|
48
|
-
enabled: z.boolean().optional(),
|
|
49
|
-
title: HeroTextSchema.optional(),
|
|
50
|
-
subtitle: HeroTextSchema.optional(),
|
|
51
|
-
description: HeroTextSchema.optional(),
|
|
52
|
-
ctaText: HeroTextSchema.optional(),
|
|
53
|
-
ctaUrl: HeroTextSchema.optional(),
|
|
54
|
-
imageUrl: HeroTextSchema.optional(),
|
|
55
|
-
height: z.number().positive().optional(),
|
|
56
|
-
})
|
|
57
|
-
.strip()
|
|
58
|
-
|
|
59
|
-
const HeroBlockSettingsSchema = z
|
|
60
|
-
.object({
|
|
61
|
-
title: HeroTextSchema,
|
|
62
|
-
subtitle: HeroTextSchema.optional(),
|
|
63
|
-
highlight: HeroTextSchema.optional(),
|
|
64
|
-
description: HeroTextSchema.optional(),
|
|
65
|
-
ctaText: HeroTextSchema.optional(),
|
|
66
|
-
ctaUrl: HeroTextSchema.optional(),
|
|
67
|
-
imageUrl: HeroTextSchema.optional(),
|
|
68
|
-
})
|
|
69
|
-
.strip()
|
|
70
|
-
|
|
71
|
-
function isHeroSection(section: CmsSectionDto): boolean {
|
|
72
|
-
return HERO_SECTION_TYPES.has(section.type.toLowerCase())
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export function findHeroSection(
|
|
76
|
-
page?: Pick<CmsPageDetailsDto, 'sections'> | null,
|
|
77
|
-
): CmsSectionDto | null {
|
|
78
|
-
return page?.sections.find(isHeroSection) ?? null
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function heroHeightPx(
|
|
82
|
-
sectionSettings: z.infer<typeof HeroSectionSettingsSchema> | null,
|
|
83
|
-
height?: number,
|
|
84
|
-
): number | undefined {
|
|
85
|
-
if (typeof height === 'number' && height > 0) return height
|
|
86
|
-
return sectionSettings?.height
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function slidesFromSection(
|
|
90
|
-
section: CmsSectionDto,
|
|
91
|
-
sectionSettings: z.infer<typeof HeroSectionSettingsSchema>,
|
|
92
|
-
fallbacks: NonNullable<HeroProps['fallbacks']>,
|
|
93
|
-
): HeroSlide[] {
|
|
94
|
-
const blocks = [...section.blocks].sort((a, b) => a.order - b.order)
|
|
95
|
-
if (blocks.length === 0) {
|
|
96
|
-
return [
|
|
97
|
-
{
|
|
98
|
-
id: section.id,
|
|
99
|
-
title: sectionSettings.title ?? fallbacks.title ?? 'Nueva colección',
|
|
100
|
-
subtitle: sectionSettings.subtitle ?? fallbacks.subtitle,
|
|
101
|
-
description: sectionSettings.description,
|
|
102
|
-
ctaText: sectionSettings.ctaText ?? fallbacks.ctaText ?? 'Ver colección',
|
|
103
|
-
ctaUrl: sectionSettings.ctaUrl ?? fallbacks.ctaUrl ?? '/collections/all',
|
|
104
|
-
imageUrl: sectionSettings.imageUrl,
|
|
105
|
-
},
|
|
106
|
-
]
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const slides: HeroSlide[] = []
|
|
110
|
-
for (const block of blocks) {
|
|
111
|
-
if (!HERO_BLOCK_TYPES.has(block.type.toLowerCase())) continue
|
|
112
|
-
|
|
113
|
-
const parsed = HeroBlockSettingsSchema.safeParse(block.settings)
|
|
114
|
-
if (!parsed.success) continue
|
|
115
|
-
|
|
116
|
-
slides.push({
|
|
117
|
-
id: block.id,
|
|
118
|
-
title: parsed.data.title,
|
|
119
|
-
subtitle: parsed.data.subtitle,
|
|
120
|
-
highlight: parsed.data.highlight,
|
|
121
|
-
description: parsed.data.description ?? sectionSettings.subtitle,
|
|
122
|
-
ctaText: parsed.data.ctaText ?? fallbacks.ctaText ?? 'Ver colección',
|
|
123
|
-
ctaUrl: parsed.data.ctaUrl ?? fallbacks.ctaUrl ?? '/collections/all',
|
|
124
|
-
imageUrl: parsed.data.imageUrl,
|
|
125
|
-
})
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
return slides
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function resolveCmsHero(
|
|
132
|
-
section: CmsSectionDto,
|
|
133
|
-
fallbacks: NonNullable<HeroProps['fallbacks']>,
|
|
134
|
-
): {
|
|
135
|
-
settings: z.infer<typeof HeroSectionSettingsSchema>
|
|
136
|
-
slides: HeroSlide[]
|
|
137
|
-
} | null {
|
|
138
|
-
const parsed = HeroSectionSettingsSchema.safeParse(section.settings)
|
|
139
|
-
if (!parsed.success || parsed.data.enabled === false) return null
|
|
140
|
-
|
|
141
|
-
return {
|
|
142
|
-
settings: parsed.data,
|
|
143
|
-
slides: slidesFromSection(section, parsed.data, fallbacks),
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function selectedCmsSection(props: HeroProps): CmsSectionDto | null {
|
|
148
|
-
if (props.section) {
|
|
149
|
-
if (isHeroSection(props.section)) return props.section
|
|
150
|
-
return null
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
return findHeroSection(props.page)
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
function resolveSlides(props: HeroProps): { slides: HeroSlide[]; heightPx?: number } {
|
|
157
|
-
if (props.slides?.length) {
|
|
158
|
-
let sectionSettings: z.infer<typeof HeroSectionSettingsSchema> | null = null
|
|
159
|
-
if (props.section) {
|
|
160
|
-
const parsed = HeroSectionSettingsSchema.safeParse(props.section.settings)
|
|
161
|
-
if (parsed.success) sectionSettings = parsed.data
|
|
162
|
-
}
|
|
163
|
-
return {
|
|
164
|
-
slides: props.slides,
|
|
165
|
-
heightPx: heroHeightPx(sectionSettings, props.height),
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
const section = selectedCmsSection(props)
|
|
170
|
-
if (!section) return { slides: [], heightPx: props.height }
|
|
171
|
-
|
|
172
|
-
const resolved = resolveCmsHero(section, props.fallbacks ?? {})
|
|
173
|
-
if (!resolved) return { slides: [], heightPx: props.height }
|
|
174
|
-
|
|
175
|
-
return {
|
|
176
|
-
slides: resolved.slides,
|
|
177
|
-
heightPx: heroHeightPx(resolved.settings, props.height),
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
function HeroSlideContent({
|
|
182
|
-
slide,
|
|
183
|
-
priorityImage,
|
|
184
|
-
heightPx,
|
|
185
|
-
}: {
|
|
186
|
-
slide: HeroSlide
|
|
187
|
-
priorityImage?: boolean
|
|
188
|
-
heightPx?: number
|
|
189
|
-
}) {
|
|
190
|
-
const [failedImage, setFailedImage] = useState<string | null>(null)
|
|
191
|
-
const imageRef = useRef<HTMLImageElement>(null)
|
|
192
|
-
const hasImage = Boolean(slide.imageUrl && slide.imageUrl !== failedImage)
|
|
193
|
-
|
|
194
|
-
useEffect(() => {
|
|
195
|
-
const image = imageRef.current
|
|
196
|
-
if (slide.imageUrl && image?.complete && image.naturalWidth === 0) {
|
|
197
|
-
setFailedImage(slide.imageUrl)
|
|
198
|
-
}
|
|
199
|
-
}, [slide.imageUrl])
|
|
200
|
-
|
|
201
|
-
return (
|
|
202
|
-
<div
|
|
203
|
-
className={cn(
|
|
204
|
-
'relative w-full overflow-hidden',
|
|
205
|
-
!heightPx && 'min-h-[min(70vh,520px)] md:aspect-[21/9] md:min-h-0',
|
|
206
|
-
)}
|
|
207
|
-
style={heightPx ? { height: `${heightPx}px` } : undefined}
|
|
208
|
-
>
|
|
209
|
-
{hasImage ? (
|
|
210
|
-
<img
|
|
211
|
-
ref={imageRef}
|
|
212
|
-
src={slide.imageUrl}
|
|
213
|
-
alt=""
|
|
214
|
-
aria-hidden
|
|
215
|
-
decoding="async"
|
|
216
|
-
fetchPriority={priorityImage ? 'high' : 'auto'}
|
|
217
|
-
loading={priorityImage ? 'eager' : 'lazy'}
|
|
218
|
-
onError={() => setFailedImage(slide.imageUrl ?? null)}
|
|
219
|
-
className="pointer-events-none absolute inset-0 h-full w-full object-cover object-center"
|
|
220
|
-
/>
|
|
221
|
-
) : (
|
|
222
|
-
<div className="absolute inset-0 bg-muted" aria-hidden />
|
|
223
|
-
)}
|
|
224
|
-
|
|
225
|
-
<div
|
|
226
|
-
className="absolute inset-0 bg-linear-to-r from-black/80 via-black/45 to-black/10"
|
|
227
|
-
aria-hidden
|
|
228
|
-
/>
|
|
229
|
-
|
|
230
|
-
<div className="relative mx-auto flex h-full min-h-[inherit] w-full max-w-6xl items-center px-4 py-12 md:py-16">
|
|
231
|
-
<div className="relative z-10 max-w-xl text-white">
|
|
232
|
-
{slide.subtitle ? (
|
|
233
|
-
<p className="text-xs font-medium uppercase tracking-[0.2em] text-white/70">
|
|
234
|
-
{slide.subtitle}
|
|
235
|
-
</p>
|
|
236
|
-
) : null}
|
|
237
|
-
<h1 className="mt-3 text-4xl font-light leading-tight tracking-tight md:text-5xl lg:text-6xl">
|
|
238
|
-
{slide.title}
|
|
239
|
-
</h1>
|
|
240
|
-
{slide.highlight ? (
|
|
241
|
-
<p className="mt-2 text-2xl font-medium tracking-wide text-primary-foreground md:text-3xl">
|
|
242
|
-
{slide.highlight}
|
|
243
|
-
</p>
|
|
244
|
-
) : null}
|
|
245
|
-
{slide.description ? (
|
|
246
|
-
<p className="mt-4 max-w-lg text-sm leading-relaxed text-white/80 md:text-base">
|
|
247
|
-
{slide.description}
|
|
248
|
-
</p>
|
|
249
|
-
) : null}
|
|
250
|
-
{slide.ctaText && slide.ctaUrl ? (
|
|
251
|
-
<Link to={slide.ctaUrl} className={cn(buttonVariants({ size: 'lg' }), 'mt-8')}>
|
|
252
|
-
{slide.ctaText}
|
|
253
|
-
</Link>
|
|
254
|
-
) : null}
|
|
255
|
-
</div>
|
|
256
|
-
</div>
|
|
257
|
-
</div>
|
|
258
|
-
)
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
/**
|
|
262
|
-
* Hero de tienda con carrusel Embla interno.
|
|
263
|
-
*
|
|
264
|
-
* ```tsx
|
|
265
|
-
* <Hero page={homePage} />
|
|
266
|
-
* <Hero section={heroSection} />
|
|
267
|
-
* <Hero slides={[{ title: 'Hola', ctaText: 'Ver', ctaUrl: '/' }]} />
|
|
268
|
-
* ```
|
|
269
|
-
*/
|
|
270
|
-
export function Hero(props: HeroProps) {
|
|
271
|
-
const { slides, heightPx } = resolveSlides(props)
|
|
272
|
-
if (!slides.length) return null
|
|
273
|
-
|
|
274
|
-
return (
|
|
275
|
-
<Carousel
|
|
276
|
-
className={cn('bg-foreground', props.className)}
|
|
277
|
-
fade
|
|
278
|
-
autoplay={props.autoplay === undefined ? 6000 : props.autoplay}
|
|
279
|
-
aria-label="Destacados"
|
|
280
|
-
>
|
|
281
|
-
{slides.map((slide, index) => (
|
|
282
|
-
<HeroSlideContent
|
|
283
|
-
key={slide.id ?? index}
|
|
284
|
-
slide={slide}
|
|
285
|
-
priorityImage={index === 0}
|
|
286
|
-
heightPx={heightPx}
|
|
287
|
-
/>
|
|
288
|
-
))}
|
|
289
|
-
</Carousel>
|
|
290
|
-
)
|
|
291
|
-
}
|
package/src/config.d.ts
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
import type { EcomiqThemeConfig } from './index'
|
|
2
|
-
|
|
3
|
-
export interface EcomiqApiConfig {
|
|
4
|
-
baseUrl?: string
|
|
5
|
-
version?: string
|
|
6
|
-
headers?: Record<string, string>
|
|
7
|
-
timeoutMs?: number
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export interface EcomiqCloudConfig {
|
|
11
|
-
/**
|
|
12
|
-
* URL privada del plano de control. En producción debe configurarse de forma
|
|
13
|
-
* explícita; durante `ecomiq dev` el runtime usa http://127.0.0.1:8020.
|
|
14
|
-
*/
|
|
15
|
-
baseUrl?: string
|
|
16
|
-
/**
|
|
17
|
-
* Slug canónico en Cloud. Si se omite, se deriva del primer label de
|
|
18
|
-
* `domain`.
|
|
19
|
-
*/
|
|
20
|
-
storeSlug?: string
|
|
21
|
-
timeoutMs?: number
|
|
22
|
-
/** TTL del cache de la publicación activa. Default: 15 segundos. */
|
|
23
|
-
cacheTtlMs?: number
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export type EcomiqCloudflareRoute =
|
|
27
|
-
| string
|
|
28
|
-
| {
|
|
29
|
-
pattern: string
|
|
30
|
-
zone_id: string
|
|
31
|
-
custom_domain?: boolean
|
|
32
|
-
}
|
|
33
|
-
| {
|
|
34
|
-
pattern: string
|
|
35
|
-
zone_name: string
|
|
36
|
-
custom_domain?: boolean
|
|
37
|
-
}
|
|
38
|
-
| {
|
|
39
|
-
pattern: string
|
|
40
|
-
custom_domain: boolean
|
|
41
|
-
enabled?: boolean
|
|
42
|
-
previews_enabled?: boolean
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export interface EcomiqConfigInput {
|
|
46
|
-
/**
|
|
47
|
-
* Identidad de tienda para `x-Store-Domain`.
|
|
48
|
-
* Acepta `marketplace`, `marketplace.ecomiq.app` o `marketplace.pe`.
|
|
49
|
-
*/
|
|
50
|
-
domain: string
|
|
51
|
-
api?: EcomiqApiConfig
|
|
52
|
-
/** Configuración server-only para resolver la publicación activa. */
|
|
53
|
-
cloud?: EcomiqCloudConfig
|
|
54
|
-
theme?: EcomiqThemeConfig
|
|
55
|
-
dev?: {
|
|
56
|
-
port?: number
|
|
57
|
-
apiBaseUrl?: string
|
|
58
|
-
/** Default `node`. `workerd` emula Cloudflare en local. */
|
|
59
|
-
runtime?: 'workerd' | 'node'
|
|
60
|
-
/**
|
|
61
|
-
* HTTPS en localhost. `true` = cert auto; `{ cert, key }` = PEM propios.
|
|
62
|
-
*/
|
|
63
|
-
https?: boolean | { cert: string; key: string }
|
|
64
|
-
}
|
|
65
|
-
deploy?: {
|
|
66
|
-
target?: 'cloudflare'
|
|
67
|
-
name?: string
|
|
68
|
-
routes?: EcomiqCloudflareRoute[]
|
|
69
|
-
}
|
|
70
|
-
env?: Record<string, string>
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export interface ResolvedEcomiqConfig
|
|
74
|
-
extends Omit<EcomiqConfigInput, 'api' | 'cloud'> {
|
|
75
|
-
api: {
|
|
76
|
-
baseUrl: string
|
|
77
|
-
version: string
|
|
78
|
-
headers?: Record<string, string>
|
|
79
|
-
timeoutMs: number
|
|
80
|
-
}
|
|
81
|
-
cloud?: {
|
|
82
|
-
baseUrl?: string
|
|
83
|
-
storeSlug?: string
|
|
84
|
-
timeoutMs: number
|
|
85
|
-
cacheTtlMs: number
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export function parseConfig(
|
|
90
|
-
config: unknown,
|
|
91
|
-
options?: { source?: string },
|
|
92
|
-
): ResolvedEcomiqConfig
|
|
93
|
-
|
|
94
|
-
export function defineConfig(config: EcomiqConfigInput): ResolvedEcomiqConfig
|
package/src/config.mjs
DELETED
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Configuración base de un proyecto Ecomiq (ecomiq.config.js).
|
|
3
|
-
* Se valida con Zod al arrancar la CLI para dar errores claros.
|
|
4
|
-
*
|
|
5
|
-
* Identidad de tienda, SEO, analytics y menús salen de `/v1/settings`.
|
|
6
|
-
* Aquí solo queda lo operativo: domain, api, cloud, theme, dev, deploy, env.
|
|
7
|
-
*/
|
|
8
|
-
import { z } from 'zod'
|
|
9
|
-
|
|
10
|
-
const cloudflareRouteSchema = z.union([
|
|
11
|
-
z.string().min(1),
|
|
12
|
-
z
|
|
13
|
-
.object({
|
|
14
|
-
pattern: z.string().min(1),
|
|
15
|
-
zone_id: z.string().min(1),
|
|
16
|
-
custom_domain: z.boolean().optional(),
|
|
17
|
-
})
|
|
18
|
-
.strict(),
|
|
19
|
-
z
|
|
20
|
-
.object({
|
|
21
|
-
pattern: z.string().min(1),
|
|
22
|
-
zone_name: z.string().min(1),
|
|
23
|
-
custom_domain: z.boolean().optional(),
|
|
24
|
-
})
|
|
25
|
-
.strict(),
|
|
26
|
-
z
|
|
27
|
-
.object({
|
|
28
|
-
pattern: z.string().min(1),
|
|
29
|
-
custom_domain: z.boolean(),
|
|
30
|
-
enabled: z.boolean().optional(),
|
|
31
|
-
previews_enabled: z.boolean().optional(),
|
|
32
|
-
})
|
|
33
|
-
.strict(),
|
|
34
|
-
])
|
|
35
|
-
|
|
36
|
-
const configSchema = z.object({
|
|
37
|
-
/**
|
|
38
|
-
* Identidad de la tienda en el API storefront (header x-Store-Domain).
|
|
39
|
-
* Acepta slug corto o dominio: `marketplace`, `marketplace.ecomiq.app`, `marketplace.pe`.
|
|
40
|
-
*/
|
|
41
|
-
domain: z.string().min(1),
|
|
42
|
-
/** API de storefront. baseUrl default: https://storefront.ecomiq.pe */
|
|
43
|
-
api: z
|
|
44
|
-
.object({
|
|
45
|
-
baseUrl: z.string().url().default('https://storefront.ecomiq.pe'),
|
|
46
|
-
/** Segmento de path de la API, ej. 'v1' → {baseUrl}/v1/settings */
|
|
47
|
-
version: z.string().min(1).default('v1'),
|
|
48
|
-
headers: z.record(z.string()).optional(),
|
|
49
|
-
/** Timeout en ms para cada request. */
|
|
50
|
-
timeoutMs: z.number().int().positive().default(10_000),
|
|
51
|
-
})
|
|
52
|
-
.default({}),
|
|
53
|
-
/**
|
|
54
|
-
* Plano de control server-only. `baseUrl` se exige operativamente en
|
|
55
|
-
* producción; si falta, el storefront degrada a la home clásica.
|
|
56
|
-
*/
|
|
57
|
-
cloud: z
|
|
58
|
-
.object({
|
|
59
|
-
baseUrl: z.string().url().optional(),
|
|
60
|
-
artifactBaseUrl: z.string().url().optional(),
|
|
61
|
-
storeSlug: z
|
|
62
|
-
.string()
|
|
63
|
-
.trim()
|
|
64
|
-
.min(2)
|
|
65
|
-
.max(100)
|
|
66
|
-
.regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/)
|
|
67
|
-
.optional(),
|
|
68
|
-
timeoutMs: z.number().int().positive().default(3_000),
|
|
69
|
-
cacheTtlMs: z.number().int().min(0).max(300_000).default(15_000),
|
|
70
|
-
})
|
|
71
|
-
.strict()
|
|
72
|
-
.optional(),
|
|
73
|
-
/** Tokens de tema; sobreescriben las variables CSS del design system. */
|
|
74
|
-
theme: z
|
|
75
|
-
.object({
|
|
76
|
-
primary: z.string().optional(),
|
|
77
|
-
primaryForeground: z.string().optional(),
|
|
78
|
-
background: z.string().optional(),
|
|
79
|
-
foreground: z.string().optional(),
|
|
80
|
-
accent: z.string().optional(),
|
|
81
|
-
radius: z.string().optional(),
|
|
82
|
-
})
|
|
83
|
-
.optional(),
|
|
84
|
-
dev: z
|
|
85
|
-
.object({
|
|
86
|
-
port: z.number().int().default(3000),
|
|
87
|
-
apiBaseUrl: z.string().url(),
|
|
88
|
-
/**
|
|
89
|
-
* Runtime SSR en `ecomiq dev`. Default: `node`.
|
|
90
|
-
* Usa `workerd` para emular Cloudflare Workers en local.
|
|
91
|
-
* `ecomiq build` / `deploy` siempre usan workerd.
|
|
92
|
-
*/
|
|
93
|
-
runtime: z.enum(['workerd', 'node']),
|
|
94
|
-
/**
|
|
95
|
-
* HTTPS en localhost (`ecomiq dev`).
|
|
96
|
-
* `true` → certificado auto (basic-ssl).
|
|
97
|
-
* `{ cert, key }` → PEM propios relativos al proyecto.
|
|
98
|
-
*/
|
|
99
|
-
https: z.union([
|
|
100
|
-
z.boolean(),
|
|
101
|
-
z
|
|
102
|
-
.object({
|
|
103
|
-
cert: z.string().min(1),
|
|
104
|
-
key: z.string().min(1),
|
|
105
|
-
})
|
|
106
|
-
.strict(),
|
|
107
|
-
]),
|
|
108
|
-
})
|
|
109
|
-
.partial()
|
|
110
|
-
.optional(),
|
|
111
|
-
deploy: z
|
|
112
|
-
.object({
|
|
113
|
-
target: z.literal('cloudflare').default('cloudflare'),
|
|
114
|
-
/** Nombre del Worker en Cloudflare. */
|
|
115
|
-
name: z.string().optional(),
|
|
116
|
-
/** Rutas/custom domains del Worker. */
|
|
117
|
-
routes: z.array(cloudflareRouteSchema).optional(),
|
|
118
|
-
})
|
|
119
|
-
.optional(),
|
|
120
|
-
/** Variables expuestas al Worker (no secretas). */
|
|
121
|
-
env: z.record(z.string()).optional(),
|
|
122
|
-
})
|
|
123
|
-
|
|
124
|
-
export function parseConfig(config, options = {}) {
|
|
125
|
-
const source = options.source ?? 'ecomiq.config.js'
|
|
126
|
-
if (
|
|
127
|
-
config === null ||
|
|
128
|
-
typeof config !== 'object' ||
|
|
129
|
-
(Object.getPrototypeOf(config) !== Object.prototype && Object.getPrototypeOf(config) !== null)
|
|
130
|
-
) {
|
|
131
|
-
throw new Error(`${source} inválido:\n · <raíz>: el default export debe ser un objeto plano`)
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const parsed = configSchema.safeParse(config)
|
|
135
|
-
if (!parsed.success) {
|
|
136
|
-
const issues = parsed.error.issues
|
|
137
|
-
.map((issue) => ` · ${issue.path.join('.') || '<raíz>'}: ${issue.message}`)
|
|
138
|
-
.join('\n')
|
|
139
|
-
throw new Error(`${source} inválido:\n${issues}`)
|
|
140
|
-
}
|
|
141
|
-
return parsed.data
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
export function defineConfig(config) {
|
|
145
|
-
return parseConfig(config)
|
|
146
|
-
}
|
package/src/format-money.ts
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
export type MoneyLocale = {
|
|
2
|
-
language: string
|
|
3
|
-
currency: string
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Formatea un monto con `Intl.NumberFormat` (moneda ISO-4217).
|
|
8
|
-
*
|
|
9
|
-
* @example
|
|
10
|
-
* formatMoney(19.9, { language: 'es-PE', currency: 'PEN' })
|
|
11
|
-
* formatMoney(19.9, 'es-PE', 'PEN')
|
|
12
|
-
*/
|
|
13
|
-
export function formatMoney(value: number, locale: MoneyLocale): string
|
|
14
|
-
export function formatMoney(value: number, language: string, currency: string): string
|
|
15
|
-
export function formatMoney(
|
|
16
|
-
value: number,
|
|
17
|
-
languageOrLocale: string | MoneyLocale,
|
|
18
|
-
currency?: string,
|
|
19
|
-
): string {
|
|
20
|
-
const language =
|
|
21
|
-
typeof languageOrLocale === 'string' ? languageOrLocale : languageOrLocale.language
|
|
22
|
-
const currencyCode =
|
|
23
|
-
typeof languageOrLocale === 'string' ? currency! : languageOrLocale.currency
|
|
24
|
-
|
|
25
|
-
return new Intl.NumberFormat(language, {
|
|
26
|
-
style: 'currency',
|
|
27
|
-
currency: currencyCode,
|
|
28
|
-
minimumFractionDigits: 2,
|
|
29
|
-
maximumFractionDigits: 2,
|
|
30
|
-
}).format(value)
|
|
31
|
-
}
|
package/src/health.ts
DELETED
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Health interno del runtime storefront.
|
|
3
|
-
* GET /ping → liveness puro: "ok" 200 (target del probe de vm-manager).
|
|
4
|
-
* GET /__ecomiq/health → proceso up + estado de /v1/settings (sin SSR de páginas).
|
|
5
|
-
*/
|
|
6
|
-
import { getStorefrontClient } from './client'
|
|
7
|
-
|
|
8
|
-
export const ECOMIQ_HEALTH_PATH = '/__ecomiq/health'
|
|
9
|
-
export const ECOMIQ_PING_PATH = '/ping'
|
|
10
|
-
|
|
11
|
-
export type EcomiqHealthSettingsStatus = {
|
|
12
|
-
ok: boolean
|
|
13
|
-
latencyMs: number
|
|
14
|
-
storeId?: string
|
|
15
|
-
domain?: string | null
|
|
16
|
-
error?: string
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export type EcomiqHealthResponse = {
|
|
20
|
-
ok: true
|
|
21
|
-
settings: EcomiqHealthSettingsStatus
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
async function probeSettings(): Promise<EcomiqHealthSettingsStatus> {
|
|
25
|
-
const started = Date.now()
|
|
26
|
-
try {
|
|
27
|
-
const settings = await getStorefrontClient().getSettings()
|
|
28
|
-
return {
|
|
29
|
-
ok: true,
|
|
30
|
-
latencyMs: Date.now() - started,
|
|
31
|
-
storeId: settings.store.id,
|
|
32
|
-
domain: settings.store.domain ?? settings.store.slug,
|
|
33
|
-
}
|
|
34
|
-
} catch (error) {
|
|
35
|
-
return {
|
|
36
|
-
ok: false,
|
|
37
|
-
latencyMs: Date.now() - started,
|
|
38
|
-
error: error instanceof Error ? error.message : String(error),
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Si el request es al health interno, responde JSON.
|
|
45
|
-
* Si no, `null` para que el caller siga con el fetch normal de TanStack.
|
|
46
|
-
*/
|
|
47
|
-
export async function handleEcomiqHealth(
|
|
48
|
-
request: Request,
|
|
49
|
-
): Promise<Response | null> {
|
|
50
|
-
const { pathname } = new URL(request.url)
|
|
51
|
-
if (pathname !== ECOMIQ_HEALTH_PATH && pathname !== ECOMIQ_PING_PATH) {
|
|
52
|
-
return null
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (request.method !== 'GET' && request.method !== 'HEAD') {
|
|
56
|
-
return new Response(null, {
|
|
57
|
-
status: 405,
|
|
58
|
-
headers: { allow: 'GET, HEAD' },
|
|
59
|
-
})
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if (pathname === ECOMIQ_PING_PATH) {
|
|
63
|
-
return new Response(request.method === 'HEAD' ? null : 'ok', {
|
|
64
|
-
status: 200,
|
|
65
|
-
headers: {
|
|
66
|
-
'content-type': 'text/plain; charset=utf-8',
|
|
67
|
-
'cache-control': 'no-store',
|
|
68
|
-
},
|
|
69
|
-
})
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const body: EcomiqHealthResponse = {
|
|
73
|
-
ok: true,
|
|
74
|
-
settings: await probeSettings(),
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if (request.method === 'HEAD') {
|
|
78
|
-
return new Response(null, {
|
|
79
|
-
status: 200,
|
|
80
|
-
headers: { 'content-type': 'application/json; charset=utf-8' },
|
|
81
|
-
})
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return Response.json(body, {
|
|
85
|
-
status: 200,
|
|
86
|
-
headers: {
|
|
87
|
-
'cache-control': 'no-store',
|
|
88
|
-
},
|
|
89
|
-
})
|
|
90
|
-
}
|