@ecomiq/storefront 1.1.5 → 2.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 (134) hide show
  1. package/README.md +26 -417
  2. package/bin/ecomiq.mjs +154 -0
  3. package/package.json +72 -84
  4. package/src/api-proxy.ts +194 -0
  5. package/src/auth/create-storefront-auth.ts +35 -0
  6. package/src/auth/ecomiq-oauth-password-client.ts +26 -0
  7. package/src/auth/ecomiq-oauth-password-constants.ts +26 -0
  8. package/src/auth/ecomiq-oauth-password-plugin.ts +299 -0
  9. package/src/auth/ecomiq-oauth-password-types.ts +25 -0
  10. package/src/auth/ecomiq-storefront-sign-in.ts +47 -0
  11. package/src/auth/env.ts +7 -0
  12. package/src/cart-session.ts +241 -0
  13. package/src/client.ts +48 -0
  14. package/src/codegen.mjs +603 -0
  15. package/src/components/checkout/cart-sync.ts +143 -0
  16. package/src/components/checkout/checkout-external-update-banner.tsx +15 -0
  17. package/src/components/checkout/checkout-form-controls.tsx +139 -0
  18. package/src/components/checkout/checkout-line-items.tsx +43 -0
  19. package/src/components/checkout/checkout-order-summary.tsx +156 -0
  20. package/src/components/checkout/checkout-payment-section.tsx +215 -0
  21. package/src/components/checkout/checkout-session-view.tsx +240 -0
  22. package/src/components/checkout/checkout-shipping-address-section.tsx +266 -0
  23. package/src/components/checkout/checkout-shipping-section.tsx +90 -0
  24. package/src/components/checkout/checkout-states.tsx +73 -0
  25. package/src/components/checkout/form-schema.ts +229 -0
  26. package/src/components/checkout/format.ts +27 -0
  27. package/src/components/checkout/index.ts +51 -0
  28. package/src/components/checkout/types.ts +43 -0
  29. package/src/components/checkout/use-checkout-geo.ts +50 -0
  30. package/src/components/checkout/use-checkout-session.ts +583 -0
  31. package/src/components/hero.tsx +291 -0
  32. package/src/config.d.ts +69 -0
  33. package/src/config.mjs +165 -0
  34. package/src/index.ts +274 -0
  35. package/src/link.tsx +2 -0
  36. package/src/pages/checkout.$checkoutId.tsx +60 -0
  37. package/src/pages/checkout.tsx +31 -0
  38. package/src/pages/thank-you.tsx +53 -0
  39. package/src/providers/auth.tsx +69 -0
  40. package/src/providers/cart.tsx +275 -0
  41. package/src/providers/ecomiq.ts +137 -0
  42. package/src/providers/index.ts +26 -0
  43. package/src/public-config.ts +44 -0
  44. package/src/review-submission.ts +89 -0
  45. package/src/runtime/context.ts +1 -0
  46. package/src/runtime/default-layout.tsx +80 -0
  47. package/src/runtime/head.ts +126 -0
  48. package/src/runtime/page.tsx +82 -0
  49. package/src/runtime/postcss-browser-stub.mjs +5 -0
  50. package/src/server-config-store.ts +26 -0
  51. package/src/server.ts +279 -0
  52. package/src/styles.css +76 -0
  53. package/src/ui/badge.tsx +32 -0
  54. package/src/ui/button.tsx +31 -0
  55. package/src/ui/card.tsx +31 -0
  56. package/src/ui/carousel.tsx +218 -0
  57. package/src/ui/cn.ts +6 -0
  58. package/src/ui/index.ts +8 -0
  59. package/src/ui/separator.tsx +6 -0
  60. package/src/ui/skeleton.tsx +6 -0
  61. package/src/ui/storefront-error.tsx +49 -0
  62. package/src/utils.ts +36 -0
  63. package/src/vite-inline.mjs +95 -0
  64. package/templates/__root.tsx +77 -0
  65. package/templates/router.tsx +19 -0
  66. package/dist/chunk-3QGB55R6.js +0 -157
  67. package/dist/chunk-3QGB55R6.js.map +0 -1
  68. package/dist/chunk-64GLOWMJ.js +0 -216
  69. package/dist/chunk-64GLOWMJ.js.map +0 -1
  70. package/dist/chunk-ASFU32ZM.js +0 -236
  71. package/dist/chunk-ASFU32ZM.js.map +0 -1
  72. package/dist/chunk-Q4U5OEUT.js +0 -21
  73. package/dist/chunk-Q4U5OEUT.js.map +0 -1
  74. package/dist/chunk-QRR7EPCH.js +0 -176
  75. package/dist/chunk-QRR7EPCH.js.map +0 -1
  76. package/dist/chunk-S7CPZ4UE.js +0 -6
  77. package/dist/chunk-S7CPZ4UE.js.map +0 -1
  78. package/dist/chunk-ZG2XUYGZ.js +0 -1281
  79. package/dist/chunk-ZG2XUYGZ.js.map +0 -1
  80. package/dist/chunks/client.css +0 -984
  81. package/dist/chunks/client.css.map +0 -1
  82. package/dist/chunks/client.d.ts +0 -10041
  83. package/dist/chunks/client.js +0 -5363
  84. package/dist/chunks/client.js.map +0 -1
  85. package/dist/chunks/config.d.ts +0 -31
  86. package/dist/chunks/config.js +0 -5
  87. package/dist/chunks/config.js.map +0 -1
  88. package/dist/chunks/core-public.d.ts +0 -130
  89. package/dist/chunks/core-public.js +0 -92
  90. package/dist/chunks/core-public.js.map +0 -1
  91. package/dist/chunks/server-nextjs.d.ts +0 -33
  92. package/dist/chunks/server-nextjs.js +0 -47
  93. package/dist/chunks/server-nextjs.js.map +0 -1
  94. package/dist/chunks/server-tanstack.d.ts +0 -22
  95. package/dist/chunks/server-tanstack.js +0 -34
  96. package/dist/chunks/server-tanstack.js.map +0 -1
  97. package/dist/collection-C6bWDLiw.d.ts +0 -3926
  98. package/dist/ecomiq-oauth-password-plugin-J5wDFqzL.d.ts +0 -3629
  99. package/dist/ecomiq-shell-ui-SWIFLQVM.js +0 -8
  100. package/dist/ecomiq-shell-ui-SWIFLQVM.js.map +0 -1
  101. package/dist/index.d.ts +0 -9
  102. package/dist/index.js +0 -1
  103. package/dist/index.js.map +0 -1
  104. package/dist/index.server.d.ts +0 -9
  105. package/dist/index.server.js +0 -8
  106. package/dist/index.server.js.map +0 -1
  107. package/dist/nextjs/api.d.ts +0 -45
  108. package/dist/nextjs/api.js +0 -32
  109. package/dist/nextjs/api.js.map +0 -1
  110. package/dist/nextjs/globals.css +0 -13
  111. package/dist/nextjs.d.ts +0 -2
  112. package/dist/nextjs.js +0 -2
  113. package/dist/nextjs.js.map +0 -1
  114. package/dist/nextjs.server.d.ts +0 -1
  115. package/dist/nextjs.server.js +0 -1
  116. package/dist/nextjs.server.js.map +0 -1
  117. package/dist/postcss.config.mjs +0 -6
  118. package/dist/react/checkout-page.css +0 -855
  119. package/dist/react/client.css +0 -984
  120. package/dist/react/payment-provider.css +0 -91
  121. package/dist/react/thank-you-page.css +0 -105
  122. package/dist/react.d.ts +0 -1
  123. package/dist/react.js +0 -2
  124. package/dist/react.js.map +0 -1
  125. package/dist/store-proxy-handler-CNzKlTFA.d.ts +0 -10
  126. package/dist/tanstack/api.d.ts +0 -41
  127. package/dist/tanstack/api.js +0 -30
  128. package/dist/tanstack/api.js.map +0 -1
  129. package/dist/tanstack.d.ts +0 -2
  130. package/dist/tanstack.js +0 -2
  131. package/dist/tanstack.js.map +0 -1
  132. package/dist/tanstack.server.d.ts +0 -1
  133. package/dist/tanstack.server.js +0 -1
  134. package/dist/tanstack.server.js.map +0 -1
