@ecomiq/storefront 0.1.0 → 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 oficial para construir tiendas online con **Next.js** y **React** sobre la plataforma Ecomiq.
3
+ SDK **headless** para construir tu tienda con **Next.js** y **React** sobre la API de Ecomiq.
4
4
 
5
- Incluye componentes de catálogo, carrito, checkout, cuenta de cliente, CMS, temas y un proxy listo para conectar tu app con la API de tienda.
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
 
@@ -19,8 +19,6 @@ npm install @ecomiq/storefront
19
19
  pnpm add @ecomiq/storefront
20
20
  ```
21
21
 
22
- En `next.config.js` (o `.mjs`), transpila el SDK:
23
-
24
22
  ```js
25
23
  /** @type {import('next').NextConfig} */
26
24
  const nextConfig = {
@@ -30,20 +28,37 @@ const nextConfig = {
30
28
  export default nextConfig;
31
29
  ```
32
30
 
33
- ## Inicio rápido
31
+ ## `ecomiq.config.js` (opcional)
34
32
 
35
- ### 1. Estilos
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`:
36
34
 
37
- En `app/globals.css`:
35
+ ```js
36
+ // @ts-check
37
+
38
+ /** @type {import('@ecomiq/storefront').EcomiqConfig} */
39
+ const ecomiqConfig = {
40
+ slugStore: "mi-tienda",
41
+ };
38
42
 
39
- ```css
40
- @import "tailwindcss";
41
- @import "@ecomiq/storefront/nextjs/css";
43
+ module.exports = ecomiqConfig;
42
44
  ```
43
45
 
44
- ### 2. Proxy de la API de tienda
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).
45
47
 
46
- Crea la ruta que reenvía las peticiones al backend de Ecomiq:
48
+ Cuándo usarlo lo decides tú: en local, staging o producción. Plantilla: [ecomiq.config.example.cjs](./ecomiq.config.example.cjs).
49
+
50
+ ## Arquitectura
51
+
52
+ ```
53
+ Navegador → /api/store/* (proxy en tu Next.js) → API Ecomiq
54
+ ↑ X-Store-Id (tras getPublicStore)
55
+ ```
56
+
57
+ El browser no llama al upstream directo; montas el proxy con `toNextJsStoreApiHandler`.
58
+
59
+ ## Inicio rápido
60
+
61
+ ### 1. Proxy BFF
47
62
 
48
63
  ```ts
49
64
  // app/api/store/[...path]/route.ts
@@ -54,124 +69,214 @@ export const { GET, POST, PUT, PATCH, DELETE } = toNextJsStoreApiHandler({
54
69
  });
55
70
  ```
56
71
 
57
- ### 3. Layout y provider
72
+ ### 2. Layout y tienda
58
73
 
59
74
  ```tsx
60
- // app/(shop)/layout.tsx
61
- import { EcomiqProvider, getPublicStore, ThemeComponentLoader } from "@ecomiq/storefront/nextjs";
62
-
63
- export default async function ShopLayout({ children }: { children: React.ReactNode }) {
64
- const { data } = await getPublicStore();
75
+ // app/layout.tsx (Server Component)
76
+ import { getPublicStore, EcomiqProvider } from "@ecomiq/storefront/nextjs";
65
77
 
78
+ export default async function RootLayout({ children }: { children: React.ReactNode }) {
79
+ const { data: store, error } = await getPublicStore();
80
+ if (error) {
81
+ // tu página de error / tienda no encontrada
82
+ }
66
83
  return (
67
- <EcomiqProvider store={data} authClient={yourAuthClient}>
68
- <ThemeComponentLoader themeName="default" componentName="header" props={{ /* logo, etc. */ }} />
84
+ <EcomiqProvider store={store}>
69
85
  {children}
70
- <ThemeComponentLoader themeName="default" componentName="footer" props={{ /* … */ }} />
71
86
  </EcomiqProvider>
72
87
  );
73
88
  }
74
89
  ```
75
90
 
76
- ### 4. Rutas de la tienda
91
+ `EcomiqProvider` hidrata el estado de la tienda (`useStore`, `setClientStoreId`) y React Query. La autenticación es opcional (pasos 5 y 6).
77
92
 
78
- Usa el enrutador incluido para catálogo, producto, carrito, checkout y cuenta:
93
+ ### 3. Catálogo con hooks
79
94
 
80
95
  ```tsx
