@dutchiesdk/ecommerce-extensions-sdk 0.16.0 → 0.17.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
CHANGED
|
@@ -191,6 +191,220 @@ const currentLocations = await dataLoaders.locations(); // Current context locat
|
|
|
191
191
|
|
|
192
192
|
You can register callback functions that will be triggered by certain events in the Dutchie platform in your extension's `RemoteModuleRegistry` object.
|
|
193
193
|
|
|
194
|
+
## Meta Fields Function
|
|
195
|
+
|
|
196
|
+
### Overview
|
|
197
|
+
|
|
198
|
+
The `getStoreFrontMetaFields` function is the **recommended approach** for generating page metadata (title tags, meta descriptions, Open Graph tags, etc.) in your theme. This function-based approach replaces the `StoreFrontMeta` component pattern.
|
|
199
|
+
|
|
200
|
+
**Why use this approach:**
|
|
201
|
+
|
|
202
|
+
- ✅ **Full data access** - Direct access to data loaders (products, categories, etc.)
|
|
203
|
+
- ✅ **Async support** - Make API calls to fetch data for dynamic meta tags
|
|
204
|
+
- ✅ **Centralized control** - Single function handles all page metadata logic
|
|
205
|
+
- ✅ **Better performance** - Called once per navigation, not re-rendered on state changes
|
|
206
|
+
|
|
207
|
+
> **🚀 Rollout Status:** The Dutchie platform is currently rolling out support for `getStoreFrontMetaFields` via a feature flag. During the transition period:
|
|
208
|
+
>
|
|
209
|
+
> - When the flag is **enabled**: Your `getStoreFrontMetaFields` function will be used (recommended)
|
|
210
|
+
> - When the flag is **disabled**: The legacy `StoreFrontMeta` component will be used
|
|
211
|
+
> - Both implementations can coexist in your theme during migration
|
|
212
|
+
> - The component approach will be deprecated once the rollout is complete
|
|
213
|
+
|
|
214
|
+
> **Note:** When you provide `getStoreFrontMetaFields` and the feature flag is enabled, the `StoreFrontMeta` component will not be used. We recommend implementing the function approach now to prepare for the full migration
|
|
215
|
+
|
|
216
|
+
### Function Signature
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
export type StoreFrontMetaFieldsFunction = (
|
|
220
|
+
data: CommerceComponentsDataInterface
|
|
221
|
+
) => MetaFields | Promise<MetaFields>;
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
The function receives the full data bridge interface and can return meta fields synchronously or asynchronously.
|
|
225
|
+
|
|
226
|
+
### Meta Fields Type
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
type MetaFields = {
|
|
230
|
+
title?: string; // Page title
|
|
231
|
+
description?: string; // Meta description
|
|
232
|
+
ogImage?: string; // Open Graph image URL
|
|
233
|
+
canonical?: string; // Canonical URL
|
|
234
|
+
structuredData?: Record<string, any>; // JSON-LD structured data
|
|
235
|
+
customMeta?: Array<{
|
|
236
|
+
// Additional meta tags
|
|
237
|
+
name?: string;
|
|
238
|
+
property?: string;
|
|
239
|
+
content: string;
|
|
240
|
+
}>;
|
|
241
|
+
};
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### Implementation
|
|
245
|
+
|
|
246
|
+
Create a file in your theme (e.g., `get-meta-fields.ts`):
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
import {
|
|
250
|
+
CommerceComponentsDataInterface,
|
|
251
|
+
MetaFields,
|
|
252
|
+
} from "@dutchiesdk/ecommerce-extensions-sdk";
|
|
253
|
+
|
|
254
|
+
export const getStoreFrontMetaFields = async (
|
|
255
|
+
data: CommerceComponentsDataInterface
|
|
256
|
+
): Promise<MetaFields> => {
|
|
257
|
+
const { location, dataLoaders } = data;
|
|
258
|
+
const page = typeof window !== "undefined" ? window.location.pathname : "";
|
|
259
|
+
|
|
260
|
+
// Access data loaders for dynamic content
|
|
261
|
+
const product = await dataLoaders.product();
|
|
262
|
+
const categories = await dataLoaders.categories();
|
|
263
|
+
|
|
264
|
+
// Generate page-specific metadata
|
|
265
|
+
let pageTitle = location?.name || "Shop Cannabis";
|
|
266
|
+
let pageDescription = `Browse our selection at ${location?.name}`;
|
|
267
|
+
|
|
268
|
+
if (product) {
|
|
269
|
+
pageTitle = `${product.name} | ${location?.name}`;
|
|
270
|
+
pageDescription = `${product.description?.substring(0, 155)}...`;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return {
|
|
274
|
+
title: pageTitle,
|
|
275
|
+
description: pageDescription,
|
|
276
|
+
ogImage: location?.images?.logo,
|
|
277
|
+
canonical: `${location?.links?.storeFrontRoot}${page}`,
|
|
278
|
+
structuredData: {
|
|
279
|
+
"@context": "https://schema.org",
|
|
280
|
+
"@type": "WebPage",
|
|
281
|
+
name: pageTitle,
|
|
282
|
+
description: pageDescription,
|
|
283
|
+
},
|
|
284
|
+
customMeta: [
|
|
285
|
+
{
|
|
286
|
+
name: "robots",
|
|
287
|
+
content: "index, follow",
|
|
288
|
+
},
|
|
289
|
+
],
|
|
290
|
+
};
|
|
291
|
+
};
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### Register in Module Registry
|
|
295
|
+
|
|
296
|
+
Add the function to your theme's `index.tsx`:
|
|
297
|
+
|
|
298
|
+
```typescript
|
|
299
|
+
import { RemoteModuleRegistry } from "@dutchiesdk/ecommerce-extensions-sdk";
|
|
300
|
+
import { getStoreFrontMetaFields } from "./get-meta-fields";
|
|
301
|
+
|
|
302
|
+
export default {
|
|
303
|
+
StoreFrontHeader: createLazyRemoteBoundaryComponent(() => import("./header")),
|
|
304
|
+
StoreFrontFooter: createLazyRemoteBoundaryComponent(() => import("./footer")),
|
|
305
|
+
getStoreFrontMetaFields, // Register the function
|
|
306
|
+
} satisfies RemoteModuleRegistry;
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### Available Data
|
|
310
|
+
|
|
311
|
+
The function receives the full `CommerceComponentsDataInterface`:
|
|
312
|
+
|
|
313
|
+
```typescript
|
|
314
|
+
{
|
|
315
|
+
menuContext: 'store-front' | 'kiosk',
|
|
316
|
+
location?: Dispensary,
|
|
317
|
+
user?: User,
|
|
318
|
+
cart?: Cart,
|
|
319
|
+
dataLoaders: {
|
|
320
|
+
product: () => Promise<Product | null>,
|
|
321
|
+
products: () => Promise<Product[]>,
|
|
322
|
+
categories: () => Promise<Category[]>,
|
|
323
|
+
// ... other loaders
|
|
324
|
+
},
|
|
325
|
+
actions: {
|
|
326
|
+
// Navigation and cart actions
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
### Example Use Cases
|
|
332
|
+
|
|
333
|
+
**Product Pages:**
|
|
334
|
+
|
|
335
|
+
```typescript
|
|
336
|
+
const product = await dataLoaders.product();
|
|
337
|
+
if (product) {
|
|
338
|
+
return {
|
|
339
|
+
title: `${product.name} - ${location?.name}`,
|
|
340
|
+
description: product.description,
|
|
341
|
+
ogImage: product.images?.[0]?.url,
|
|
342
|
+
structuredData: {
|
|
343
|
+
"@context": "https://schema.org",
|
|
344
|
+
"@type": "Product",
|
|
345
|
+
name: product.name,
|
|
346
|
+
description: product.description,
|
|
347
|
+
offers: {
|
|
348
|
+
"@type": "Offer",
|
|
349
|
+
price: product.variants?.[0]?.priceRec,
|
|
350
|
+
priceCurrency: "USD",
|
|
351
|
+
},
|
|
352
|
+
},
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
**Category Pages:**
|
|
358
|
+
|
|
359
|
+
```typescript
|
|
360
|
+
const categories = await dataLoaders.categories();
|
|
361
|
+
const pathname = window.location.pathname;
|
|
362
|
+
const categorySlug = pathname.split("/").pop();
|
|
363
|
+
const category = categories.find((c) => c.slug === categorySlug);
|
|
364
|
+
|
|
365
|
+
if (category) {
|
|
366
|
+
return {
|
|
367
|
+
title: `${category.name} | ${location?.name}`,
|
|
368
|
+
description: `Shop ${category.name} products at ${location?.name}`,
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
**Home Page:**
|
|
374
|
+
|
|
375
|
+
```typescript
|
|
376
|
+
if (pathname === "/" || pathname === "") {
|
|
377
|
+
return {
|
|
378
|
+
title: `${location?.name} - Cannabis Dispensary`,
|
|
379
|
+
description: `Shop cannabis products online at ${location?.name}`,
|
|
380
|
+
structuredData: {
|
|
381
|
+
"@context": "https://schema.org",
|
|
382
|
+
"@type": "Organization",
|
|
383
|
+
name: location?.name,
|
|
384
|
+
url: location?.links?.storeFrontRoot,
|
|
385
|
+
logo: location?.images?.logo,
|
|
386
|
+
},
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
### Notes
|
|
392
|
+
|
|
393
|
+
- The function is called on every page navigation
|
|
394
|
+
- Async operations (like `dataLoaders.product()`) are supported
|
|
395
|
+
- Return values are rendered into `<head>` tags automatically
|
|
396
|
+
- The title tag will update dynamically as meta fields load
|
|
397
|
+
- All fields are optional - return only what's needed for each page
|
|
398
|
+
|
|
399
|
+
### Migration Strategy
|
|
400
|
+
|
|
401
|
+
If you have an existing theme using the `StoreFrontMeta` component:
|
|
402
|
+
|
|
403
|
+
1. **Implement the function**: Add `getStoreFrontMetaFields` to your theme's module registry
|
|
404
|
+
2. **Test both approaches**: Keep your existing `StoreFrontMeta` component during the transition
|
|
405
|
+
3. **Feature flag controls behavior**: Dutchie's feature flag determines which implementation is used
|
|
406
|
+
4. **Future-proof**: Once the rollout completes, only the function approach will be supported
|
|
407
|
+
|
|
194
408
|
## Best Practices
|
|
195
409
|
|
|
196
410
|
### Data Loading
|
|
@@ -203,7 +417,7 @@ const MyComponent = () => {
|
|
|
203
417
|
const { data, isLoading } = useAsyncLoader(dataLoaders.products);
|
|
204
418
|
|
|
205
419
|
if (isLoading) return <LoadingSpinner />;
|
|
206
|
-
if (!data) return <EmptyProducts message=
|
|
420
|
+
if (!data) return <EmptyProducts message='No products found' />;
|
|
207
421
|
|
|
208
422
|
return <ProductList products={data} />;
|
|
209
423
|
};
|
|
@@ -249,7 +463,7 @@ const UserProfile = () => {
|
|
|
249
463
|
|
|
250
464
|
if (!user) {
|
|
251
465
|
return (
|
|
252
|
-
<div className=
|
|
466
|
+
<div className='login-prompt'>
|
|
253
467
|
<p>Please log in to view your profile</p>
|
|
254
468
|
<button onClick={actions.goToLogin}>Login</button>
|
|
255
469
|
</div>
|
|
@@ -257,7 +471,7 @@ const UserProfile = () => {
|
|
|
257
471
|
}
|
|
258
472
|
|
|
259
473
|
return (
|
|
260
|
-
<div className=
|
|
474
|
+
<div className='user-profile'>
|
|
261
475
|
<h2>Welcome, {user.firstName}!</h2>
|
|
262
476
|
{/* Profile content */}
|
|
263
477
|
</div>
|
|
@@ -30,7 +30,7 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
30
30
|
useDataBridge: ()=>useDataBridge
|
|
31
31
|
});
|
|
32
32
|
const external_react_namespaceObject = require("react");
|
|
33
|
-
const DataBridgeVersion = '0.
|
|
33
|
+
const DataBridgeVersion = '0.17.0';
|
|
34
34
|
const DataBridgeContext = (0, external_react_namespaceObject.createContext)(void 0);
|
|
35
35
|
const useDataBridge = ()=>{
|
|
36
36
|
const context = (0, external_react_namespaceObject.useContext)(DataBridgeContext);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createContext, useContext, useEffect, useState } from "react";
|
|
2
|
-
const DataBridgeVersion = '0.
|
|
2
|
+
const DataBridgeVersion = '0.17.0';
|
|
3
3
|
const DataBridgeContext = createContext(void 0);
|
|
4
4
|
const useDataBridge = ()=>{
|
|
5
5
|
const context = useContext(DataBridgeContext);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type React from 'react';
|
|
2
2
|
import type { Events } from './events';
|
|
3
|
+
import type { CommerceComponentsDataInterface } from './interface';
|
|
3
4
|
export type RemoteBoundaryComponent<P = {}> = React.FC<P> & {
|
|
4
5
|
DataBridgeVersion: string;
|
|
5
6
|
};
|
|
@@ -7,6 +8,36 @@ export type RoutablePageRegistryEntry = {
|
|
|
7
8
|
component: RemoteBoundaryComponent;
|
|
8
9
|
path: string;
|
|
9
10
|
};
|
|
11
|
+
/**
|
|
12
|
+
* Meta fields for page metadata (SEO, social sharing, etc.)
|
|
13
|
+
*/
|
|
14
|
+
export type MetaFields = {
|
|
15
|
+
/** Page title (rendered in <title> tag) */
|
|
16
|
+
title?: string;
|
|
17
|
+
/** Page description (rendered in <meta name="description"> tag) */
|
|
18
|
+
description?: string;
|
|
19
|
+
/** Open Graph image URL */
|
|
20
|
+
ogImage?: string;
|
|
21
|
+
/** Canonical URL */
|
|
22
|
+
canonical?: string;
|
|
23
|
+
/** JSON-LD structured data for rich snippets */
|
|
24
|
+
structuredData?: Record<string, any>;
|
|
25
|
+
/** Additional custom meta tags */
|
|
26
|
+
customMeta?: Array<{
|
|
27
|
+
name?: string;
|
|
28
|
+
property?: string;
|
|
29
|
+
content: string;
|
|
30
|
+
}>;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Function that returns meta fields for the current page.
|
|
34
|
+
* Receives the data bridge context as a parameter, allowing access to
|
|
35
|
+
* data loaders, cart, user, location, and actions.
|
|
36
|
+
*
|
|
37
|
+
* @param data - The commerce components data interface with loaders and state
|
|
38
|
+
* @returns Meta fields for the current page (title, description, OG tags, etc.)
|
|
39
|
+
*/
|
|
40
|
+
export type StoreFrontMetaFieldsFunction = (data: CommerceComponentsDataInterface) => MetaFields | Promise<MetaFields>;
|
|
10
41
|
export type RemoteModuleRegistry = {
|
|
11
42
|
RouteablePages?: RoutablePageRegistryEntry[];
|
|
12
43
|
StoreFrontHeader?: RemoteBoundaryComponent;
|
|
@@ -17,5 +48,13 @@ export type RemoteModuleRegistry = {
|
|
|
17
48
|
StoreFrontHero?: RemoteBoundaryComponent;
|
|
18
49
|
ProductDetailsMeta?: RemoteBoundaryComponent;
|
|
19
50
|
ProductDetailsPrimary?: RemoteBoundaryComponent;
|
|
51
|
+
/**
|
|
52
|
+
* Function that provides meta fields for the current page.
|
|
53
|
+
* Replaces the StoreFrontMeta component approach.
|
|
54
|
+
*
|
|
55
|
+
* Called by the host app with the full data bridge context:
|
|
56
|
+
* `const metaFields = registry.getStoreFrontMetaFields?.(dataBridgeData)`
|
|
57
|
+
*/
|
|
58
|
+
getStoreFrontMetaFields?: StoreFrontMetaFieldsFunction;
|
|
20
59
|
events?: Events;
|
|
21
60
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type React from 'react';
|
|
2
2
|
import type { Events } from './events';
|
|
3
|
+
import type { CommerceComponentsDataInterface } from './interface';
|
|
3
4
|
export type RemoteBoundaryComponent<P = {}> = React.FC<P> & {
|
|
4
5
|
DataBridgeVersion: string;
|
|
5
6
|
};
|
|
@@ -7,6 +8,36 @@ export type RoutablePageRegistryEntry = {
|
|
|
7
8
|
component: RemoteBoundaryComponent;
|
|
8
9
|
path: string;
|
|
9
10
|
};
|
|
11
|
+
/**
|
|
12
|
+
* Meta fields for page metadata (SEO, social sharing, etc.)
|
|
13
|
+
*/
|
|
14
|
+
export type MetaFields = {
|
|
15
|
+
/** Page title (rendered in <title> tag) */
|
|
16
|
+
title?: string;
|
|
17
|
+
/** Page description (rendered in <meta name="description"> tag) */
|
|
18
|
+
description?: string;
|
|
19
|
+
/** Open Graph image URL */
|
|
20
|
+
ogImage?: string;
|
|
21
|
+
/** Canonical URL */
|
|
22
|
+
canonical?: string;
|
|
23
|
+
/** JSON-LD structured data for rich snippets */
|
|
24
|
+
structuredData?: Record<string, any>;
|
|
25
|
+
/** Additional custom meta tags */
|
|
26
|
+
customMeta?: Array<{
|
|
27
|
+
name?: string;
|
|
28
|
+
property?: string;
|
|
29
|
+
content: string;
|
|
30
|
+
}>;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Function that returns meta fields for the current page.
|
|
34
|
+
* Receives the data bridge context as a parameter, allowing access to
|
|
35
|
+
* data loaders, cart, user, location, and actions.
|
|
36
|
+
*
|
|
37
|
+
* @param data - The commerce components data interface with loaders and state
|
|
38
|
+
* @returns Meta fields for the current page (title, description, OG tags, etc.)
|
|
39
|
+
*/
|
|
40
|
+
export type StoreFrontMetaFieldsFunction = (data: CommerceComponentsDataInterface) => MetaFields | Promise<MetaFields>;
|
|
10
41
|
export type RemoteModuleRegistry = {
|
|
11
42
|
RouteablePages?: RoutablePageRegistryEntry[];
|
|
12
43
|
StoreFrontHeader?: RemoteBoundaryComponent;
|
|
@@ -17,5 +48,13 @@ export type RemoteModuleRegistry = {
|
|
|
17
48
|
StoreFrontHero?: RemoteBoundaryComponent;
|
|
18
49
|
ProductDetailsMeta?: RemoteBoundaryComponent;
|
|
19
50
|
ProductDetailsPrimary?: RemoteBoundaryComponent;
|
|
51
|
+
/**
|
|
52
|
+
* Function that provides meta fields for the current page.
|
|
53
|
+
* Replaces the StoreFrontMeta component approach.
|
|
54
|
+
*
|
|
55
|
+
* Called by the host app with the full data bridge context:
|
|
56
|
+
* `const metaFields = registry.getStoreFrontMetaFields?.(dataBridgeData)`
|
|
57
|
+
*/
|
|
58
|
+
getStoreFrontMetaFields?: StoreFrontMetaFieldsFunction;
|
|
20
59
|
events?: Events;
|
|
21
60
|
};
|