@ecomiq/storefront 0.1.1 → 0.1.2

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/README.md CHANGED
@@ -1,15 +1,15 @@
1
1
  # @ecomiq/storefront
2
2
 
3
- SDK **headless** para construir tu propia tienda con **Next.js** y **React** sobre Ecomiq.
3
+ SDK **headless** para construir tu tienda con **Next.js** y **React** sobre la API de Ecomiq.
4
4
 
5
- Incluye capa de **datos y API**: tipos, schemas Zod, cliente BFF (`/api/store`), hooks y utilidades server. **No** incluye UI precargada (checkout, CMS renderer, temas, `ShellRouter`, etc.): diseñas e implementas la interfaz en tu proyecto.
5
+ Incluye tipos, schemas Zod, cliente BFF (`/api/store`), hooks, `EcomiqProvider` y utilidades server. La interfaz de la tienda la implementas en tu proyecto.
6
6
 
7
7
  ## Requisitos
8
8
 
9
9
  - Node.js 20+
10
10
  - Next.js 15+ (App Router)
11
11
  - React 19+
12
- - Tailwind CSS 4+
12
+ - Tailwind CSS 4+ (opcional)
13
13
 
14
14
  ## Instalación
15
15
 
@@ -28,14 +28,33 @@ const nextConfig = {
28
28
  export default nextConfig;
29
29
  ```
30
30
 
31
+ ## `ecomiq.config.js` (opcional)
32
+
33
+ Puedes añadir en la **raíz de tu app** (junto a `next.config.js`) un archivo `ecomiq.config.js`, `ecomiq.config.cjs` o `ecomiq.config.mjs`:
34
+
35
+ ```js
36
+ // @ts-check
37
+
38
+ /** @type {import('@ecomiq/storefront').EcomiqConfig} */
39
+ const ecomiqConfig = {
40
+ slugStore: "mi-tienda",
41
+ };
42
+
43
+ module.exports = ecomiqConfig;
44
+ ```
45
+
46
+ Si defines `slugStore`, `getPublicStore` usa ese slug como identificador de tienda y no lo infiere del header `Host`. Si no usas el archivo, la tienda se resuelve por el `Host` de la request (por ejemplo subdominio o dominio propio).
47
+
48
+ Cuándo usarlo lo decides tú: en local, staging o producción. Plantilla: [ecomiq.config.example.cjs](./ecomiq.config.example.cjs).
49
+
31
50
  ## Arquitectura
32
51
 
33
52
  ```
34
53
  Navegador → /api/store/* (proxy en tu Next.js) → API Ecomiq
35
- ↑ X-Store-Id (cookie / header tras getPublicStore)
54
+ ↑ X-Store-Id (tras getPublicStore)
36
55
  ```
37
56
 
38
- El SDK **no** llama al upstream desde el browser; todas las peticiones van al proxy que tú montas con `toNextJsStoreApiHandler`.
57
+ El browser no llama al upstream directo; montas el proxy con `toNextJsStoreApiHandler`.
39
58
 
40
59
  ## Inicio rápido
41
60
 
@@ -50,12 +69,11 @@ export const { GET, POST, PUT, PATCH, DELETE } = toNextJsStoreApiHandler({
50
69
  });
51
70
  ```
52
71
 
53
- ### 2. Resolver la tienda (server)
72
+ ### 2. Layout y tienda
54
73
 
55
74
  ```tsx
56
75
  // app/layout.tsx (Server Component)
57
76
  import { getPublicStore, EcomiqProvider } from "@ecomiq/storefront/nextjs";
58
- import { authClient } from "./auth-client";
59
77
 
60
78
  export default async function RootLayout({ children }: { children: React.ReactNode }) {
61
79
  const { data: store, error } = await getPublicStore();
@@ -63,14 +81,14 @@ export default async function RootLayout({ children }: { children: React.ReactNo
63
81
  // tu página de error / tienda no encontrada
64
82
  }
65
83
  return (
66
- <EcomiqProvider store={store} authClient={authClient}>
84
+ <EcomiqProvider store={store}>
67
85
  {children}
68
86
  </EcomiqProvider>
69
87
  );
70
88
  }
71
89
  ```
72
90
 
73
- `EcomiqProvider` es un Client Component: puedes importarlo desde un layout servidor como en el ejemplo. Hidrata la tienda (`useStore`, `setClientStoreId`), React Query y la sesión (`authClient` de Better Auth).
91
+ `EcomiqProvider` hidrata el estado de la tienda (`useStore`, `setClientStoreId`) y React Query. La autenticación es opcional (pasos 5 y 6).
74
92
 
75
93
  ### 3. Catálogo con hooks
76
94
 
@@ -105,67 +123,146 @@ export default async function CatalogPage() {
105
123
  }
106
124
  ```
107
125
 
108
- ### 5. Estilos (opcional)
126
+ ### 5. Autenticación (Better Auth + SDK)
109
127
 
110
- ```css
111
- @import "tailwindcss";
112
- @import "@ecomiq/storefront/nextjs/css";
113
- ```
128
+ Instala [Better Auth](https://www.better-auth.com/) y los plugins del SDK (OAuth password grant con credenciales Ecomiq).
114
129
 
115
- El CSS publicado es **mínimo**. Tu design system y componentes viven en la app.
130
+ **Servidor:**
116
131
 
117
- ## Puntos de entrada (npm)
132
+ ```ts
133
+ // lib/auth.ts
134
+ import { betterAuth } from "better-auth";
135
+ import { ecomiqOAuthPassword } from "@ecomiq/storefront/react";
118
136
 
119
- | Import | Uso |
120
- |--------|-----|
121
- | `@ecomiq/storefront/core` | Tipos, Zod, utilidades, servicios axios legacy |
122
- | `@ecomiq/storefront/react` | `EcomiqProvider`, hooks, stores, auth OAuth, `findStorefrontCmsPageBySlug`, selector de variantes |
123
- | `@ecomiq/storefront/nextjs` | `getPublicStore`, `EcomiqProvider`, cliente BFF, hooks `useStore*` |
124
- | `@ecomiq/storefront/nextjs/api` | Handler del proxy `/api/store/*` |
125
- | `@ecomiq/storefront/nextjs/css` | Tailwind base opcional |
126
- | `@ecomiq/storefront/nextjs/postcss` | PostCSS de referencia |
137
+ export const auth = betterAuth({
138
+ plugins: [ecomiqOAuthPassword()],
139
+ });
140
+ ```
127
141
 
128
- No hay subpath `./client` en npm: las funciones del cliente BFF se importan desde **`/nextjs`**.
142
+ **Ruta API:**
129
143
 
130
- ## Auth (Better Auth)
144
+ ```ts
145
+ // app/api/auth/[...all]/route.ts
146
+ import { toNextJsHandler } from "better-auth/next-js";
147
+ import { auth } from "@/lib/auth";
131
148
 
132
- Plugins para OAuth password grant de Ecomiq:
149
+ export const { GET, POST } = toNextJsHandler(auth);
150
+ ```
151
+
152
+ **Cliente:**
133
153
 
134
154
  ```ts
155
+ // lib/auth-client.ts
156
+ "use client";
157
+
158
+ import { createAuthClient } from "better-auth/react";
159
+ import { ecomiqOAuthPasswordClient } from "@ecomiq/storefront/react";
160
+
161
+ export const authClient = createAuthClient({
162
+ plugins: [ecomiqOAuthPasswordClient()],
163
+ });
164
+ ```
165
+
166
+ **Layout** — añade `authClient` al provider del paso 2:
167
+
168
+ ```tsx
169
+ import { authClient } from "@/lib/auth-client";
170
+
171
+ <EcomiqProvider store={store} authClient={authClient}>
172
+ {children}
173
+ </EcomiqProvider>
174
+ ```
175
+
176
+ ### 6. Login, registro y logout
177
+
178
+ Con el paso 5 configurado:
179
+
180
+ ```tsx
181
+ "use client";
182
+
135
183
  import {
136
- ecomiqOAuthPassword,
137
- ecomiqOAuthPasswordClient,
184
+ useStore,
185
+ useStorefrontAuthClient,
186
+ useStorefrontSession,
138
187
  } from "@ecomiq/storefront/react";
188
+ import {
189
+ registerCustomer,
190
+ signInWithEcomiqCredentials,
191
+ } from "@ecomiq/storefront/nextjs";
192
+
193
+ export function AccountActions() {
194
+ const { data: store } = useStore();
195
+ const authClient = useStorefrontAuthClient();
196
+ const { isAuthenticated, user, signOut, refetch } = useStorefrontSession();
197
+
198
+ async function handleLogin(email: string, password: string) {
199
+ await signInWithEcomiqCredentials(authClient, store, { email, password });
200
+ await refetch();
201
+ }
202
+
203
+ async function handleRegister(input: {
204
+ email: string;
205
+ password: string;
206
+ firstName: string;
207
+ lastName: string;
208
+ countryIsoCode: string;
209
+ phone: string;
210
+ }) {
211
+ await registerCustomer(input);
212
+ await handleLogin(input.email, input.password);
213
+ }
214
+
215
+ if (isAuthenticated) {
216
+ return (
217
+ <div>
218
+ <p>Hola, {user?.firstName ?? user?.email}</p>
219
+ <button type="button" onClick={() => signOut()}>
220
+ Cerrar sesión
221
+ </button>
222
+ </div>
223
+ );
224
+ }
225
+
226
+ return (
227
+ <div>
228
+ {/* Tu UI: formularios que llaman handleLogin / handleRegister */}
229
+ </div>
230
+ );
231
+ }
139
232
  ```
140
233
 
141
- ## Variables de entorno
234
+ - **Login:** `signInWithEcomiqCredentials` (headers de tienda los resuelve el SDK).
235
+ - **Registro:** `registerCustomer`, luego login con las mismas credenciales.
236
+ - **Logout:** `signOut()` de `useStorefrontSession`.
142
237
 
143
- | Variable | Descripción |
144
- |----------|-------------|
145
- | `STOREFRONT_API_URL` | URL base de la API de tienda (proxy) |
146
- | `PUBLIC_STORE_SETTINGS_SOURCE` | `api` (default) o `kv` (OpenNext / Cloudflare) |
238
+ ## Puntos de entrada (npm)
147
239
 
148
- Tras `getPublicStore`, el SDK fija `X-Store-Id` en el servidor para las llamadas al proxy.
240
+ | Import | Uso |
241
+ |--------|-----|
242
+ | `@ecomiq/storefront` | `EcomiqConfig`, `loadEcomiqConfig` (tipado de `ecomiq.config.js`) |
243
+ | `@ecomiq/storefront/core` | Tipos, Zod, utilidades |
244
+ | `@ecomiq/storefront/react` | `EcomiqProvider`, hooks, sesión (`useStorefrontSession`), plugins Better Auth |
245
+ | `@ecomiq/storefront/nextjs` | `getPublicStore`, cliente BFF, `registerCustomer`, `signInWithEcomiqCredentials`, hooks `useStore*` |
246
+ | `@ecomiq/storefront/nextjs/api` | Proxy `/api/store/*` |
149
247
 
150
- ## Qué no viene en el paquete npm
248
+ El cliente BFF se importa desde **`/nextjs`** (no hay subpath `./client` en npm).
151
249
 
152
- Pensado para que **tú** construyas la UI:
250
+ ## Variables de entorno
153
251
 
154
- - `ShellRouter`, páginas exportadas
155
- - Checkout, carrito drawer, cuenta, formularios precargados
156
- - `CmsSectionsRenderer`, `DefaultCmsSectionsRenderer`, `SafeThemeInjector`
157
- - `ThemeComponentLoader` y temas `default` / `ana` / `sony`
158
- - Dependencia de `@ecomiq/ui`
252
+ | Variable | Descripción |
253
+ |----------|-------------|
254
+ | `STOREFRONT_API_URL` | URL base de la API de tienda (proxy) |
255
+ | `PUBLIC_STORE_SETTINGS_SOURCE` | `api` (default) o `kv` (despliegues con KV) |
159
256
 
160
- Esa capa completa existe en el **monorepo Ecomiq** (`workspace:*` sobre `src/`), no en el tarball npm.
257
+ Tras `getPublicStore`, el SDK configura el contexto de tienda en el servidor para las llamadas al proxy.
161
258
 
162
- ## Desarrollo en el monorepo Ecomiq
259
+ **Identificador de tienda:** `slugStore` en `ecomiq.config.*` si existe; si no, header `Host`.
163
260
 
164
- Si trabajas dentro del repo `ecomiq`, el paquete se consume por fuente con exports amplios (`./react/components/*`, `./nextjs/export-pages/*`, etc.) y UI con `@ecomiq/ui`.
261
+ ## Publicación del paquete
165
262
 
166
- Para empaquetar o publicar en npm: ver [PUBLISHING.md](./PUBLISHING.md).
263
+ Mantenimiento del tarball npm: [PUBLISHING.md](./PUBLISHING.md).
167
264
 
168
- ## Dependencias recomendadas en tu app
265
+ ## Dependencias recomendadas
169
266
 
170
267
  ```json
171
268
  {
package/dist/..d.ts ADDED
@@ -0,0 +1,30 @@
1
+ import { z } from 'zod';
2
+
3
+ declare const EcomiqConfigSchema: z.ZodObject<{
4
+ /**
5
+ * Slug de tienda en Ecomiq (p. ej. `mi-tienda` para `mi-tienda.ecomiq.app`).
6
+ * Si está definido, tiene prioridad sobre el `Host` de la request (útil en `localhost`).
7
+ */
8
+ slugStore: z.ZodOptional<z.ZodString>;
9
+ }, "strip", z.ZodTypeAny, {
10
+ slugStore?: string | undefined;
11
+ }, {
12
+ slugStore?: string | undefined;
13
+ }>;
14
+ type EcomiqConfig = z.infer<typeof EcomiqConfigSchema>;
15
+ /**
16
+ * Carga `ecomiq.config.js` (o `.cjs` / `.mjs`) desde la raíz del proyecto (`process.cwd()`).
17
+ * Resultado en caché por proceso.
18
+ */
19
+ declare function loadEcomiqConfig(cwd?: string): EcomiqConfig | null;
20
+ /** Limpia la caché (tests o recarga en dev). */
21
+ declare function clearEcomiqConfigCache(): void;
22
+ /**
23
+ * Identificador de tienda para settings (API / KV).
24
+ * 1. `slugStore` en `ecomiq.config.*` si existe.
25
+ * 2. Si no, deriva del header `Host` (`resolveStorefrontSettingsHost`).
26
+ */
27
+ declare function resolvePublicStoreKey(hostHeader: string | null | undefined, cwd?: string): string;
28
+ declare function hasEcomiqSlugStoreConfig(cwd?: string): boolean;
29
+
30
+ export { type EcomiqConfig, EcomiqConfigSchema, clearEcomiqConfigCache, hasEcomiqSlugStoreConfig, loadEcomiqConfig, resolvePublicStoreKey };