81
- // app/(shop)/[[...route]]/page.tsx
82
- import {
83
- ShellRouter,
84
- generateMetadata as generateStoreMetadata,
85
- } from "@ecomiq/storefront/nextjs";
96
+ "use client";
86
97
 
87
- export default function StorePage(props: {
88
- params: Promise<{ route?: string[] }>;
89
- searchParams: Record<string, string | string[] | undefined>;
90
- }) {
91
- return <ShellRouter {...props} getServerSession={yourGetSession} />;
98
+ import { useStoreProducts } from "@ecomiq/storefront/nextjs";
99
+
100
+ export function ProductList() {
101
+ const { useStorefrontProductsList } = useStoreProducts();
102
+ const { data, isLoading } = useStorefrontProductsList({ page: 1, pageSize: 24 });
103
+
104
+ if (isLoading) return <p>Cargando…</p>;
105
+ return (
106
+ <ul>
107
+ {data?.products.map((p) => (
108
+ <li key={p.id}>{p.name}</li>
109
+ ))}
110
+ </ul>
111
+ );
92
112
  }
113
+ ```
114
+
115
+ ### 4. Datos en Server Components
116
+
117
+ ```tsx
118
+ import { getStorefrontProducts } from "@ecomiq/storefront/nextjs";
93
119
 
94
- export function generateMetadata(props: { params: Promise<{ route?: string[] }> }) {
95
- return generateStoreMetadata({ params: props.params });
120
+ export default async function CatalogPage() {
121
+ const { products } = await getStorefrontProducts({ page: 1, pageSize: 24 });
122
+ return <YourGrid products={products} />;
96
123
  }
97
124
  ```
98
125
 
99
- Con esto tienes una tienda funcional: home, listados, ficha de producto, carrito, checkout y área de cliente.
126
+ ### 5. Autenticación (Better Auth + SDK)
100
127
 
101
- ## Puntos de entrada
128
+ Instala [Better Auth](https://www.better-auth.com/) y los plugins del SDK (OAuth password grant con credenciales Ecomiq).
102
129
 
103
- Elige el subpath según lo que necesites en tu proyecto:
130
+ **Servidor:**
104
131
 
105
- | Import | Cuándo usarlo |
106
- |--------|----------------|
107
- | `@ecomiq/storefront/nextjs` | App Next.js completa: layout, router, provider, páginas exportables, temas |
108
- | `@ecomiq/storefront/nextjs/api` | Solo el handler del proxy `/api/store/*` |
109
- | `@ecomiq/storefront/nextjs/css` | Estilos base del shell |
110
- | `@ecomiq/storefront/react` | Componentes, hooks, tipos y utilidades para UI personalizada |
111
- | `@ecomiq/storefront/client` | Llamadas a la API de tienda (catálogo, carrito, pedidos, cliente) sin componentes |
112
- | `@ecomiq/storefront/core` | Tipos y schemas Zod (módulos sin React) |
132
+ ```ts
133
+ // lib/auth.ts
134
+ import { betterAuth } from "better-auth";
135
+ import { ecomiqOAuthPassword } from "@ecomiq/storefront/react";
113
136
 
114
- En la mayoría de storefronts nuevos basta con **`/nextjs`** para el armazón y **`/react`** cuando añadas pantallas o bloques propios.
137
+ export const auth = betterAuth({
138
+ plugins: [ecomiqOAuthPassword()],
139
+ });
140
+ ```
115
141
 
116
- ## Variables de entorno
142
+ **Ruta API:**
117
143
 
118
- | Variable | Descripción |
119
- |----------|-------------|
120
- | `STOREFRONT_API_URL` | URL base de la API de tienda Ecomiq (usada en el proxy) |
144
+ ```ts
145
+ // app/api/auth/[...all]/route.ts
146
+ import { toNextJsHandler } from "better-auth/next-js";
147
+ import { auth } from "@/lib/auth";
121
148
 
122
- Configura también en tu app el identificador de tienda y auth según tu despliegue (subdominio, `X-Store-Id`, Better Auth, etc.).
149
+ export const { GET, POST } = toNextJsHandler(auth);
150
+ ```
123
151
 
124
- ## Personalización
152
+ **Cliente:**
125
153
 
126
- ### Temas
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
+ ```
127
165
 
128
- Los bloques visuales (header, footer, tarjeta de producto, etc.) se cargan por nombre de tema:
166
+ **Layout** — añade `authClient` al provider del paso 2:
129
167
 
130
168
  ```tsx
131
- import { ThemeComponentLoader } from "@ecomiq/storefront/nextjs";
169
+ import { authClient } from "@/lib/auth-client";
132
170
 
133
- <ThemeComponentLoader
134
- themeName="default"
135
- componentName="productCard"
136
- props={{ product }}
137
- />
171
+ <EcomiqProvider store={store} authClient={authClient}>
172
+ {children}
173
+ </EcomiqProvider>
138
174
  ```
139
175
 
140
- Puedes registrar un tema propio y referenciarlo con `themeName`.
176
+ ### 6. Login, registro y logout
141
177
 
142
- ### UI y datos en componentes propios
178
+ Con el paso 5 configurado:
143
179
 
144
180
  ```tsx
145
- import { useProducts, ProductsSection, type StorefrontProduct } from "@ecomiq/storefront/react";
146
- ```
181
+ "use client";
147
182
 
148
- ```ts
149
- import { setClientStoreId, getStorefrontProducts } from "@ecomiq/storefront/client";
183
+ import {
184
+ useStore,
185
+ useStorefrontAuthClient,
186
+ useStorefrontSession,
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
+ }
150
225
 
151
- setClientStoreId("tu-store-id");
152
- const page = await getStorefrontProducts({ page: 1, pageSize: 24 });
226
+ return (
227
+ <div>
228
+ {/* Tu UI: formularios que llaman handleLogin / handleRegister */}
229
+ </div>
230
+ );
231
+ }
153
232
  ```
154
233
 
155
- ### Páginas preconstruidas
234
+ - **Login:** `signInWithEcomiqCredentials` (headers de tienda los resuelve el SDK).
235
+ - **Registro:** `registerCustomer`, luego login con las mismas credenciales.
236
+ - **Logout:** `signOut()` de `useStorefrontSession`.
237
+
238
+ ## Puntos de entrada (npm)
239
+
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/*` |
247
+
248
+ El cliente BFF se importa desde **`/nextjs`** (no hay subpath `./client` en npm).
249
+
250
+ ## Variables de entorno
251
+
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) |
156
256
 