package/src/index.ts ADDED
@@ -0,0 +1,274 @@
1
+ /**
2
+ * API pública de @ecomiq/storefront.
3
+ * Todo lo que el usuario importa sale de aquí o de '@ecomiq/storefront/ui'.
4
+ * Las herramientas internas (router, bundler, SSR) no se exponen.
5
+ */
6
+ import type { ReactNode } from 'react'
7
+ import type { StorefrontSettings } from '@ecomiq/core'
8
+ import type { PublicEcomiqConfig } from './public-config'
9
+
10
+ export type {
11
+ EcomiqAnalyticsConfig,
12
+ EcomiqConfig,
13
+ EcomiqLayoutConfig,
14
+ EcomiqSeoConfig,
15
+ EcomiqThemeConfig,
16
+ PublicEcomiqConfig,
17
+ } from './public-config'
18
+
19
+ export {
20
+ createApi,
21
+ createStorefrontClient,
22
+ ApiError,
23
+ DEFAULT_API_BASE_URL,
24
+ DEFAULT_API_VERSION,
25
+ } from '@ecomiq/core'
26
+ export type { Api, CreateApiOptions, StorefrontClient } from '@ecomiq/core'
27
+ export type { CreateApiOptions as CreateClientOptions } from '@ecomiq/core'
28
+ export {
29
+ CollectionSchema,
30
+ CollectionsSchema,
31
+ CountrySchema,
32
+ CountriesSchema,
33
+ DepartmentSchema,
34
+ DepartmentsSchema,
35
+ DistrictSchema,
36
+ DistrictsSchema,
37
+ GeoDivisionSchema,
38
+ GeoDivisionTypeSchema,
39
+ GetDepartmentsParamsSchema,
40
+ GetDistrictsParamsSchema,
41
+ GetProvincesParamsSchema,
42
+ ProvinceSchema,
43
+ ProvincesSchema,
44
+ StorefrontSettingsSchema,
45
+ StorefrontMenuSchema,
46
+ StorefrontMenuItemSchema,
47
+ getMenuItems,
48
+ } from '@ecomiq/core'
49
+ export { getCountries, getDepartments, getDistricts, getProvinces } from '@ecomiq/core'
50
+ export {
51
+ AddCartItemParamsSchema,
52
+ CreateCartParamsSchema,
53
+ CreateProductReviewParamsSchema,
54
+ GetCartParamsSchema,
55
+ ListProductReviewsParamsSchema,
56
+ ProductDetailParamsSchema,
57
+ ProductDetailSchema,
58
+ ProductAutocompleteParamsSchema,
59
+ ProductSuggestionSchema,
60
+ ProductSuggestionsSchema,
61
+ ProductSummarySchema,
62
+ ProductsParamsSchema,
63
+ ProductsResponseSchema,
64
+ RelatedProductsParamsSchema,
65
+ RemoveCartItemParamsSchema,
66
+ StorefrontCartSchema,
67
+ StorefrontReviewSchema,
68
+ StorefrontReviewsSchema,
69
+ CartCheckoutReadParamsSchema,
70
+ CartCheckoutParamsSchema,
71
+ GetShippingOptionsParamsSchema,
72
+ PutShippingMethodParamsSchema,
73
+ CartBuyerParamsSchema,
74
+ CartAddressesParamsSchema,
75
+ CartPromotionCodeParamsSchema,
76
+ UpdateCartItemQuantityParamsSchema,
77
+ cartVariantIdFromProduct,
78
+ sanitizeHtml,
79
+ stripHtml,
80
+ } from '@ecomiq/core'
81
+ export type {
82
+ Collection,
83
+ Collections,
84
+ Countries,
85
+ Country,
86
+ Department,
87
+ Departments,
88
+ District,
89
+ Districts,
90
+ GeoDivision,
91
+ GeoDivisionType,
92
+ GetDepartmentsParams,
93
+ GetDistrictsParams,
94
+ GetProvincesParams,
95
+ Province,
96
+ Provinces,
97
+ StorefrontSettings,
98
+ StorefrontMenu,
99
+ StorefrontMenuItem,
100
+ AddCartItemParams,
101
+ BundleItem,
102
+ CreateCartParams,
103
+ CreateProductReviewParams,
104
+ GetCartParams,
105
+ ListProductReviewsParams,
106
+ ProductDetail,
107
+ ProductDetailParams,
108
+ Product,
109
+ ProductAutocompleteParams,
110
+ ProductImage,
111
+ ProductPriceTier,
112
+ ProductSuggestion,
113
+ ProductSummary,
114
+ ProductsParams,
115
+ ProductsParamsInput,
116
+ ProductsResponse,
117
+ RelatedProductsParams,
118
+ RemoveCartItemParams,
119
+ SearchFilter,
120
+ SizeGuide,
121
+ SpecItem,
122
+ StorefrontCart,
123
+ StorefrontCartItem,
124
+ CartCheckoutReadParams,
125
+ CartCheckoutParams,
126
+ CartCheckoutResponse,
127
+ CartPaymentMethod,
128
+ GetShippingOptionsParams,
129
+ PutShippingMethodParams,
130
+ CartBuyerParams,
131
+ CartAddressesParams,
132
+ CartPromotionCodeParams,
133
+ ShippingRate,
134
+ ShippingOptionGroup,
135
+ ShippingOptionsResponse,
136
+ StorefrontOrderBump,
137
+ StorefrontReview,
138
+ StorefrontReviews,
139
+ UpdateCartItemQuantityParams,
140
+ VariantItem,
141
+ } from '@ecomiq/core'
142
+ export { createClient, getStorefrontClient } from './client'
143
+ export type {
144
+ AddProductToCartInput,
145
+ AddProductToCartResult,
146
+ ProductCartMutationResult,
147
+ } from './cart-session'
148
+ export type {
149
+ ProductReviewSubmissionError,
150
+ ProductReviewSubmissionResult,
151
+ } from './review-submission'
152
+ export { serverApi } from './server'
153
+ export { proxyStorefrontRequest } from './api-proxy'
154
+ export {
155
+ AppEcomiqProvider,
156
+ useApiQuery,
157
+ useAppConfig,
158
+ useSettings,
159
+ useMenu,
160
+ useSearchNavigate,
161
+ CartProvider,
162
+ useCart,
163
+ AuthProvider,
164
+ createStorefrontAuthClient,
165
+ useAuth,
166
+ type CartContextValue,
167
+ type CartLoadStatus,
168
+ type CartMutation,
169
+ type StorefrontAuthClientInstance,
170
+ type StorefrontSession,
171
+ } from './providers'
172
+ export { createStorefrontAuth } from './auth/create-storefront-auth'
173
+ export { ecomiqOAuthPassword } from './auth/ecomiq-oauth-password-plugin'
174
+ export { ecomiqOAuthPasswordClient } from './auth/ecomiq-oauth-password-client'
175
+ export { signInWithEcomiqCredentials } from './auth/ecomiq-storefront-sign-in'
176
+ export type { EcomiqSignInCredentials } from './auth/ecomiq-storefront-sign-in'
177
+ export * from './utils'
178
+ export { Link } from './link'
179
+ export { Await } from '@tanstack/react-router'
180
+ export { Hero, findHeroSection, type HeroProps, type HeroSlide } from './components/hero'
181
+ export { z } from 'zod'
182
+
183
+ /**
184
+ * Contexto del loader. Por defecto el loader es isomorfo (SSR + cliente).
185
+ * Preferí `serverApi` para que el fetch a storefront solo corra en el servidor.
186
+ */
187
+ export interface LoaderContext<
188
+ TParams = Record<string, string>,
189
+ TSearch extends Record<string, unknown> = Record<string, unknown>,
190
+ > {
191
+ params: TParams
192
+ search: TSearch
193
+ config: PublicEcomiqConfig
194
+ }
195
+
196
+ /** Props que recibe el componente de una página. */
197
+ export interface PageProps<
198
+ TData = unknown,
199
+ TParams = Record<string, string>,
200
+ TSearch extends Record<string, unknown> = Record<string, unknown>,
201
+ > {
202
+ data: TData
203
+ params: TParams
204
+ search: TSearch
205
+ }
206
+
207
+ export interface PageMeta {
208
+ title?: string
209
+ description?: string
210
+ /** Imagen OG específica de esta página (sobreescribe seo.ogImage). */
211
+ ogImage?: string
212
+ /** Marca la página como noindex,nofollow. */
213
+ noIndex?: boolean
214
+ /** Metas extra solo para esta página. */
215
+ meta?: Array<{ name?: string; property?: string; content: string }>
216
+ /** Datos estructurados (schema.org); se serializa como JSON-LD. */
217
+ jsonLd?: Record<string, unknown>
218
+ }
219
+
220
+ /** Props que recibe un layout personalizado (layout.tsx en la raíz del proyecto). */
221
+ export interface LayoutProps {
222
+ children: ReactNode
223
+ config: PublicEcomiqConfig
224
+ /** Settings storefront (CMS, store, menus). Cargado en el root para todas las páginas. */
225
+ settings: StorefrontSettings
226
+ }
227
+
228
+ /**
229
+ * Define el layout global de la tienda (opcional).
230
+ * Crea `layout.tsx` en la raíz del proyecto y el framework lo usa en lugar
231
+ * del layout por defecto. Recibe `children` (la página) y `config`.
232
+ */
233
+ export function defineLayout(layout: (props: LayoutProps) => ReactNode) {
234
+ return layout
235
+ }
236
+
237
+ export interface EcomiqPage<
238
+ TData = unknown,
239
+ TParams = Record<string, string>,
240
+ TSearch extends Record<string, unknown> = Record<string, unknown>,
241
+ > {
242
+ preload?: boolean
243
+ reloadOnSearch?: boolean
244
+ validateSearch?: (raw: Record<string, unknown>) => TSearch
245
+ /**
246
+ * Carga de datos. Corre en SSR y en navegaciones cliente.
247
+ * Usa `serverApi` (no `createClient`) para que el HTTP a storefront
248
+ * solo ocurra en el servidor.
249
+ */
250
+ loader?: (ctx: LoaderContext<TParams, TSearch>) => Promise<TData> | TData
251
+ /** Metadatos del documento; recibe los datos ya cargados. */
252
+ meta?: (ctx: {
253
+ data: TData
254
+ params: TParams
255
+ search: TSearch
256
+ config: PublicEcomiqConfig
257
+ }) => PageMeta
258
+ /** UI de la página. */
259
+ component: (props: PageProps<TData, TParams, TSearch>) => ReactNode
260
+ /** UI mientras el loader está en curso (navegación cliente). */
261
+ loading?: () => ReactNode
262
+ /** UI cuando el loader falla. */
263
+ error?: (props: { error: Error; reset: () => void }) => ReactNode
264
+ }
265
+
266
+ export function definePage<
267
+ TData = unknown,
268
+ TParams = Record<string, string>,
269
+ TSearch extends Record<string, unknown> = Record<string, unknown>,
270
+ >(
271
+ page: EcomiqPage<TData, TParams, TSearch>,
272
+ ): EcomiqPage<TData, TParams, TSearch> {
273
+ return page
274
+ }
package/src/link.tsx ADDED
@@ -0,0 +1,2 @@
1
+ export { Link } from '@tanstack/react-router'
2
+ export type { LinkProps } from '@tanstack/react-router'
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Checkout cerrado de Ecomiq (`/checkout/$checkoutId`).
3
+ * Los templates heredan la ruta vía codegen; no se puede overridear desde `pages/`.
4
+ * `/checkout` sin ID redirige a `/cart` (ver `checkout.tsx`).
5
+ */
6
+ import { useEffect } from 'react'
7
+ import { CheckoutSessionView } from '../components/checkout'
8
+ import { definePage } from '../index'
9
+ import { useAuth, useSearchNavigate, useSettings } from '../providers'
10
+
11
+ export default definePage<undefined, { checkoutId: string }>({
12
+ meta: () => ({
13
+ title: 'Checkout',
14
+ description: 'Confirma tu pedido y completa la compra',
15
+ noIndex: true,
16
+ }),
17
+ component: CheckoutPage,
18
+ })
19
+
20
+ function CheckoutPage({ params }: { params: { checkoutId: string } }) {
21
+ const checkoutId = params.checkoutId?.trim() ?? ''
22
+ const { store } = useSettings()
23
+ const allowGuest = store.checkoutSettings.general.allowGuestCheckout
24
+ const { session, isPending: authPending } = useAuth()
25
+ const navigate = useSearchNavigate()
26
+ const isAuthenticated = Boolean(session?.user)
27
+ const needsAuth = !allowGuest && !authPending && !isAuthenticated && Boolean(checkoutId)
28
+
29
+ useEffect(() => {
30
+ if (checkoutId) return
31
+ void navigate({ to: '/cart', replace: true })
32
+ }, [checkoutId, navigate])
33
+
34
+ useEffect(() => {
35
+ if (!needsAuth || !checkoutId) return
36
+ void navigate({
37
+ to: '/account/login',
38
+ search: { redirect: `/checkout/${checkoutId}` },
39
+ replace: true,
40
+ })
41
+ }, [needsAuth, navigate, checkoutId])
42
+
43
+ if (!checkoutId) {
44
+ return (
45
+ <div className="mx-auto w-full max-w-6xl px-4 py-16 text-center text-sm text-muted-foreground">
46
+ Redirigiendo al carrito…
47
+ </div>
48
+ )
49
+ }
50
+
51
+ if (authPending || needsAuth) {
52
+ return (
53
+ <div className="mx-auto w-full max-w-6xl px-4 py-16 text-center text-sm text-muted-foreground">
54
+ {needsAuth ? 'Redirigiendo para iniciar sesión…' : 'Cargando…'}
55
+ </div>
56
+ )
57
+ }
58
+
59
+ return <CheckoutSessionView cartId={checkoutId} />
60
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Checkout cerrado de Ecomiq (`/checkout` / `/checkout/`).
3
+ * Sin ID → redirige al carrito. El flujo vive en `/checkout/$checkoutId`
4
+ * (ruta hermana, no hija: codegen usa `checkout.index.tsx`).
5
+ */
6
+ import { useEffect } from 'react'
7
+ import { definePage } from '../index'
8
+ import { useSearchNavigate } from '../providers'
9
+
10
+ export default definePage({
11
+ meta: () => ({
12
+ title: 'Checkout',
13
+ description: 'Confirma tu pedido y completa la compra',
14
+ noIndex: true,
15
+ }),
16
+ component: CheckoutRedirectPage,
17
+ })
18
+
19
+ function CheckoutRedirectPage() {
20
+ const navigate = useSearchNavigate()
21
+
22
+ useEffect(() => {
23
+ void navigate({ to: '/cart', replace: true })
24
+ }, [navigate])
25
+
26
+ return (
27
+ <div className="mx-auto w-full max-w-6xl px-4 py-16 text-center text-sm text-muted-foreground">
28
+ Redirigiendo al carrito…
29
+ </div>
30
+ )
31
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Página de agradecimiento cerrada (`/thank-you`).
3
+ * Tras un checkout exitoso (sin redirect del provider).
4
+ */
5
+ import { CheckCircle2 } from 'lucide-react'
6
+ import { z } from 'zod'
7
+ import { definePage, Link } from '../index'
8
+ import { buttonVariants, cn } from '../ui'
9
+
10
+ const ThankYouSearchSchema = z.object({
11
+ cartId: z.string().optional(),
12
+ transactionId: z.string().optional(),
13
+ })
14
+
15
+ type ThankYouSearch = z.infer<typeof ThankYouSearchSchema>
16
+
17
+ export default definePage<undefined, Record<string, never>, ThankYouSearch>({
18
+ meta: () => ({
19
+ title: 'Gracias por tu compra',
20
+ description: 'Tu pedido fue recibido correctamente',
21
+ noIndex: true,
22
+ }),
23
+ validateSearch: (raw) => ThankYouSearchSchema.parse(raw),
24
+ component: ThankYouPage,
25
+ })
26
+
27
+ function ThankYouPage({ search }: { search: ThankYouSearch }) {
28
+ return (
29
+ <section className="mx-auto flex min-h-[55vh] w-full max-w-lg flex-col items-center justify-center px-4 py-16 text-center">
30
+ <div className="mb-5 flex size-14 items-center justify-center rounded-full bg-emerald-500/10 text-emerald-600">
31
+ <CheckCircle2 className="size-7" aria-hidden="true" />
32
+ </div>
33
+ <h1 className="text-3xl font-semibold tracking-tight">¡Gracias por tu compra!</h1>
34
+ <p className="mt-3 text-sm leading-6 text-muted-foreground">
35
+ Recibimos tu pedido y lo estamos procesando. Te enviaremos una confirmación por
36
+ correo cuando esté listo.
37
+ </p>
38
+ {search.transactionId ? (
39
+ <p className="mt-4 rounded-md bg-muted px-3 py-2 font-mono text-xs text-muted-foreground">
40
+ Ref. {search.transactionId}
41
+ </p>
42
+ ) : null}
43
+ <div className="mt-8 flex flex-wrap justify-center gap-3">
44
+ <Link to="/" className={cn(buttonVariants({ size: 'lg' }))}>
45
+ Volver a la tienda
46
+ </Link>
47
+ <Link to="/products" className={cn(buttonVariants({ size: 'lg', variant: 'outline' }))}>
48
+ Seguir comprando
49
+ </Link>
50
+ </div>
51
+ </section>
52
+ )
53
+ }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Auth storefront: Better Auth client + sesión via context.
3
+ *
4
+ * ```tsx
5
+ * const client = createStorefrontAuthClient()
6
+ * <AuthProvider client={client}>
7
+ * <App />
8
+ * </AuthProvider>
9
+ *
10
+ * const { session, signIn, signOut } = useAuth()
11
+ * ```
12
+ */
13
+ import { createContext, useContext, type ReactNode } from 'react'
14
+ import { createAuthClient } from 'better-auth/react'
15
+ import { ecomiqOAuthPasswordClient } from '../auth/ecomiq-oauth-password-client'
16
+ import {
17
+ signInWithEcomiqCredentials,
18
+ type EcomiqSignInCredentials,
19
+ } from '../auth/ecomiq-storefront-sign-in'
20
+ import { useSettings } from './ecomiq'
21
+
22
+ export type CreateStorefrontAuthClientOptions = {
23
+ baseURL?: string
24
+ basePath?: string
25
+ }
26
+
27
+ export function createStorefrontAuthClient(
28
+ options: CreateStorefrontAuthClientOptions = {},
29
+ ) {
30
+ return createAuthClient({
31
+ ...options,
32
+ plugins: [ecomiqOAuthPasswordClient()],
33
+ })
34
+ }
35
+
36
+ export type StorefrontAuthClientInstance = ReturnType<typeof createStorefrontAuthClient>
37
+ /** Sesión tipada de Better Auth (`{ user, session }`). */
38
+ export type StorefrontSession = StorefrontAuthClientInstance['$Infer']['Session']
39
+
40
+ const AuthContext = createContext<StorefrontAuthClientInstance | null>(null)
41
+
42
+ export function AuthProvider({
43
+ client,
44
+ children,
45
+ }: {
46
+ client: StorefrontAuthClientInstance
47
+ children: ReactNode
48
+ }) {
49
+ return <AuthContext.Provider value={client}>{children}</AuthContext.Provider>
50
+ }
51
+
52
+ /** Acceso a sesión y acciones de auth. Requiere `<AuthProvider>` + `<AppEcomiqProvider>`. */
53
+ export function useAuth() {
54
+ const client = useContext(AuthContext)
55
+ if (!client) throw new Error('useAuth debe usarse dentro de AuthProvider')
56
+
57
+ const sessionQuery = client.useSession()
58
+ const settings = useSettings()
59
+
60
+ return {
61
+ client,
62
+ session: sessionQuery.data,
63
+ isPending: sessionQuery.isPending,
64
+ error: sessionQuery.error,
65
+ signIn: (credentials: EcomiqSignInCredentials) =>
66
+ signInWithEcomiqCredentials(client, settings, credentials),
67
+ signOut: () => client.signOut(),
68
+ }
69
+ }