@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="No products found" />;
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="login-prompt">
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="user-profile">
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.16.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.16.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
  };
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.17.0",
8
8
  "license": "MIT",
9
9
  "type": "module",
10
10
  "module": "./dist/esm/index.js",