@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 +183 -78
- package/dist/..d.ts +30 -0
- package/dist/{store-CYBCpY6v.d.ts → collection-anMkdWa7.d.ts} +2667 -2667
- package/dist/core.d.ts +47 -51
- package/dist/core.js +324 -212
- package/dist/core.js.map +1 -1
- package/dist/find-storefront-cms-page-DNwLo1iV.d.ts +178 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.js +103 -0
- package/dist/index.js.map +1 -0
- package/dist/nextjs/globals.css +3 -4
- package/dist/nextjs.d.ts +59 -54
- package/dist/nextjs.js +871 -1483
- package/dist/nextjs.js.map +1 -1
- package/dist/react.d.ts +50 -130
- package/dist/react.js +1019 -1548
- package/dist/react.js.map +1 -1
- package/package.json +5 -1
- package/dist/find-storefront-cms-page-Bo0-G1FK.d.ts +0 -119
package/README.md
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
# @ecomiq/storefront
|
|
2
2
|
|
|
3
|
-
SDK
|
|
3
|
+
SDK **headless** para construir tu tienda con **Next.js** y **React** sobre la API de Ecomiq.
|
|
4
4
|
|
|
5
|
-
Incluye
|
|
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
|
-
##
|
|
31
|
+
## `ecomiq.config.js` (opcional)
|
|
34
32
|
|
|
35
|
-
|
|
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
|
-
|
|
35
|
+
```js
|
|
36
|
+
// @ts-check
|
|
37
|
+
|
|
38
|
+
/** @type {import('@ecomiq/storefront').EcomiqConfig} */
|
|
39
|
+
const ecomiqConfig = {
|
|
40
|
+
slugStore: "mi-tienda",
|
|
41
|
+
};
|
|
38
42
|
|
|
39
|
-
|
|
40
|
-
@import "tailwindcss";
|
|
41
|
-
@import "@ecomiq/storefront/nextjs/css";
|
|
43
|
+
module.exports = ecomiqConfig;
|
|
42
44
|
```
|
|
43
45
|
|
|
44
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
72
|
+
### 2. Layout y tienda
|
|
58
73
|
|
|
59
74
|
```tsx
|
|
60
|
-
// app/
|
|
61
|
-
import {
|
|
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={
|
|
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
|
-
|
|
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
|
-
|
|
93
|
+
### 3. Catálogo con hooks
|
|
79
94
|
|
|
80
95
|
```tsx
|
|
81
|
-
|
|
82
|
-
import {
|
|
83
|
-
ShellRouter,
|
|
84
|
-
generateMetadata as generateStoreMetadata,
|
|
85
|
-
} from "@ecomiq/storefront/nextjs";
|
|
96
|
+
"use client";
|
|
86
97
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
})
|
|
91
|
-
|
|
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
|
|
95
|
-
|
|
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
|
-
|
|
126
|
+
### 5. Autenticación (Better Auth + SDK)
|
|
100
127
|
|
|
101
|
-
|
|
128
|
+
Instala [Better Auth](https://www.better-auth.com/) y los plugins del SDK (OAuth password grant con credenciales Ecomiq).
|
|
102
129
|
|
|
103
|
-
|
|
130
|
+
**Servidor:**
|
|
104
131
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
|
|
137
|
+
export const auth = betterAuth({
|
|
138
|
+
plugins: [ecomiqOAuthPassword()],
|
|
139
|
+
});
|
|
140
|
+
```
|
|
115
141
|
|
|
116
|
-
|
|
142
|
+
**Ruta API:**
|
|
117
143
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
149
|
+
export const { GET, POST } = toNextJsHandler(auth);
|
|
150
|
+
```
|
|
123
151
|
|
|
124
|
-
|
|
152
|
+
**Cliente:**
|
|
125
153
|
|
|
126
|
-
|
|
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
|
-
|
|
166
|
+
**Layout** — añade `authClient` al provider del paso 2:
|
|
129
167
|
|
|
130
168
|
```tsx
|
|
131
|
-
import {
|
|
169
|
+
import { authClient } from "@/lib/auth-client";
|
|
132
170
|
|
|
133
|
-
<
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
props={{ product }}
|
|
137
|
-
/>
|
|
171
|
+
<EcomiqProvider store={store} authClient={authClient}>
|
|
172
|
+
{children}
|
|
173
|
+
</EcomiqProvider>
|
|
138
174
|
```
|
|
139
175
|
|
|
140
|
-
|
|
176
|
+
### 6. Login, registro y logout
|
|
141
177
|
|
|
142
|
-
|
|
178
|
+
Con el paso 5 configurado:
|
|
143
179
|
|
|
144
180
|
```tsx
|
|
145
|
-
|
|
146
|
-
```
|
|
181
|
+
"use client";
|
|
147
182
|
|
|
148
|
-
|
|
149
|
-
|
|
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
|
-
|
|
152
|
-
|
|
226
|
+
return (
|
|
227
|
+
<div>
|
|
228
|
+
{/* Tu UI: formularios que llaman handleLogin / handleRegister */}
|
|
229
|
+
</div>
|
|
230
|
+
);
|
|
231
|
+
}
|
|
153
232
|
```
|
|
154
233
|
|
|
155
|
-
|
|
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
|
-
|
|
257
|
+
Tras `getPublicStore`, el SDK configura el contexto de tienda en el servidor para las llamadas al proxy.
|
|
158
258
|
|
|
159
|
-
|
|
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
|
|
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 };
|