@bacano/sdk 1.0.1 → 1.1.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/README.md +342 -7
- package/STOREFRONT_CLARITY.md +140 -0
- package/STOREFRONT_CONSTRUCTION.md +92 -0
- package/STOREFRONT_LEADS.md +130 -0
- package/STOREFRONT_LEGAL.md +66 -0
- package/STOREFRONT_MARKETING.md +286 -0
- package/STOREFRONT_PERFORMANCE.md +265 -0
- package/STOREFRONT_PREVIEW.md +474 -0
- package/client.d.ts +7 -1
- package/errors.d.ts +9 -1
- package/graphql.cjs +63 -0
- package/graphql.d.ts +2 -2
- package/graphql.js +63 -0
- package/index.cjs +1542 -192
- package/index.d.ts +8 -6
- package/index.js +1542 -192
- package/marketing/attribution.d.ts +51 -0
- package/marketing/clarity.d.ts +3 -0
- package/marketing/domain.d.ts +1 -0
- package/marketing/ga4.d.ts +2 -0
- package/marketing/index.d.ts +5 -0
- package/marketing/runtime.d.ts +2 -0
- package/marketing/sanitize.d.ts +2 -0
- package/marketing/types.d.ts +109 -0
- package/media.d.ts +45 -2
- package/modules/auth.d.ts +3 -2
- package/modules/availability.d.ts +2 -2
- package/modules/cart-session.d.ts +2 -2
- package/modules/cart.d.ts +3 -3
- package/modules/catalog.d.ts +15 -3
- package/modules/content.d.ts +2 -2
- package/modules/marketing.d.ts +5 -0
- package/modules/orders.d.ts +2 -2
- package/modules/profile.d.ts +1 -1
- package/modules/static-catalog.d.ts +13 -0
- package/modules/website.d.ts +7 -1
- package/package.json +14 -3
- package/react/context-registry.d.ts +9 -0
- package/react/context.d.ts +1 -1
- package/react/hooks.d.ts +23 -2
- package/react/marketing-context.d.ts +3 -0
- package/react/marketing-provider.d.ts +10 -0
- package/react/preview/cache.d.ts +23 -0
- package/react/preview/context.d.ts +5 -0
- package/react/preview/navigation.d.ts +9 -0
- package/react/preview/provider.d.ts +3 -0
- package/react/preview/shell.d.ts +3 -0
- package/react/preview/toolbar.d.ts +3 -0
- package/react/preview/types.d.ts +60 -0
- package/react/preview.cjs +742 -0
- package/react/preview.d.ts +14 -0
- package/react/preview.js +730 -0
- package/react/website-gate.d.ts +22 -0
- package/react.cjs +3432 -1780
- package/react.d.ts +8 -4
- package/react.js +3428 -1772
- package/skills/bacano-web/SKILL.md +109 -0
- package/skills/bacano-web/agents/openai.yaml +4 -0
- package/skills/bacano-web/references/common.md +91 -0
- package/skills/bacano-web/references/informational.md +39 -0
- package/skills/bacano-web/references/report.md +52 -0
- package/skills/bacano-web/references/storefront.md +116 -0
- package/skills/bacano-web/scripts/audit-http.mjs +247 -0
- package/skills/bacano-web/scripts/cli.mjs +222 -0
- package/skills/bacano-web/scripts/postinstall.mjs +8 -0
- package/transport/documents.d.ts +8 -6
- package/transport/graphql.d.ts +13 -1
- package/types.d.ts +202 -0
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
# Bacano Storefront Content Preview
|
|
2
|
+
|
|
3
|
+
Official guide for `@bacano/sdk/react/preview`, available since SDK `1.0.5`.
|
|
4
|
+
|
|
5
|
+
## Problem solved
|
|
6
|
+
|
|
7
|
+
Static storefronts should keep their public catalog and editorial pages frozen
|
|
8
|
+
at build time for SEO, speed, reliability, and predictable hosting costs. That
|
|
9
|
+
also means a published Content change is not visible on the public page until
|
|
10
|
+
the storefront is rebuilt and deployed.
|
|
11
|
+
|
|
12
|
+
The preview entrypoint provides a separate client-side route that reads the
|
|
13
|
+
current **published** Bacano Content on demand and renders it with the
|
|
14
|
+
storefront's own production components:
|
|
15
|
+
|
|
16
|
+
```text
|
|
17
|
+
Public route: Bacano Content -> Next build -> static HTML
|
|
18
|
+
Preview route: Browser -> @bacano/sdk -> current published Content
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Preview V1 is not a draft bypass and is not a realtime subscription. An editor
|
|
22
|
+
publishes the entry and then opens or refreshes the preview without rebuilding
|
|
23
|
+
the storefront.
|
|
24
|
+
|
|
25
|
+
## Architectural contract
|
|
26
|
+
|
|
27
|
+
### Bacano SDK owns
|
|
28
|
+
|
|
29
|
+
- Loading, ready, and safe error states.
|
|
30
|
+
- Per-route in-memory cache and simultaneous-request deduplication.
|
|
31
|
+
- Protection against stale responses after navigation or unmount.
|
|
32
|
+
- Forced refresh.
|
|
33
|
+
- Expanded state and four toolbar positions.
|
|
34
|
+
- `sessionStorage` persistence isolated by website slug.
|
|
35
|
+
- The official responsive and accessible toolbar.
|
|
36
|
+
- Neutral route normalization and internal-link interception.
|
|
37
|
+
- A preview-mode signal.
|
|
38
|
+
- A marketing no-op boundary inside the preview tree.
|
|
39
|
+
- SSR-safe module evaluation.
|
|
40
|
+
|
|
41
|
+
### The storefront owns
|
|
42
|
+
|
|
43
|
+
- The single preview route, for example `/vista-previa/`.
|
|
44
|
+
- The route map and routes that are safe to preview.
|
|
45
|
+
- The loader that combines Content keys for each route.
|
|
46
|
+
- Mapping Bacano Content to storefront view models.
|
|
47
|
+
- Rendering with the same visual components used by production.
|
|
48
|
+
- Translating preview navigation to its router or query-string convention.
|
|
49
|
+
- `noindex` metadata and sitemap exclusion.
|
|
50
|
+
- Hiding or disabling popups, automatic chat, capture banners, cart drawers,
|
|
51
|
+
checkout links, and other non-editorial features outside the preview tree.
|
|
52
|
+
|
|
53
|
+
The SDK does not know whether `/` needs one Content key or six, and it does not
|
|
54
|
+
render Home, About, Blog, Product, Catalog, or Checkout pages.
|
|
55
|
+
|
|
56
|
+
## Packaging
|
|
57
|
+
|
|
58
|
+
Import preview only from its dedicated entrypoint:
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
import {
|
|
62
|
+
BacanoPreviewShell,
|
|
63
|
+
interceptPreviewLink,
|
|
64
|
+
type PreviewRouteLoader,
|
|
65
|
+
} from "@bacano/sdk/react/preview";
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
It is a separate ESM/CJS entrypoint and is not re-exported by
|
|
69
|
+
`@bacano/sdk/react`. It has no Next.js or Tailwind dependency. The official
|
|
70
|
+
toolbar carries self-contained styles and does not require a stylesheet import.
|
|
71
|
+
Both React entrypoints share the same Bacano client and marketing contexts, so
|
|
72
|
+
preview must remain under the storefront's single `BacanoProvider`.
|
|
73
|
+
|
|
74
|
+
React `>=18` remains the only peer dependency.
|
|
75
|
+
|
|
76
|
+
## Required provider hierarchy
|
|
77
|
+
|
|
78
|
+
Mount exactly one `BacanoProvider` for the storefront, independently of whether
|
|
79
|
+
buyer accounts are enabled:
|
|
80
|
+
|
|
81
|
+
```tsx
|
|
82
|
+
import { BacanoProvider } from "@bacano/sdk/react";
|
|
83
|
+
|
|
84
|
+
export function StorefrontProviders({
|
|
85
|
+
children,
|
|
86
|
+
}: {
|
|
87
|
+
children: React.ReactNode;
|
|
88
|
+
}) {
|
|
89
|
+
return (
|
|
90
|
+
<BacanoProvider
|
|
91
|
+
config={{
|
|
92
|
+
apiUrl: process.env.NEXT_PUBLIC_BACANO_API_URL!,
|
|
93
|
+
websiteSlug: process.env.NEXT_PUBLIC_BACANO_WEBSITE_SLUG!,
|
|
94
|
+
}}
|
|
95
|
+
>
|
|
96
|
+
{children}
|
|
97
|
+
</BacanoProvider>
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
`BacanoPreviewProvider` consumes this existing client. Do not create another
|
|
103
|
+
client inside the preview loader and do not mount a second `BacanoProvider`.
|
|
104
|
+
|
|
105
|
+
If Clerk supplies buyer tokens, keep its token callback in this same provider.
|
|
106
|
+
The presence or absence of Clerk must not determine whether the public Bacano
|
|
107
|
+
client exists.
|
|
108
|
+
|
|
109
|
+
## Public API
|
|
110
|
+
|
|
111
|
+
### `BacanoPreviewProvider<TDocument>`
|
|
112
|
+
|
|
113
|
+
Required props:
|
|
114
|
+
|
|
115
|
+
| Prop | Purpose |
|
|
116
|
+
| ------------- | ------------------------------------------------------- |
|
|
117
|
+
| `websiteSlug` | Isolates persisted toolbar state between stores. |
|
|
118
|
+
| `route` | Current controlled storefront route. |
|
|
119
|
+
| `loadRoute` | Storefront loader receiving `{ route, force, client }`. |
|
|
120
|
+
| `navigate` | Updates the preview URL using the storefront router. |
|
|
121
|
+
|
|
122
|
+
Optional props:
|
|
123
|
+
|
|
124
|
+
| Prop | Default | Purpose |
|
|
125
|
+
| -------------------- | ----------------------- | -------------------------------------------- |
|
|
126
|
+
| `normalizeRoute` | `normalizePreviewRoute` | Canonicalizes route input. |
|
|
127
|
+
| `isPreviewableRoute` | all normalized routes | Rejects private or transactional routes. |
|
|
128
|
+
| `initialExpanded` | `true` | Initial toolbar visibility before hydration. |
|
|
129
|
+
| `initialPosition` | `top-right` | Initial toolbar corner. |
|
|
130
|
+
| `cacheMaxEntries` | `50` | Maximum cached route documents per provider. |
|
|
131
|
+
|
|
132
|
+
Loader contract:
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
type PreviewRouteLoader<TDocument> = (context: {
|
|
136
|
+
route: string;
|
|
137
|
+
force: boolean;
|
|
138
|
+
client: BacanoClient;
|
|
139
|
+
}) => Promise<TDocument>;
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
A `null` or `undefined` result becomes the safe `EMPTY_DOCUMENT` state. Loader
|
|
143
|
+
errors are not exposed verbatim in the public UI.
|
|
144
|
+
|
|
145
|
+
### `useBacanoPreview<TDocument>()`
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
{
|
|
151
|
+
isPreview: true;
|
|
152
|
+
websiteSlug: string;
|
|
153
|
+
route: string;
|
|
154
|
+
status: "idle" | "loading" | "ready" | "error";
|
|
155
|
+
document: TDocument | null;
|
|
156
|
+
error: { code: string; message: string } | null;
|
|
157
|
+
refreshedAt: Date | null;
|
|
158
|
+
isExpanded: boolean;
|
|
159
|
+
position: "top-left" | "top-right" | "bottom-left" | "bottom-right";
|
|
160
|
+
refresh(): Promise<void>;
|
|
161
|
+
navigate(route: string): void;
|
|
162
|
+
setExpanded(expanded: boolean): void;
|
|
163
|
+
setPosition(position): void;
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### `BacanoPreviewToolbar`
|
|
168
|
+
|
|
169
|
+
The optional tokens are deliberately limited:
|
|
170
|
+
|
|
171
|
+
```tsx
|
|
172
|
+
<BacanoPreviewToolbar
|
|
173
|
+
websiteName="My store"
|
|
174
|
+
logo={<img src="/logo.svg" alt="" />}
|
|
175
|
+
primaryColor="#172033"
|
|
176
|
+
accentColor="#2f80ed"
|
|
177
|
+
backgroundColor="#ffffff"
|
|
178
|
+
locale="es-CO"
|
|
179
|
+
zIndex={2147483000}
|
|
180
|
+
suggestedRoutes={["/", "/about", "/blog"]}
|
|
181
|
+
/>
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
The toolbar provides route input, refresh, collapse/expand, last refresh time,
|
|
185
|
+
safe status messages, keyboard focus, `aria-live`, responsive layout, reduced
|
|
186
|
+
motion support, and four positions.
|
|
187
|
+
|
|
188
|
+
### `BacanoPreviewShell<TDocument>`
|
|
189
|
+
|
|
190
|
+
Convenience composition of the provider and toolbar:
|
|
191
|
+
|
|
192
|
+
```tsx
|
|
193
|
+
<BacanoPreviewShell {...providerProps} toolbar={toolbarProps}>
|
|
194
|
+
{(preview) => renderPreviewState(preview)}
|
|
195
|
+
</BacanoPreviewShell>
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Pass `toolbar={false}` only when the storefront supplies another accessible
|
|
199
|
+
control surface.
|
|
200
|
+
|
|
201
|
+
## Minimal Next.js static-export example
|
|
202
|
+
|
|
203
|
+
The SDK contains no Next.js imports. This adapter belongs to the storefront.
|
|
204
|
+
|
|
205
|
+
```tsx
|
|
206
|
+
// app/vista-previa/page.tsx
|
|
207
|
+
import type { Metadata } from "next";
|
|
208
|
+
import { Suspense } from "react";
|
|
209
|
+
import { PreviewClient } from "./PreviewClient";
|
|
210
|
+
|
|
211
|
+
export const metadata: Metadata = {
|
|
212
|
+
title: "Vista previa del sitio",
|
|
213
|
+
robots: { index: false, follow: false },
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
export default function PreviewPage() {
|
|
217
|
+
return (
|
|
218
|
+
<Suspense fallback={<p>Cargando vista previa...</p>}>
|
|
219
|
+
<PreviewClient />
|
|
220
|
+
</Suspense>
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
```tsx
|
|
226
|
+
// app/vista-previa/PreviewClient.tsx
|
|
227
|
+
"use client";
|
|
228
|
+
|
|
229
|
+
import { useSearchParams } from "next/navigation";
|
|
230
|
+
import {
|
|
231
|
+
BacanoPreviewShell,
|
|
232
|
+
interceptPreviewLink,
|
|
233
|
+
normalizePreviewRoute,
|
|
234
|
+
type PreviewRouteLoader,
|
|
235
|
+
} from "@bacano/sdk/react/preview";
|
|
236
|
+
|
|
237
|
+
type PreviewDocument =
|
|
238
|
+
| { kind: "home"; hero: unknown; story: unknown }
|
|
239
|
+
| { kind: "about"; content: unknown }
|
|
240
|
+
| { kind: "unsupported"; route: string };
|
|
241
|
+
|
|
242
|
+
const previewable = new Set(["/", "/about"]);
|
|
243
|
+
|
|
244
|
+
const loadRoute: PreviewRouteLoader<PreviewDocument> = async ({
|
|
245
|
+
route,
|
|
246
|
+
client,
|
|
247
|
+
}) => {
|
|
248
|
+
if (route === "/") {
|
|
249
|
+
const [hero, story] = await Promise.all([
|
|
250
|
+
client.content.getByKey("home-hero"),
|
|
251
|
+
client.content.getByKey("home-story"),
|
|
252
|
+
]);
|
|
253
|
+
return { kind: "home", hero, story };
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (route === "/about") {
|
|
257
|
+
return {
|
|
258
|
+
kind: "about",
|
|
259
|
+
content: await client.content.getByKey("about"),
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return { kind: "unsupported", route };
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
export function PreviewClient() {
|
|
267
|
+
const searchParams = useSearchParams();
|
|
268
|
+
const route = normalizePreviewRoute(searchParams.get("route"));
|
|
269
|
+
|
|
270
|
+
return (
|
|
271
|
+
<BacanoPreviewShell
|
|
272
|
+
websiteSlug={process.env.NEXT_PUBLIC_BACANO_WEBSITE_SLUG!}
|
|
273
|
+
route={route}
|
|
274
|
+
loadRoute={loadRoute}
|
|
275
|
+
isPreviewableRoute={(nextRoute) => previewable.has(nextRoute)}
|
|
276
|
+
navigate={(nextRoute) => {
|
|
277
|
+
window.location.assign(
|
|
278
|
+
`/vista-previa/?route=${encodeURIComponent(nextRoute)}`,
|
|
279
|
+
);
|
|
280
|
+
}}
|
|
281
|
+
toolbar={{
|
|
282
|
+
websiteName: "My store",
|
|
283
|
+
suggestedRoutes: Array.from(previewable),
|
|
284
|
+
}}
|
|
285
|
+
>
|
|
286
|
+
{(preview) => {
|
|
287
|
+
if (preview.status === "loading" && !preview.document) {
|
|
288
|
+
return <PreviewSkeleton />;
|
|
289
|
+
}
|
|
290
|
+
if (preview.status === "error" && !preview.document) {
|
|
291
|
+
return <PreviewError onRetry={preview.refresh} />;
|
|
292
|
+
}
|
|
293
|
+
if (!preview.document) return null;
|
|
294
|
+
|
|
295
|
+
return (
|
|
296
|
+
<div
|
|
297
|
+
onClickCapture={(event) =>
|
|
298
|
+
interceptPreviewLink(event, {
|
|
299
|
+
navigate: preview.navigate,
|
|
300
|
+
isPreviewableRoute: (nextRoute) => previewable.has(nextRoute),
|
|
301
|
+
})
|
|
302
|
+
}
|
|
303
|
+
>
|
|
304
|
+
<ProductionRenderer document={preview.document} />
|
|
305
|
+
</div>
|
|
306
|
+
);
|
|
307
|
+
}}
|
|
308
|
+
</BacanoPreviewShell>
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
The example intentionally leaves `PreviewSkeleton`, `PreviewError`, and
|
|
314
|
+
`ProductionRenderer` to the storefront so the page retains its visual language.
|
|
315
|
+
|
|
316
|
+
## Route map guidance
|
|
317
|
+
|
|
318
|
+
Keep a finite allowlist. Typical V1 routes include:
|
|
319
|
+
|
|
320
|
+
- Home.
|
|
321
|
+
- About and other editorial pages.
|
|
322
|
+
- Blog index and published articles.
|
|
323
|
+
- Content-driven catalog landing pages.
|
|
324
|
+
- Published legal pages.
|
|
325
|
+
|
|
326
|
+
Do not preview in V1:
|
|
327
|
+
|
|
328
|
+
- Checkout or payment response.
|
|
329
|
+
- Account creation, login, or private account pages.
|
|
330
|
+
- Private orders.
|
|
331
|
+
- Gateway sessions.
|
|
332
|
+
- Any route whose primary purpose is transactional.
|
|
333
|
+
|
|
334
|
+
Product pages can be added later if the storefront defines an explicit loader
|
|
335
|
+
and non-transactional renderer. The SDK does not enable them automatically.
|
|
336
|
+
|
|
337
|
+
## Internal-link interception
|
|
338
|
+
|
|
339
|
+
`interceptPreviewLink()` only captures plain primary clicks on same-origin
|
|
340
|
+
links. It preserves:
|
|
341
|
+
|
|
342
|
+
- Ctrl/Cmd click.
|
|
343
|
+
- Shift and Alt click.
|
|
344
|
+
- External origins.
|
|
345
|
+
- `target="_blank"`.
|
|
346
|
+
- `download` links.
|
|
347
|
+
- Routes rejected by `isPreviewableRoute`.
|
|
348
|
+
|
|
349
|
+
The callback receives the normalized public route. The storefront decides how
|
|
350
|
+
to encode it into `/vista-previa/?route=...`.
|
|
351
|
+
|
|
352
|
+
## Marketing and commercial UI isolation
|
|
353
|
+
|
|
354
|
+
`BacanoPreviewProvider` overrides `useMarketing()` with a synchronous no-op in
|
|
355
|
+
its subtree. This prevents GA4, Meta, and Clarity browser events from preview renderers,
|
|
356
|
+
even if the root storefront marketing runtime is active.
|
|
357
|
+
|
|
358
|
+
`useBacanoPreviewMode()` is also available to components placed inside the
|
|
359
|
+
preview boundary:
|
|
360
|
+
|
|
361
|
+
```tsx
|
|
362
|
+
const isPreview = useBacanoPreviewMode();
|
|
363
|
+
if (isPreview) return null;
|
|
364
|
+
return <CommercialPopup />;
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
Components rendered above or beside the preview provider cannot read a child
|
|
368
|
+
context. The storefront must therefore hide or disable root-level commercial
|
|
369
|
+
elements on its preview route, including:
|
|
370
|
+
|
|
371
|
+
- Marketing popups and lead banners.
|
|
372
|
+
- Automatic chat and WhatsApp launchers.
|
|
373
|
+
- Cart drawers and checkout calls to action.
|
|
374
|
+
- Independently mounted analytics providers.
|
|
375
|
+
|
|
376
|
+
When `BacanoMarketingProvider` is nested inside a preview-mode boundary, it also
|
|
377
|
+
recognizes the signal and remains a no-op.
|
|
378
|
+
|
|
379
|
+
Server-side conversion events remain protected by keeping all transactional
|
|
380
|
+
routes and actions outside preview. A browser preview flag is not an
|
|
381
|
+
authorization mechanism and must never be sent to bypass commercial rules.
|
|
382
|
+
|
|
383
|
+
## Security and privacy
|
|
384
|
+
|
|
385
|
+
V1 calls only the existing public Content methods:
|
|
386
|
+
|
|
387
|
+
- `client.content.getByKey()`
|
|
388
|
+
- `client.content.getByCategory()`
|
|
389
|
+
- `client.content.getByTag()`
|
|
390
|
+
|
|
391
|
+
Those contracts return current published content. There is no `includeDrafts`,
|
|
392
|
+
GraphQL bypass, alternate endpoint, admin secret, or permission change.
|
|
393
|
+
|
|
394
|
+
The route remains publicly reachable, so it must show only information that is
|
|
395
|
+
already public. Keep `noindex`, exclude it from sitemap, and do not place tokens,
|
|
396
|
+
customer data, payment data, or secret identifiers in the preview query string.
|
|
397
|
+
|
|
398
|
+
A future draft preview requires a Bacano-signed session containing website,
|
|
399
|
+
user, scopes, allowed routes, expiration, revocation, and audit information.
|
|
400
|
+
Never implement drafts using `?includeDrafts=true`.
|
|
401
|
+
|
|
402
|
+
## Static production guarantees
|
|
403
|
+
|
|
404
|
+
When the storefront follows this contract:
|
|
405
|
+
|
|
406
|
+
- Public pages keep their build-time Content snapshots.
|
|
407
|
+
- `output: "export"` remains valid.
|
|
408
|
+
- `generateStaticParams`, sitemap, and public metadata do not change.
|
|
409
|
+
- The preview entrypoint is imported only by its one route.
|
|
410
|
+
- Preview API errors affect only the preview route.
|
|
411
|
+
- A temporary Bacano outage while someone uses preview does not alter the
|
|
412
|
+
already deployed public storefront.
|
|
413
|
+
|
|
414
|
+
The build still depends on whatever Bacano data the public static pages request
|
|
415
|
+
at build time. Preview does not change or replace that build contract.
|
|
416
|
+
|
|
417
|
+
## Validated reference implementation: Baby Rex
|
|
418
|
+
|
|
419
|
+
Baby Rex validated the official integration with `@bacano/sdk` `1.0.5` on
|
|
420
|
+
2026-08-06. It uses one global `BacanoProvider`; `BacanoPreviewShell` consumes
|
|
421
|
+
that initialized client and the loader receives `{ route, force, client }`.
|
|
422
|
+
|
|
423
|
+
The validated static production build generated all `417/417` pages. TypeScript,
|
|
424
|
+
lint, unit tests, E2E, preview navigation, cache, refresh, toolbar behavior, and
|
|
425
|
+
the `idle -> loading -> ready` transition passed. No marketing event was emitted
|
|
426
|
+
inside preview.
|
|
427
|
+
|
|
428
|
+
The implementation removed these generic storefront responsibilities:
|
|
429
|
+
|
|
430
|
+
- Local `PreviewToolbar.tsx`.
|
|
431
|
+
- Local expanded/position/sessionStorage state.
|
|
432
|
+
- Local loading/error/refreshed-at controller state.
|
|
433
|
+
- Local request-promise cache and forced-refresh orchestration.
|
|
434
|
+
- Generic internal-link interception.
|
|
435
|
+
- Pathname-based marketing suppression inside preview renderers once the SDK
|
|
436
|
+
boundary covers them.
|
|
437
|
+
|
|
438
|
+
Baby Rex retained these storefront-specific responsibilities:
|
|
439
|
+
|
|
440
|
+
- `/vista-previa/page.tsx` and its `noindex` metadata.
|
|
441
|
+
- Sitemap exclusion.
|
|
442
|
+
- `PreviewDocument` union.
|
|
443
|
+
- Route allowlist and suggested routes.
|
|
444
|
+
- `loadPreviewDocument()` mappings for Home, About, Blog, legal pages, and
|
|
445
|
+
content-driven catalog sections.
|
|
446
|
+
- `PreviewDocumentView` and all production renderers.
|
|
447
|
+
- Storefront-specific skeleton, missing-content, and unsupported-route views.
|
|
448
|
+
- The Next.js adapter that writes `?ruta={encodedRoute}`.
|
|
449
|
+
|
|
450
|
+
Baby Rex is the implementation reference for integration shape, not a source to
|
|
451
|
+
copy blindly. Other storefronts keep their own route maps, Content keys,
|
|
452
|
+
mappers, and visual components.
|
|
453
|
+
|
|
454
|
+
## Short checklist for agents
|
|
455
|
+
|
|
456
|
+
- [ ] Use `@bacano/sdk` `1.0.5` or newer.
|
|
457
|
+
- [ ] Mount exactly one global `BacanoProvider`.
|
|
458
|
+
- [ ] Place `BacanoPreviewShell` below that provider; never create a preview client.
|
|
459
|
+
- [ ] Load only published Content through the `client` supplied to `loadRoute`.
|
|
460
|
+
- [ ] Keep route allowlists, Content keys, mappers, and renderers in the storefront.
|
|
461
|
+
- [ ] Keep preview `noindex`, outside the sitemap, and free of transactional routes.
|
|
462
|
+
- [ ] Hide root-level commercial UI and verify zero marketing events in preview.
|
|
463
|
+
- [ ] Test idle, loading, ready, safe error, refresh, cache, and navigation states.
|
|
464
|
+
- [ ] Verify static public pages and their build output remain unchanged.
|
|
465
|
+
- [ ] Run typecheck, lint, unit tests, E2E, and the complete production build.
|
|
466
|
+
|
|
467
|
+
## V1 limitations
|
|
468
|
+
|
|
469
|
+
- Published Content only.
|
|
470
|
+
- Manual refresh; no WebSocket, polling, or subscriptions.
|
|
471
|
+
- In-memory document cache lasts for the mounted provider session.
|
|
472
|
+
- Toolbar preferences use browser `sessionStorage`.
|
|
473
|
+
- The storefront owns all route-specific loaders and renderers.
|
|
474
|
+
- Transactional and private pages are intentionally excluded.
|
package/client.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { BacanoClientConfig, BacanoClientContext, Brand, Cart, CartItem, CatalogAttributeList,
|
|
1
|
+
import type { BacanoClientConfig, BacanoClientContext, Brand, Cart, CartItem, CatalogAttributeList, CatalogCategoryList, CatalogPageResult, CheckoutResult, CheckoutSubmission, ContentEntry, CustomerAddress, CustomerProfile, CustomerProfileUpdate, GetAttributeListOptions, GetCatalogPageOptions, GetCategoryListOptions, GetContentOptions, GetOrdersOptions, GetProductFiltersOptions, GetProductRecommendationsOptions, GetProductSeoEntriesOptions, GetProductsOptions, GetSearchSuggestionsOptions, GetStaticCatalogBootstrapOptions, GetStaticCatalogSnapshotOptions, Order, Product, ProductAvailability, ProductCategory, ProductContentBlock, ProductFilters, ProductRecommendationsResult, ProductSeoEntriesResult, ProductVariantLiveState, PublicOrder, PublicOrderLookupInput, SearchSuggestion, StartGuestCheckoutInput, StaticCatalogBootstrap, StaticCatalogSnapshot, WaitForPublicOrderPaymentInput, WebsiteCampaignCaptureInput, WebsiteCampaignCaptureResult, WebsiteCheckoutAddress, WebsiteCheckoutItem, WebsiteCouponActionResult, WebsiteDeliveryOption, WebsiteFreeShippingProgress, WebsiteFulfillmentFamily, WebsiteGuestContact, WebsiteLocationOptions, WebsitePaymentMethod, WebsitePaymentOption, WebsitePaymentSessionResult, WebsiteShippingPolicy, WebsiteShippingQuote } from "./types";
|
|
2
2
|
export interface BacanoClient {
|
|
3
3
|
/** Initialize the client and resolve its website context. */
|
|
4
4
|
init(): Promise<void>;
|
|
@@ -12,8 +12,11 @@ export interface BacanoClient {
|
|
|
12
12
|
};
|
|
13
13
|
catalog: {
|
|
14
14
|
getCatalogPage(opts: GetCatalogPageOptions): Promise<CatalogPageResult>;
|
|
15
|
+
getStaticCatalogSnapshot(opts: GetStaticCatalogSnapshotOptions): Promise<StaticCatalogSnapshot>;
|
|
16
|
+
getStaticCatalogBootstrap(opts: GetStaticCatalogBootstrapOptions): Promise<StaticCatalogBootstrap>;
|
|
15
17
|
getProductSeoEntries(opts?: GetProductSeoEntriesOptions): Promise<ProductSeoEntriesResult>;
|
|
16
18
|
getProductRecommendations(opts: GetProductRecommendationsOptions): Promise<ProductRecommendationsResult>;
|
|
19
|
+
getProductLiveState(productVariantIds: string[]): Promise<ProductVariantLiveState[]>;
|
|
17
20
|
getProducts(opts?: GetProductsOptions): Promise<Product[]>;
|
|
18
21
|
getProduct(productId: string): Promise<Product | null>;
|
|
19
22
|
getProductBySlug(slug: string): Promise<Product | null>;
|
|
@@ -92,5 +95,8 @@ export interface BacanoClient {
|
|
|
92
95
|
getAddresses(): Promise<CustomerAddress[]>;
|
|
93
96
|
update(data: CustomerProfileUpdate): Promise<CustomerProfile>;
|
|
94
97
|
};
|
|
98
|
+
marketing: {
|
|
99
|
+
captureLead(input: WebsiteCampaignCaptureInput): Promise<WebsiteCampaignCaptureResult>;
|
|
100
|
+
};
|
|
95
101
|
}
|
|
96
102
|
export declare function createBacanoClient(config: BacanoClientConfig): BacanoClient;
|
package/errors.d.ts
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
export declare class WebsiteError extends Error {
|
|
2
2
|
readonly code: string;
|
|
3
|
-
|
|
3
|
+
readonly status: number | null;
|
|
4
|
+
readonly retryAfterSeconds: number | null;
|
|
5
|
+
constructor(message: string, code?: string, options?: {
|
|
6
|
+
status?: number;
|
|
7
|
+
retryAfterSeconds?: number;
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
export declare class RateLimitError extends WebsiteError {
|
|
11
|
+
constructor(message: string, retryAfterSeconds: number);
|
|
4
12
|
}
|
|
5
13
|
export declare class NotInitializedError extends WebsiteError {
|
|
6
14
|
constructor();
|
package/graphql.cjs
CHANGED
|
@@ -888,6 +888,62 @@ var CHECK_AVAILABILITY = (
|
|
|
888
888
|
product_variant_id
|
|
889
889
|
in_stock
|
|
890
890
|
label
|
|
891
|
+
max_purchasable_quantity
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
`
|
|
895
|
+
);
|
|
896
|
+
var GET_PRODUCT_LIVE_STATE = (
|
|
897
|
+
/* GraphQL */
|
|
898
|
+
`
|
|
899
|
+
query GetProductLiveState(
|
|
900
|
+
$variantIds: [uuid!]!
|
|
901
|
+
$companyId: uuid!
|
|
902
|
+
$chainId: uuid!
|
|
903
|
+
$websiteId: uuid!
|
|
904
|
+
) {
|
|
905
|
+
product_variants(
|
|
906
|
+
where: {
|
|
907
|
+
id: { _in: $variantIds }
|
|
908
|
+
is_disabled: { _eq: false }
|
|
909
|
+
product: {
|
|
910
|
+
company_id: { _eq: $companyId }
|
|
911
|
+
product_website_publications: {
|
|
912
|
+
website_id: { _eq: $websiteId }
|
|
913
|
+
is_visible: { _eq: true }
|
|
914
|
+
}
|
|
915
|
+
chain_products: {
|
|
916
|
+
chain_id: { _eq: $chainId }
|
|
917
|
+
is_enabled: { _eq: true }
|
|
918
|
+
status: { _eq: active }
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
) {
|
|
923
|
+
id
|
|
924
|
+
product_variant_prices(
|
|
925
|
+
where: {
|
|
926
|
+
price_type: { _in: [CLIENT, SALE, WHOLESALE, WHOLESALE_2] }
|
|
927
|
+
_or: [
|
|
928
|
+
{ chain_id: { _eq: $chainId } }
|
|
929
|
+
{ chain_id: { _is_null: true } }
|
|
930
|
+
]
|
|
931
|
+
}
|
|
932
|
+
) {
|
|
933
|
+
id
|
|
934
|
+
price
|
|
935
|
+
price_type
|
|
936
|
+
chain_id
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
websiteProductAvailability(
|
|
940
|
+
product_variant_ids: $variantIds
|
|
941
|
+
chain_id: $chainId
|
|
942
|
+
) {
|
|
943
|
+
product_variant_id
|
|
944
|
+
in_stock
|
|
945
|
+
label
|
|
946
|
+
max_purchasable_quantity
|
|
891
947
|
}
|
|
892
948
|
}
|
|
893
949
|
`
|
|
@@ -997,6 +1053,7 @@ var START_GUEST_CHECKOUT = (
|
|
|
997
1053
|
$contact: WebsiteGuestContactInput!
|
|
998
1054
|
$items: [WebsiteCheckoutItemInput!]!
|
|
999
1055
|
$fulfillmentFamily: String
|
|
1056
|
+
$marketingAttribution: WebsiteMarketingAttributionInput
|
|
1000
1057
|
) {
|
|
1001
1058
|
websiteStartGuestCheckout(
|
|
1002
1059
|
website_id: $websiteId
|
|
@@ -1004,6 +1061,7 @@ var START_GUEST_CHECKOUT = (
|
|
|
1004
1061
|
contact: $contact
|
|
1005
1062
|
items: $items
|
|
1006
1063
|
fulfillment_family: $fulfillmentFamily
|
|
1064
|
+
marketing_attribution: $marketingAttribution
|
|
1007
1065
|
) {
|
|
1008
1066
|
success
|
|
1009
1067
|
order_id
|
|
@@ -1161,6 +1219,8 @@ var CHECKOUT = (
|
|
|
1161
1219
|
$paymentMethodId: uuid
|
|
1162
1220
|
$buyerAccountOptIn: Boolean
|
|
1163
1221
|
$initialPayment: WebsiteInitialPaymentInput
|
|
1222
|
+
$marketingAttribution: WebsiteMarketingAttributionInput
|
|
1223
|
+
$advertisingConsent: WebsiteAdvertisingConsentInput
|
|
1164
1224
|
) {
|
|
1165
1225
|
websiteCheckout(
|
|
1166
1226
|
order_id: $orderId
|
|
@@ -1173,6 +1233,8 @@ var CHECKOUT = (
|
|
|
1173
1233
|
payment_method_id: $paymentMethodId
|
|
1174
1234
|
buyer_account_opt_in: $buyerAccountOptIn
|
|
1175
1235
|
initial_payment: $initialPayment
|
|
1236
|
+
marketing_attribution: $marketingAttribution
|
|
1237
|
+
advertising_consent: $advertisingConsent
|
|
1176
1238
|
) {
|
|
1177
1239
|
success
|
|
1178
1240
|
order_id
|
|
@@ -2028,6 +2090,7 @@ var documents = {
|
|
|
2028
2090
|
WEBSITE_GET_PRODUCT_RECOMMENDATIONS,
|
|
2029
2091
|
GET_BRANDS,
|
|
2030
2092
|
CHECK_AVAILABILITY,
|
|
2093
|
+
GET_PRODUCT_LIVE_STATE,
|
|
2031
2094
|
GET_PRODUCT_FILTERS,
|
|
2032
2095
|
GET_SEARCH_SUGGESTIONS,
|
|
2033
2096
|
WEBSITE_GET_CONTENT,
|
package/graphql.d.ts
CHANGED
|
@@ -13,5 +13,5 @@
|
|
|
13
13
|
* ```
|
|
14
14
|
*/
|
|
15
15
|
export { createAuthHeaders } from "./modules/auth";
|
|
16
|
-
export { APPLY_COUPON, CHECK_AVAILABILITY, CHECKOUT, CREATE_CART, CREATE_PAYMENT_SESSION, documents, GET_BRANDS, GET_CART, GET_CATALOG_ATTRIBUTE_LIST, GET_CATALOG_CATEGORY_LIST, GET_CATEGORIES, GET_CONTEXTUAL_CATALOG_ATTRIBUTE_LIST, GET_DELIVERY_OPTIONS, GET_FREE_SHIPPING_PROGRESS, GET_LOCATION_OPTIONS, GET_ORDER_BY_PK, GET_ORDERS,
|
|
17
|
-
export type { AccessTokenProvider, BacanoClientConfig, BacanoClientContext, Brand, Cart, CartItem, CatalogAttributeList, CatalogAttributeListFilters, CatalogAttributeListItem, CatalogAttributeListValue, CatalogCategoryList, CatalogCategoryListItem, CatalogCategoryListItemSourceType, CheckoutResult, CheckoutSubmission, ContentEntry, ContentJson, ContentMedia, ContentTypeSummary, CustomerProfile, CustomerProfileUpdate, GetAttributeListOptions, GetCategoryListOptions, GetContentOptions, GetOrdersOptions, GetProductSeoEntriesOptions, GetProductsOptions, Order, OrderItem, Product, ProductAttribute, ProductAvailability, ProductCategory, ProductMedia, ProductMediaKind, ProductPrice, ProductPriceType, ProductSeoEntriesResult, ProductSeoEntry, ProductSubcategory, ProductVariant, ProductVariantPricing, PublicOrder, PublicOrderDeliverySummary, PublicOrderItem, PublicOrderLookupInput, PublicOrderPaymentSummary, StartGuestCheckoutInput, VariantAttribute, WebsiteCheckoutAddress, WebsiteCheckoutItem, WebsiteCouponActionResult, WebsiteDeliveryOption, WebsiteDepartmentOption, WebsiteFreeShippingProgress, WebsiteFulfillmentFamily, WebsiteGuestContact, WebsiteLocationOptions, WebsiteMunicipalityOption, WebsitePaymentMethod, WebsitePaymentSessionResult, WebsitePickupInfo,
|
|
16
|
+
export { APPLY_COUPON, CHECK_AVAILABILITY, CHECKOUT, CREATE_CART, CREATE_PAYMENT_SESSION, documents, GET_BRANDS, GET_CART, GET_CATALOG_ATTRIBUTE_LIST, GET_CATALOG_CATEGORY_LIST, GET_CATEGORIES, GET_CONTEXTUAL_CATALOG_ATTRIBUTE_LIST, GET_DELIVERY_OPTIONS, GET_FREE_SHIPPING_PROGRESS, GET_LOCATION_OPTIONS, GET_ORDER_BY_PK, GET_ORDERS, GET_PAYMENT_METHODS, GET_PRODUCT_BY_PK, GET_PRODUCT_BY_SLUG, GET_PRODUCT_SEO_ENTRIES, GET_PRODUCTS, GET_PROFILE, GET_PUBLIC_ORDER, GET_SHIPPING_POLICY, GET_SHIPPING_QUOTES, REMOVE_CART_ITEM, REMOVE_COUPON, RESOLVE_WEBSITE_BY_SLUG, SET_CART_ITEM, START_GUEST_CHECKOUT, UPDATE_PROFILE, VALIDATE_COUPON, WEBSITE_GET_CONTENT, } from "./transport/documents";
|
|
17
|
+
export type { AccessTokenProvider, BacanoClientConfig, BacanoClientContext, Brand, Cart, CartItem, CatalogAttributeList, CatalogAttributeListFilters, CatalogAttributeListItem, CatalogAttributeListValue, CatalogCategoryList, CatalogCategoryListItem, CatalogCategoryListItemSourceType, CheckoutResult, CheckoutSubmission, ContentEntry, ContentJson, ContentMedia, ContentTypeSummary, CustomerProfile, CustomerProfileUpdate, GetAttributeListOptions, GetCategoryListOptions, GetContentOptions, GetOrdersOptions, GetProductSeoEntriesOptions, GetProductsOptions, Order, OrderItem, Product, ProductAttribute, ProductAvailability, ProductCategory, ProductMedia, ProductMediaKind, ProductPrice, ProductPriceType, ProductSeoEntriesResult, ProductSeoEntry, ProductSubcategory, ProductVariant, ProductVariantPricing, PublicOrder, PublicOrderDeliverySummary, PublicOrderItem, PublicOrderLookupInput, PublicOrderPaymentSummary, StartGuestCheckoutInput, VariantAttribute, WebsiteCheckoutAddress, WebsiteCheckoutItem, WebsiteCouponActionResult, WebsiteDeliveryOption, WebsiteDepartmentOption, WebsiteFreeShippingProgress, WebsiteFulfillmentFamily, WebsiteGuestContact, WebsiteLocationOptions, WebsiteMunicipalityOption, WebsitePaymentMethod, WebsitePaymentSessionResult, WebsitePickupInfo, WebsiteShippingPolicy, WebsiteShippingQuote, } from "./types";
|