157
- Importa páginas listas desde `@ecomiq/storefront/nextjs/export-pages/*` si prefieres componer rutas manualmente en lugar de `ShellRouter`.
257
+ Tras `getPublicStore`, el SDK configura el contexto de tienda en el servidor para las llamadas al proxy.
158
258
 
159
- ## Ejemplo de dependencias
259
+ **Identificador de tienda:** `slugStore` en `ecomiq.config.*` si existe; si no, header `Host`.
260
+
261
+ ## Publicación del paquete
262
+
263
+ Mantenimiento del tarball npm: [PUBLISHING.md](./PUBLISHING.md).
264
+
265
+ ## Dependencias recomendadas
160
266
 
161
267
  ```json
162
268
  {
163
269
  "dependencies": {
164
270
  "@ecomiq/storefront": "^0.1.0",
271
+ "@tanstack/react-query": "^5.66.0",
272
+ "better-auth": "^1.6.0",
165
273
  "next": "^15.3.0",
166
274
  "react": "^19.0.0",
167
- "react-dom": "^19.0.0",
168
- "tailwindcss": "^4.0.0"
275
+ "react-dom": "^19.0.0"
169
276
  }
170
277
  }
171
278
  ```
172
279
 
173
280
  ## Soporte
174
281
 
175
- Documentación de la plataforma y credenciales de API: [ecomiq.pe](https://ecomiq.pe) (o el portal que use tu organización).
176
-
177
- Para incidencias del SDK, abre un issue en el repositorio del paquete o contacta al equipo Ecomiq.
282
+ Documentación y credenciales: [ecomiq.pe](https://ecomiq.pe) (o el portal de tu organización).
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 };