@dutchiesdk/ecommerce-extensions-sdk 0.16.0 → 0.18.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
@@ -26,11 +26,7 @@ yarn add @dutchie/ecommerce-extensions-sdk
26
26
 
27
27
  ```tsx
28
28
  import React from "react";
29
- import {
30
- useDataBridge,
31
- RemoteBoundaryComponent,
32
- DataBridgeVersion,
33
- } from "@dutchie/ecommerce-extensions-sdk";
29
+ import { useDataBridge, RemoteBoundaryComponent, DataBridgeVersion } from "@dutchie/ecommerce-extensions-sdk";
34
30
 
35
31
  const MyExtension: RemoteBoundaryComponent = () => {
36
32
  const { dataLoaders, actions, location, user, cart } = useDataBridge();
@@ -78,10 +74,7 @@ const MyComponent = () => {
78
74
  A utility hook for handling async data loading with loading states.
79
75
 
80
76
  ```tsx
81
- import {
82
- useAsyncLoader,
83
- useDataBridge,
84
- } from "@dutchie/ecommerce-extensions-sdk";
77
+ import { useAsyncLoader, useDataBridge } from "@dutchie/ecommerce-extensions-sdk";
85
78
 
86
79
  const ProductList = () => {
87
80
  const { dataLoaders } = useDataBridge();
@@ -125,11 +118,7 @@ All extension components that integrate with the Dutchie platform should satisfy
125
118
  **Example:**
126
119
 
127
120
  ```tsx
128
- import {
129
- RemoteBoundaryComponent,
130
- useDataBridge,
131
- DataBridgeVersion,
132
- } from "@dutchie/ecommerce-extensions-sdk";
121
+ import { RemoteBoundaryComponent, useDataBridge, DataBridgeVersion } from "@dutchie/ecommerce-extensions-sdk";
133
122
 
134
123
  const MyCustomHeader: RemoteBoundaryComponent = () => {
135
124
  const { location, user, actions } = useDataBridge();
@@ -137,11 +126,7 @@ const MyCustomHeader: RemoteBoundaryComponent = () => {
137
126
  return (
138
127
  <header>
139
128
  <h1>{location?.name}</h1>
140
- {user ? (
141
- <span>Welcome, {user.firstName}!</span>
142
- ) : (
143
- <button onClick={actions.goToLogin}>Login</button>
144
- )}
129
+ {user ? <span>Welcome, {user.firstName}!</span> : <button onClick={actions.goToLogin}>Login</button>}
145
130
  </header>
146
131
  );
147
132
  };
@@ -191,6 +176,231 @@ const currentLocations = await dataLoaders.locations(); // Current context locat
191
176
 
192
177
  You can register callback functions that will be triggered by certain events in the Dutchie platform in your extension's `RemoteModuleRegistry` object.
193
178
 
179
+ ## Meta Fields Function
180
+
181
+ ### Overview
182
+
183
+ 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.
184
+
185
+ **Why use this approach:**
186
+
187
+ - ✅ **Full data access** - Direct access to data loaders (products, categories, etc.)
188
+ - ✅ **Async support** - Make API calls to fetch data for dynamic meta tags
189
+ - ✅ **Centralized control** - Single function handles all page metadata logic
190
+ - ✅ **Better performance** - Called once per navigation, not re-rendered on state changes
191
+
192
+ > **🚀 Rollout Status:** The Dutchie platform is currently rolling out support for `getStoreFrontMetaFields` via a feature flag. During the transition period:
193
+ >
194
+ > - When the flag is **enabled**: Your `getStoreFrontMetaFields` function will be used (recommended)
195
+ > - When the flag is **disabled**: The legacy `StoreFrontMeta` component will be used
196
+ > - Both implementations can coexist in your theme during migration
197
+ > - The component approach will be deprecated once the rollout is complete
198
+
199
+ > **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
200
+
201
+ ### Function Signature
202
+
203
+ ```typescript
204
+ export type StoreFrontMetaFieldsFunction = (data: CommerceComponentsDataInterface) => MetaFields | Promise<MetaFields>;
205
+ ```
206
+
207
+ The function receives the full data bridge interface and can return meta fields synchronously or asynchronously.
208
+
209
+ ### Meta Fields Type
210
+
211
+ ```typescript
212
+ type MetaFields = {
213
+ title?: string; // Page title
214
+ description?: string; // Meta description
215
+ ogImage?: string; // Open Graph image URL
216
+ canonical?: string; // Canonical URL
217
+ structuredData?: Record<string, any>; // JSON-LD structured data
218
+ customMeta?: Array<{
219
+ // Additional meta tags
220
+ name?: string;
221
+ property?: string;
222
+ content: string;
223
+ }>;
224
+ };
225
+ ```
226
+
227
+ ### Implementation
228
+
229
+ Create a file in your theme (e.g., `get-meta-fields.ts`):
230
+
231
+ ```typescript
232
+ import { CommerceComponentsDataInterface, MetaFields } from "@dutchiesdk/ecommerce-extensions-sdk";
233
+
234
+ export const getStoreFrontMetaFields = async (data: CommerceComponentsDataInterface): Promise<MetaFields> => {
235
+ const { location, dataLoaders } = data;
236
+ const page = typeof window !== "undefined" ? window.location.pathname : "";
237
+
238
+ // Access data loaders for dynamic content
239
+ const product = await dataLoaders.product();
240
+ const categories = await dataLoaders.categories();
241
+
242
+ // Generate page-specific metadata
243
+ let pageTitle = location?.name || "Shop Cannabis";
244
+ let pageDescription = `Browse our selection at ${location?.name}`;
245
+
246
+ if (product) {
247
+ pageTitle = `${product.name} | ${location?.name}`;
248
+ pageDescription = `${product.description?.substring(0, 155)}...`;
249
+ }
250
+
251
+ return {
252
+ title: pageTitle,
253
+ description: pageDescription,
254
+ ogImage: location?.images?.logo,
255
+ canonical: `${location?.links?.storeFrontRoot}${page}`,
256
+ structuredData: {
257
+ "@context": "https://schema.org",
258
+ "@type": "WebPage",
259
+ name: pageTitle,
260
+ description: pageDescription,
261
+ },
262
+ customMeta: [
263
+ {
264
+ name: "robots",
265
+ content: "index, follow",
266
+ },
267
+ ],
268
+ };
269
+ };
270
+ ```
271
+
272
+ ### Register in Module Registry
273
+
274
+ Add the function to your theme's `index.tsx`:
275
+
276
+ ```typescript
277
+ import { RemoteModuleRegistry } from "@dutchiesdk/ecommerce-extensions-sdk";
278
+ import { getStoreFrontMetaFields } from "./get-meta-fields";
279
+
280
+ export default {
281
+ StoreFrontHeader: createLazyRemoteBoundaryComponent(() => import("./header")),
282
+ StoreFrontFooter: createLazyRemoteBoundaryComponent(() => import("./footer")),
283
+ getStoreFrontMetaFields, // Register the function
284
+ } satisfies RemoteModuleRegistry;
285
+ ```
286
+
287
+ You may also register components for only specific menu contexts:
288
+
289
+ ```typescript
290
+ import { RemoteModuleRegistry } from "@dutchiesdk/ecommerce-extensions-sdk";
291
+
292
+ export default {
293
+ StoreFrontHeader: {
294
+ // use one component for storefront and a different one for kiosk
295
+ 'store-front': createLazyRemoteBoundaryComponent(() => import("./store-header")),
296
+ kiosk: createLazyRemoteBoundaryComponent(() => import("./kiosk-header")),
297
+ },
298
+ StoreFrontFooter: {
299
+ // only override storefront but not kiosk, kiosk will have default dutchie footer
300
+ 'store-front': createLazyRemoteBoundaryComponent(() => import("./footer")),
301
+ },
302
+ } satisfies RemoteModuleRegistry;
303
+ ```
304
+
305
+ ### Available Data
306
+
307
+ The function receives the full `CommerceComponentsDataInterface`:
308
+
309
+ ```typescript
310
+ {
311
+ menuContext: 'store-front' | 'kiosk',
312
+ location?: Dispensary,
313
+ user?: User,
314
+ cart?: Cart,
315
+ dataLoaders: {
316
+ product: () => Promise<Product | null>,
317
+ products: () => Promise<Product[]>,
318
+ categories: () => Promise<Category[]>,
319
+ // ... other loaders
320
+ },
321
+ actions: {
322
+ // Navigation and cart actions
323
+ }
324
+ }
325
+ ```
326
+
327
+ ### Example Use Cases
328
+
329
+ **Product Pages:**
330
+
331
+ ```typescript
332
+ const product = await dataLoaders.product();
333
+ if (product) {
334
+ return {
335
+ title: `${product.name} - ${location?.name}`,
336
+ description: product.description,
337
+ ogImage: product.images?.[0]?.url,
338
+ structuredData: {
339
+ "@context": "https://schema.org",
340
+ "@type": "Product",
341
+ name: product.name,
342
+ description: product.description,
343
+ offers: {
344
+ "@type": "Offer",
345
+ price: product.variants?.[0]?.priceRec,
346
+ priceCurrency: "USD",
347
+ },
348
+ },
349
+ };
350
+ }
351
+ ```
352
+
353
+ **Category Pages:**
354
+
355
+ ```typescript
356
+ const categories = await dataLoaders.categories();
357
+ const pathname = window.location.pathname;
358
+ const categorySlug = pathname.split("/").pop();
359
+ const category = categories.find((c) => c.slug === categorySlug);
360
+
361
+ if (category) {
362
+ return {
363
+ title: `${category.name} | ${location?.name}`,
364
+ description: `Shop ${category.name} products at ${location?.name}`,
365
+ };
366
+ }
367
+ ```
368
+
369
+ **Home Page:**
370
+
371
+ ```typescript
372
+ if (pathname === "/" || pathname === "") {
373
+ return {
374
+ title: `${location?.name} - Cannabis Dispensary`,
375
+ description: `Shop cannabis products online at ${location?.name}`,
376
+ structuredData: {
377
+ "@context": "https://schema.org",
378
+ "@type": "Organization",
379
+ name: location?.name,
380
+ url: location?.links?.storeFrontRoot,
381
+ logo: location?.images?.logo,
382
+ },
383
+ };
384
+ }
385
+ ```
386
+
387
+ ### Notes
388
+
389
+ - The function is called on every page navigation
390
+ - Async operations (like `dataLoaders.product()`) are supported
391
+ - Return values are rendered into `<head>` tags automatically
392
+ - The title tag will update dynamically as meta fields load
393
+ - All fields are optional - return only what's needed for each page
394
+
395
+ ### Migration Strategy
396
+
397
+ If you have an existing theme using the `StoreFrontMeta` component:
398
+
399
+ 1. **Implement the function**: Add `getStoreFrontMetaFields` to your theme's module registry
400
+ 2. **Test both approaches**: Keep your existing `StoreFrontMeta` component during the transition
401
+ 3. **Feature flag controls behavior**: Dutchie's feature flag determines which implementation is used
402
+ 4. **Future-proof**: Once the rollout completes, only the function approach will be supported
403
+
194
404
  ## Best Practices
195
405
 
196
406
  ### Data Loading
@@ -218,11 +428,7 @@ Any uncaught errors will be caught by a Dutchie error boundary, and the fallback
218
428
  Leverage the provided types for better development experience:
219
429
 
220
430
  ```tsx
221
- import {
222
- Product,
223
- Category,
224
- useDataBridge,
225
- } from "@dutchie/ecommerce-extensions-sdk";
431
+ import { Product, Category, useDataBridge } from "@dutchie/ecommerce-extensions-sdk";
226
432
 
227
433
  interface ProductFilterProps {
228
434
  products: Product[];
@@ -230,11 +436,7 @@ interface ProductFilterProps {
230
436
  onFilterChange: (categoryId: string) => void;
231
437
  }
232
438
 
233
- const ProductFilter: React.FC<ProductFilterProps> = ({
234
- products,
235
- categories,
236
- onFilterChange,
237
- }) => {
439
+ const ProductFilter: React.FC<ProductFilterProps> = ({ products, categories, onFilterChange }) => {
238
440
  // Implementation
239
441
  };
240
442
  ```
@@ -312,3 +514,11 @@ For technical support and questions about the Dutchie Ecommerce Extensions SDK:
312
514
  - 📧 Contact your Dutchie agency partner representative
313
515
  - 📚 Refer to the Dutchie Pro platform documentation
314
516
  - 🐛 Report issues through the official Dutchie support channels
517
+
518
+ ## Updates
519
+
520
+ Stay up to date with this SDK by checking here for changes, and updating to the latest version with:
521
+
522
+ ```bash
523
+ npm install @dutchiesdk/ecommerce-extensions-sdk@latest
524
+ ```
@@ -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.16.0';
33
+ const DataBridgeVersion = '0.18.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.16.0';
2
+ const DataBridgeVersion = '0.18.0';
3
3
  const DataBridgeContext = createContext(void 0);
4
4
  const useDataBridge = ()=>{
5
5
  const context = useContext(DataBridgeContext);
@@ -1,21 +1,71 @@
1
1
  import type React from 'react';
2
+ import type { MenuContext } from './data';
2
3
  import type { Events } from './events';
4
+ import type { CommerceComponentsDataInterface } from './interface';
3
5
  export type RemoteBoundaryComponent<P = {}> = React.FC<P> & {
4
6
  DataBridgeVersion: string;
5
7
  };
8
+ export type MenuSpecificRemoteComponent = {
9
+ [key in MenuContext]?: RemoteBoundaryComponent;
10
+ };
11
+ export type ModuleRegistryEntry = MenuSpecificRemoteComponent | RemoteBoundaryComponent;
6
12
  export type RoutablePageRegistryEntry = {
7
13
  component: RemoteBoundaryComponent;
8
14
  path: string;
9
15
  };
16
+ /**
17
+ * Meta fields for page metadata (SEO, social sharing, etc.)
18
+ */
19
+ export type MetaFields = {
20
+ /** Page title (rendered in <title> tag) */
21
+ title?: string;
22
+ /** Page description (rendered in <meta name="description"> tag) */
23
+ description?: string;
24
+ /** Open Graph image URL */
25
+ ogImage?: string;
26
+ /** Canonical URL */
27
+ canonical?: string;
28
+ /** JSON-LD structured data for rich snippets */
29
+ structuredData?: Record<string, any>;
30
+ /** Additional custom meta tags */
31
+ customMeta?: Array<{
32
+ name?: string;
33
+ property?: string;
34
+ content: string;
35
+ }>;
36
+ };
37
+ /**
38
+ * Function that returns meta fields for the current page.
39
+ * Receives the data bridge context as a parameter, allowing access to
40
+ * data loaders, cart, user, location, and actions.
41
+ *
42
+ * @param data - The commerce components data interface with loaders and state
43
+ * @returns Meta fields for the current page (title, description, OG tags, etc.)
44
+ */
45
+ export type StoreFrontMetaFieldsFunction = (data: CommerceComponentsDataInterface) => MetaFields | Promise<MetaFields>;
10
46
  export type RemoteModuleRegistry = {
11
47
  RouteablePages?: RoutablePageRegistryEntry[];
12
- StoreFrontHeader?: RemoteBoundaryComponent;
48
+ StoreFrontHeader?: ModuleRegistryEntry;
49
+ StoreFrontNavigation?: ModuleRegistryEntry;
50
+ StoreFrontFooter?: ModuleRegistryEntry;
51
+ StoreFrontCarouselInterstitials?: ModuleRegistryEntry[];
52
+ StoreFrontHero?: ModuleRegistryEntry;
53
+ ProductDetailsPrimary?: ModuleRegistryEntry;
54
+ /**
55
+ * Function that provides meta fields for the current page.
56
+ * Replaces the StoreFrontMeta component approach.
57
+ *
58
+ * Called by the host app with the full data bridge context:
59
+ * `const metaFields = registry.getStoreFrontMetaFields?.(dataBridgeData)`
60
+ */
61
+ getStoreFrontMetaFields?: StoreFrontMetaFieldsFunction;
62
+ events?: Events;
63
+ /**
64
+ * @deprecated Use getStoreFrontMetaFields instead
65
+ */
13
66
  StoreFrontMeta?: RemoteBoundaryComponent;
14
- StoreFrontNavigation?: RemoteBoundaryComponent;
15
- StoreFrontFooter?: RemoteBoundaryComponent;
16
- StoreFrontCarouselInterstitials?: RemoteBoundaryComponent[];
17
- StoreFrontHero?: RemoteBoundaryComponent;
67
+ /**
68
+ * @deprecated Use getStoreFrontMetaFields instead
69
+ */
18
70
  ProductDetailsMeta?: RemoteBoundaryComponent;
19
- ProductDetailsPrimary?: RemoteBoundaryComponent;
20
- events?: Events;
21
71
  };
@@ -1,21 +1,71 @@
1
1
  import type React from 'react';
2
+ import type { MenuContext } from './data';
2
3
  import type { Events } from './events';
4
+ import type { CommerceComponentsDataInterface } from './interface';
3
5
  export type RemoteBoundaryComponent<P = {}> = React.FC<P> & {
4
6
  DataBridgeVersion: string;
5
7
  };
8
+ export type MenuSpecificRemoteComponent = {
9
+ [key in MenuContext]?: RemoteBoundaryComponent;
10
+ };
11
+ export type ModuleRegistryEntry = MenuSpecificRemoteComponent | RemoteBoundaryComponent;
6
12
  export type RoutablePageRegistryEntry = {
7
13
  component: RemoteBoundaryComponent;
8
14
  path: string;
9
15
  };
16
+ /**
17
+ * Meta fields for page metadata (SEO, social sharing, etc.)
18
+ */
19
+ export type MetaFields = {
20
+ /** Page title (rendered in <title> tag) */
21
+ title?: string;
22
+ /** Page description (rendered in <meta name="description"> tag) */
23
+ description?: string;
24
+ /** Open Graph image URL */
25
+ ogImage?: string;
26
+ /** Canonical URL */
27
+ canonical?: string;
28
+ /** JSON-LD structured data for rich snippets */
29
+ structuredData?: Record<string, any>;
30
+ /** Additional custom meta tags */
31
+ customMeta?: Array<{
32
+ name?: string;
33
+ property?: string;
34
+ content: string;
35
+ }>;
36
+ };
37
+ /**
38
+ * Function that returns meta fields for the current page.
39
+ * Receives the data bridge context as a parameter, allowing access to
40
+ * data loaders, cart, user, location, and actions.
41
+ *
42
+ * @param data - The commerce components data interface with loaders and state
43
+ * @returns Meta fields for the current page (title, description, OG tags, etc.)
44
+ */
45
+ export type StoreFrontMetaFieldsFunction = (data: CommerceComponentsDataInterface) => MetaFields | Promise<MetaFields>;
10
46
  export type RemoteModuleRegistry = {
11
47
  RouteablePages?: RoutablePageRegistryEntry[];
12
- StoreFrontHeader?: RemoteBoundaryComponent;
48
+ StoreFrontHeader?: ModuleRegistryEntry;
49
+ StoreFrontNavigation?: ModuleRegistryEntry;
50
+ StoreFrontFooter?: ModuleRegistryEntry;
51
+ StoreFrontCarouselInterstitials?: ModuleRegistryEntry[];
52
+ StoreFrontHero?: ModuleRegistryEntry;
53
+ ProductDetailsPrimary?: ModuleRegistryEntry;
54
+ /**
55
+ * Function that provides meta fields for the current page.
56
+ * Replaces the StoreFrontMeta component approach.
57
+ *
58
+ * Called by the host app with the full data bridge context:
59
+ * `const metaFields = registry.getStoreFrontMetaFields?.(dataBridgeData)`
60
+ */
61
+ getStoreFrontMetaFields?: StoreFrontMetaFieldsFunction;
62
+ events?: Events;
63
+ /**
64
+ * @deprecated Use getStoreFrontMetaFields instead
65
+ */
13
66
  StoreFrontMeta?: RemoteBoundaryComponent;
14
- StoreFrontNavigation?: RemoteBoundaryComponent;
15
- StoreFrontFooter?: RemoteBoundaryComponent;
16
- StoreFrontCarouselInterstitials?: RemoteBoundaryComponent[];
17
- StoreFrontHero?: RemoteBoundaryComponent;
67
+ /**
68
+ * @deprecated Use getStoreFrontMetaFields instead
69
+ */
18
70
  ProductDetailsMeta?: RemoteBoundaryComponent;
19
- ProductDetailsPrimary?: RemoteBoundaryComponent;
20
- events?: Events;
21
71
  };
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
7
- "version": "0.16.0",
7
+ "version": "0.18.0",
8
8
  "license": "MIT",
9
9
  "type": "module",
10
10
  "module": "./dist/esm/index.js",