@dutchiesdk/ecommerce-extensions-sdk 0.6.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 ADDED
@@ -0,0 +1,868 @@
1
+
2
+
3
+ ![Dutchie Logo](https://cdn.prod.website-files.com/61930755e474060ca6298871/64a32a14b7840d452f594fbc_Logo.svg)
4
+
5
+ # @dutchiesdk/ecommerce-extensions-sdk
6
+
7
+ A comprehensive TypeScript SDK for building custom extensions that run on the Dutchie Ecommerce Pro platform. This SDK provides certified agency partners with the tools needed to create powerful, integrated e-commerce experiences for cannabis retailers.
8
+
9
+ > ⚠️ **Alpha Release Warning**
10
+ >
11
+ > This SDK is currently in **alpha** and is subject to breaking changes. APIs, types, and functionality may change significantly between versions. Please use with caution in production environments and be prepared to update your extensions as the SDK evolves.
12
+
13
+ ## Development Environment Access
14
+
15
+ This SDK requires a **Dutchie-provided development environment** to build, test, and deploy extensions. The SDK alone is not sufficient for development - you must have access to the complete Dutchie Pro development and deployment infrastructure.
16
+
17
+ **To request access to the development environment:**
18
+ 📧 Contact: **partners@dutchie.com**
19
+
20
+ Please include your agency information and intended use case when requesting access.
21
+
22
+ ## Table of Contents
23
+
24
+ - [Installation](#installation)
25
+ - [Quick Start](#quick-start)
26
+ - [Core Concepts](#core-concepts)
27
+ - [React Context & Hooks](#react-context--hooks)
28
+ - [Data Types Reference](#data-types-reference)
29
+ - [Extension Components](#extension-components)
30
+ - [Actions API](#actions-api)
31
+ - [Data Loaders](#data-loaders)
32
+ - [Examples](#examples)
33
+ - [Best Practices](#best-practices)
34
+ - [Development](#development)
35
+
36
+ ## Installation
37
+
38
+ ```bash
39
+ npm install @dutchie/ecommerce-extensions-sdk
40
+ # or
41
+ yarn add @dutchie/ecommerce-extensions-sdk
42
+ # or
43
+ pnpm add @dutchie/ecommerce-extensions-sdk
44
+ ```
45
+
46
+ ## Quick Start
47
+
48
+ ```tsx
49
+ import React from 'react';
50
+ import { useDataBridge, RemoteBoundaryComponent } from '@dutchie/ecommerce-extensions-sdk';
51
+
52
+ const MyExtension: RemoteBoundaryComponent = () => {
53
+ const { dataLoaders, actions, location, user, cart } = useDataBridge();
54
+
55
+ return (
56
+ <div>
57
+ <h1>Welcome to {location?.name}</h1>
58
+ <p>Cart items: {cart?.items.length || 0}</p>
59
+ </div>
60
+ );
61
+ };
62
+
63
+ MyExtension.DataBridgeVersion = '0.2';
64
+
65
+ export default MyExtension;
66
+ ```
67
+
68
+ ## Core Concepts
69
+
70
+ The Dutchie Ecommerce Extensions SDK is built around several key concepts:
71
+
72
+ 1. **Data Bridge**: A unified interface for accessing dispensary data, user information, and cart state
73
+ 2. **Actions**: Pre-built navigation and cart management functions
74
+ 3. **Remote Components**: Extension components that integrate seamlessly with the Dutchie platform
75
+ 4. **Context-Driven Architecture**: React context provides data and actions throughout your extension
76
+
77
+ ## React Context & Hooks
78
+
79
+ ### `useDataBridge()`
80
+
81
+ The primary hook for accessing the Dutchie platform data and functionality.
82
+
83
+ ```tsx
84
+ import { useDataBridge } from '@dutchie/ecommerce-extensions-sdk';
85
+
86
+ const MyComponent = () => {
87
+ const {
88
+ menuContext, // 'store-front' | 'kiosk'
89
+ location, // Current dispensary information
90
+ user, // Authenticated user data
91
+ cart, // Current cart state
92
+ dataLoaders, // Async data loading functions
93
+ actions // Navigation and cart actions
94
+ } = useDataBridge();
95
+
96
+ // Component logic here
97
+ };
98
+ ```
99
+
100
+ **Throws**: Error if used outside of a `DataBridgeProvider`
101
+
102
+ ### `useAsyncLoader(fn)`
103
+
104
+ A utility hook for handling async data loading with loading states.
105
+
106
+ ```tsx
107
+ import { useAsyncLoader, useDataBridge } from '@dutchie/ecommerce-extensions-sdk';
108
+
109
+ const ProductList = () => {
110
+ const { dataLoaders } = useDataBridge();
111
+ const { data: products, isLoading } = useAsyncLoader(dataLoaders.products);
112
+
113
+ if (isLoading) return <div>Loading products...</div>;
114
+
115
+ return (
116
+ <div>
117
+ {products?.map(product => (
118
+ <div key={product.id}>{product.name}</div>
119
+ ))}
120
+ </div>
121
+ );
122
+ };
123
+ ```
124
+
125
+ **Parameters:**
126
+ - `fn`: `() => Promise<unknown>` - Async function to execute
127
+
128
+ **Returns:**
129
+ - `data`: The resolved data or `null` while loading
130
+ - `isLoading`: Boolean indicating loading state
131
+
132
+ ## Data Types Reference
133
+
134
+ ### Core Platform Types
135
+
136
+ #### `CommerceComponentsDataInterface`
137
+
138
+ The main interface providing access to all platform data and functionality.
139
+
140
+ ```typescript
141
+ interface CommerceComponentsDataInterface {
142
+ menuContext: 'store-front' | 'kiosk';
143
+ location?: Dispensary;
144
+ user?: User;
145
+ cart?: Cart;
146
+ dataLoaders: DataLoaders;
147
+ actions: Actions;
148
+ }
149
+ ```
150
+
151
+ #### `Dispensary`
152
+
153
+ Complete dispensary information including location, hours, and capabilities.
154
+
155
+ ```typescript
156
+ interface Dispensary {
157
+ id: string;
158
+ status: string;
159
+ name: string;
160
+ cname: string; // URL-friendly name
161
+ chain: string;
162
+ phone: string;
163
+ email: string;
164
+ hours: DispensaryHoursSettings;
165
+ orderTypes: DispensaryOrderTypes;
166
+ images: {
167
+ logo: string;
168
+ };
169
+ address: DispensaryAddress;
170
+ links: {
171
+ website: string;
172
+ storeFrontRoot: string;
173
+ };
174
+ orderTypesConfig: OrderTypesConfigV2;
175
+ }
176
+ ```
177
+
178
+ #### `Product`
179
+
180
+ Product information with essential e-commerce data.
181
+
182
+ ```typescript
183
+ interface Product {
184
+ id: string;
185
+ name: string;
186
+ cname: string; // URL-friendly name
187
+ price: number;
188
+ image: string;
189
+ description: string;
190
+ }
191
+ ```
192
+
193
+ #### `User`
194
+
195
+ Authenticated user information.
196
+
197
+ ```typescript
198
+ interface User {
199
+ email: string;
200
+ firstName: string;
201
+ lastName: string;
202
+ birthday: string;
203
+ }
204
+ ```
205
+
206
+ #### `Cart`
207
+
208
+ Current shopping cart state and totals.
209
+
210
+ ```typescript
211
+ interface Cart {
212
+ items: CartItem[];
213
+ total: number;
214
+ subtotal: number;
215
+ tax: number;
216
+ discount: number;
217
+ }
218
+
219
+ interface CartItem {
220
+ productId: string;
221
+ name: string;
222
+ price: number;
223
+ quantity: number;
224
+ }
225
+ ```
226
+
227
+ ### Category & Brand Types
228
+
229
+ #### `Category`
230
+
231
+ ```typescript
232
+ interface Category {
233
+ id: string;
234
+ name: string;
235
+ cname: string;
236
+ }
237
+ ```
238
+
239
+ #### `Brand`
240
+
241
+ ```typescript
242
+ interface Brand {
243
+ id: string;
244
+ name: string;
245
+ cname: string;
246
+ }
247
+ ```
248
+
249
+ #### `Collection`
250
+
251
+ ```typescript
252
+ interface Collection {
253
+ id: string;
254
+ name: string;
255
+ cname: string;
256
+ }
257
+ ```
258
+
259
+ ### Business Hours & Order Types
260
+
261
+ #### `DispensaryHoursSettings`
262
+
263
+ ```typescript
264
+ interface DispensaryHoursSettings {
265
+ inStorePickup?: HoursSettingsForOrderType;
266
+ curbsidePickup?: HoursSettingsForOrderType;
267
+ driveThruPickup?: HoursSettingsForOrderType;
268
+ delivery?: HoursSettingsForOrderType;
269
+ }
270
+
271
+ interface HoursSettingsForOrderType {
272
+ enabled: boolean;
273
+ effectiveHours?: {
274
+ Monday?: DayHours;
275
+ Tuesday?: DayHours;
276
+ Wednesday?: DayHours;
277
+ Thursday?: DayHours;
278
+ Friday?: DayHours;
279
+ Saturday?: DayHours;
280
+ Sunday?: DayHours;
281
+ };
282
+ }
283
+
284
+ interface DayHours {
285
+ active?: boolean;
286
+ start?: string; // "09:00"
287
+ end?: string; // "21:00"
288
+ }
289
+ ```
290
+
291
+ #### `DispensaryOrderTypes`
292
+
293
+ ```typescript
294
+ interface DispensaryOrderTypes {
295
+ pickup: boolean;
296
+ inStorePickup: boolean;
297
+ curbsidePickup: boolean;
298
+ driveThruPickup: boolean;
299
+ delivery: boolean;
300
+ kiosk: boolean;
301
+ }
302
+ ```
303
+
304
+ ## Extension Components
305
+
306
+ ### `RemoteBoundaryComponent`
307
+
308
+ The base type for all extension components that integrate with the Dutchie platform.
309
+
310
+ ```typescript
311
+ type RemoteBoundaryComponent = React.FC & {
312
+ DataBridgeVersion: string;
313
+ };
314
+ ```
315
+
316
+ **Example:**
317
+
318
+ ```tsx
319
+ const MyCustomHeader: RemoteBoundaryComponent = () => {
320
+ const { location, user, actions } = useDataBridge();
321
+
322
+ return (
323
+ <header>
324
+ <h1>{location?.name}</h1>
325
+ {user ? (
326
+ <span>Welcome, {user.firstName}!</span>
327
+ ) : (
328
+ <button onClick={actions.goToLogin}>Login</button>
329
+ )}
330
+ </header>
331
+ );
332
+ };
333
+
334
+ MyCustomHeader.DataBridgeVersion = '0.2';
335
+ ```
336
+
337
+ ### `RemoteModuleRegistry`
338
+
339
+ Registry for different types of extension components.
340
+
341
+ ```typescript
342
+ interface RemoteModuleRegistry {
343
+ RouteablePages?: RoutablePageRegistryEntry[];
344
+ StoreFrontHeader?: RemoteBoundaryComponent;
345
+ StoreFrontNavigation?: RemoteBoundaryComponent;
346
+ StoreFrontFooter?: RemoteBoundaryComponent;
347
+ StoreFrontHero?: RemoteBoundaryComponent;
348
+ ProductDetailsPrimary?: RemoteBoundaryComponent;
349
+ ProductDetailsSecondary?: RemoteBoundaryComponent;
350
+ }
351
+
352
+ interface RoutablePageRegistryEntry {
353
+ path: string;
354
+ component: RemoteBoundaryComponent;
355
+ }
356
+ ```
357
+
358
+ ## Actions API
359
+
360
+ The actions object provides pre-built functions for common e-commerce operations.
361
+
362
+ ### Navigation Actions
363
+
364
+ ```typescript
365
+ const { actions } = useDataBridge();
366
+
367
+ // Page navigation
368
+ actions.goToInfoPage(); // Navigate to store info
369
+ actions.goToStoreFront(); // Navigate to store front
370
+ actions.goToStoreLocator(); // Navigate to store locator
371
+ actions.goToStoreBrowser(); // Navigate to store browser
372
+
373
+ // Authentication
374
+ actions.goToLogin(); // Navigate to login page
375
+ actions.goToRegister(); // Navigate to registration
376
+
377
+ // Product & category navigation
378
+ actions.goToProductDetails('product-123'); // Navigate to product details
379
+ actions.goToCategory('flower'); // Navigate to category page
380
+ actions.goToBrand('brand-456'); // Navigate to brand page
381
+ actions.goToStore('store-789', 'dispensary-name'); // Navigate to specific store
382
+ actions.goToProductList('category-id', 'category-name'); // Navigate to product list
383
+ actions.goToBrandList('brand-id', 'brand-name'); // Navigate to brand list
384
+
385
+ // Checkout
386
+ actions.goToCheckout(); // Navigate to checkout
387
+ ```
388
+
389
+ ### Cart Actions
390
+
391
+ ```typescript
392
+ const { actions } = useDataBridge();
393
+
394
+ // Cart visibility
395
+ actions.showCart(); // Show cart sidebar/modal
396
+ actions.hideCart(); // Hide cart sidebar/modal
397
+
398
+ // Cart management
399
+ actions.addToCart('product-123', 2); // Add 2 items to cart
400
+ actions.removeFromCart('product-123'); // Remove product from cart
401
+ actions.updateCartItem('product-123', 5); // Update quantity to 5
402
+ actions.clearCart(); // Remove all items from cart
403
+ ```
404
+
405
+ ## Data Loaders
406
+
407
+ Async functions for loading platform data. All loaders return promises that resolve to arrays.
408
+
409
+ ```typescript
410
+ const { dataLoaders } = useDataBridge();
411
+
412
+ // Location data
413
+ const locations = await dataLoaders.getAllLocations(); // All dispensary locations
414
+ const currentLocations = await dataLoaders.locations(); // Current context locations
415
+
416
+ // Catalog data
417
+ const categories = await dataLoaders.categories(); // Product categories
418
+ const brands = await dataLoaders.brands(); // Available brands
419
+ const products = await dataLoaders.products(); // Product catalog
420
+ const collections = await dataLoaders.collections(); // Product collections
421
+ ```
422
+
423
+ **Note:** All data loaders return empty arrays (`[]`) when no data is available.
424
+
425
+ ## Examples
426
+
427
+ ### Complete Product Catalog Component
428
+
429
+ ```tsx
430
+ import React from 'react';
431
+ import { useDataBridge, useAsyncLoader, RemoteBoundaryComponent } from '@dutchie/ecommerce-extensions-sdk';
432
+
433
+ const ProductCatalog: RemoteBoundaryComponent = () => {
434
+ const { dataLoaders, actions, cart } = useDataBridge();
435
+
436
+ const { data: products, isLoading: productsLoading } = useAsyncLoader(dataLoaders.products);
437
+ const { data: categories, isLoading: categoriesLoading } = useAsyncLoader(dataLoaders.categories);
438
+
439
+ const [selectedCategory, setSelectedCategory] = React.useState<string>('');
440
+
441
+ const filteredProducts = selectedCategory
442
+ ? products?.filter(product => /* filter logic */)
443
+ : products || [];
444
+
445
+ if (productsLoading || categoriesLoading) {
446
+ return <div className="loading">Loading catalog...</div>;
447
+ }
448
+
449
+ return (
450
+ <div className="product-catalog">
451
+ <div className="filters">
452
+ <select
453
+ value={selectedCategory}
454
+ onChange={(e) => setSelectedCategory(e.target.value)}
455
+ >
456
+ <option value="">All Categories</option>
457
+ {categories?.map(category => (
458
+ <option key={category.id} value={category.id}>
459
+ {category.name}
460
+ </option>
461
+ ))}
462
+ </select>
463
+ </div>
464
+
465
+ <div className="product-grid">
466
+ {filteredProducts.map(product => (
467
+ <div key={product.id} className="product-card">
468
+ <img src={product.image} alt={product.name} />
469
+ <h3>{product.name}</h3>
470
+ <p>${product.price.toFixed(2)}</p>
471
+ <p>{product.description}</p>
472
+
473
+ <div className="product-actions">
474
+ <button
475
+ onClick={() => actions.goToProductDetails(product.id)}
476
+ >
477
+ View Details
478
+ </button>
479
+
480
+ <button
481
+ onClick={() => actions.addToCart(product.id, 1)}
482
+ >
483
+ Add to Cart
484
+ </button>
485
+ </div>
486
+ </div>
487
+ ))}
488
+ </div>
489
+
490
+ {cart && cart.items.length > 0 && (
491
+ <div className="cart-summary">
492
+ <p>Cart: {cart.items.length} items - ${cart.total.toFixed(2)}</p>
493
+ <button onClick={actions.showCart}>View Cart</button>
494
+ </div>
495
+ )}
496
+ </div>
497
+ );
498
+ };
499
+
500
+ ProductCatalog.DataBridgeVersion = '0.2';
501
+ export default ProductCatalog;
502
+ ```
503
+
504
+ ### Custom Store Locator
505
+
506
+ ```tsx
507
+ import React from 'react';
508
+ import { useDataBridge, useAsyncLoader, RemoteBoundaryComponent } from '@dutchie/ecommerce-extensions-sdk';
509
+
510
+ const StoreLocator: RemoteBoundaryComponent = () => {
511
+ const { dataLoaders, actions, location: currentLocation } = useDataBridge();
512
+ const { data: locations, isLoading } = useAsyncLoader(dataLoaders.getAllLocations);
513
+
514
+ if (isLoading) return <div>Loading locations...</div>;
515
+
516
+ const formatHours = (hours: any) => {
517
+ if (!hours?.effectiveHours) return 'Hours not available';
518
+
519
+ const today = new Date().toLocaleDateString('en-US', { weekday: 'long' });
520
+ const todayHours = hours.effectiveHours[today];
521
+
522
+ if (!todayHours?.active) return 'Closed today';
523
+ return `${todayHours.start} - ${todayHours.end}`;
524
+ };
525
+
526
+ return (
527
+ <div className="store-locator">
528
+ <h2>Find a Store</h2>
529
+
530
+ {locations?.map(store => (
531
+ <div
532
+ key={store.id}
533
+ className={`store-card ${store.id === currentLocation?.id ? 'current' : ''}`}
534
+ >
535
+ <div className="store-header">
536
+ <img src={store.images.logo} alt={`${store.name} logo`} />
537
+ <div>
538
+ <h3>{store.name}</h3>
539
+ <p className="chain">{store.chain}</p>
540
+ </div>
541
+ </div>
542
+
543
+ <div className="store-details">
544
+ <div className="address">
545
+ <p>{store.address.street1}</p>
546
+ {store.address.street2 && <p>{store.address.street2}</p>}
547
+ <p>{store.address.city}, {store.address.stateAbbreviation} {store.address.zip}</p>
548
+ </div>
549
+
550
+ <div className="contact">
551
+ <p>📞 {store.phone}</p>
552
+ <p>✉️ {store.email}</p>
553
+ </div>
554
+
555
+ <div className="hours">
556
+ <p><strong>Today:</strong> {formatHours(store.hours.inStorePickup)}</p>
557
+ </div>
558
+
559
+ <div className="services">
560
+ {store.orderTypes.inStorePickup && <span className="service">In-Store Pickup</span>}
561
+ {store.orderTypes.curbsidePickup && <span className="service">Curbside</span>}
562
+ {store.orderTypes.delivery && <span className="service">Delivery</span>}
563
+ </div>
564
+ </div>
565
+
566
+ <div className="store-actions">
567
+ <button onClick={() => actions.goToStore(store.id, store.cname)}>
568
+ Visit Store
569
+ </button>
570
+ {store.links.website && (
571
+ <a href={store.links.website} target="_blank" rel="noopener noreferrer">
572
+ Website
573
+ </a>
574
+ )}
575
+ </div>
576
+ </div>
577
+ ))}
578
+ </div>
579
+ );
580
+ };
581
+
582
+ StoreLocator.DataBridgeVersion = '0.2';
583
+ export default StoreLocator;
584
+ ```
585
+
586
+ ### Enhanced Cart Component
587
+
588
+ ```tsx
589
+ import React from 'react';
590
+ import { useDataBridge, RemoteBoundaryComponent } from '@dutchie/ecommerce-extensions-sdk';
591
+
592
+ const EnhancedCart: RemoteBoundaryComponent = () => {
593
+ const { cart, actions, location } = useDataBridge();
594
+
595
+ if (!cart || cart.items.length === 0) {
596
+ return (
597
+ <div className="empty-cart">
598
+ <h3>Your cart is empty</h3>
599
+ <button onClick={actions.goToStoreFront}>
600
+ Continue Shopping
601
+ </button>
602
+ </div>
603
+ );
604
+ }
605
+
606
+ return (
607
+ <div className="enhanced-cart">
608
+ <div className="cart-header">
609
+ <h2>Your Cart</h2>
610
+ <p>Order from {location?.name}</p>
611
+ </div>
612
+
613
+ <div className="cart-items">
614
+ {cart.items.map(item => (
615
+ <div key={item.productId} className="cart-item">
616
+ <div className="item-details">
617
+ <h4>{item.name}</h4>
618
+ <p className="price">${item.price.toFixed(2)} each</p>
619
+ </div>
620
+
621
+ <div className="quantity-controls">
622
+ <button
623
+ onClick={() => actions.updateCartItem(item.productId, item.quantity - 1)}
624
+ disabled={item.quantity <= 1}
625
+ >
626
+ -
627
+ </button>
628
+ <span className="quantity">{item.quantity}</span>
629
+ <button
630
+ onClick={() => actions.updateCartItem(item.productId, item.quantity + 1)}
631
+ >
632
+ +
633
+ </button>
634
+ </div>
635
+
636
+ <div className="item-total">
637
+ ${(item.price * item.quantity).toFixed(2)}
638
+ </div>
639
+
640
+ <button
641
+ className="remove-item"
642
+ onClick={() => actions.removeFromCart(item.productId)}
643
+ >
644
+ Remove
645
+ </button>
646
+ </div>
647
+ ))}
648
+ </div>
649
+
650
+ <div className="cart-summary">
651
+ <div className="summary-line">
652
+ <span>Subtotal:</span>
653
+ <span>${cart.subtotal.toFixed(2)}</span>
654
+ </div>
655
+
656
+ {cart.discount > 0 && (
657
+ <div className="summary-line discount">
658
+ <span>Discount:</span>
659
+ <span>-${cart.discount.toFixed(2)}</span>
660
+ </div>
661
+ )}
662
+
663
+ <div className="summary-line">
664
+ <span>Tax:</span>
665
+ <span>${cart.tax.toFixed(2)}</span>
666
+ </div>
667
+
668
+ <div className="summary-line total">
669
+ <span><strong>Total:</strong></span>
670
+ <span><strong>${cart.total.toFixed(2)}</strong></span>
671
+ </div>
672
+ </div>
673
+
674
+ <div className="cart-actions">
675
+ <button
676
+ className="clear-cart"
677
+ onClick={actions.clearCart}
678
+ >
679
+ Clear Cart
680
+ </button>
681
+
682
+ <button
683
+ className="checkout-btn"
684
+ onClick={actions.goToCheckout}
685
+ >
686
+ Proceed to Checkout
687
+ </button>
688
+ </div>
689
+ </div>
690
+ );
691
+ };
692
+
693
+ EnhancedCart.DataBridgeVersion = '0.2';
694
+ export default EnhancedCart;
695
+ ```
696
+
697
+ ## Best Practices
698
+
699
+ ### 1. Error Handling
700
+
701
+ Always wrap `useDataBridge()` calls in error boundaries and handle loading states:
702
+
703
+ ```tsx
704
+ const SafeComponent = () => {
705
+ try {
706
+ const { dataLoaders } = useDataBridge();
707
+ const { data, isLoading } = useAsyncLoader(dataLoaders.products);
708
+
709
+ if (isLoading) return <LoadingSpinner />;
710
+ if (!data) return <ErrorMessage message="Failed to load products" />;
711
+
712
+ return <ProductList products={data} />;
713
+ } catch (error) {
714
+ return <ErrorFallback error={error} />;
715
+ }
716
+ };
717
+ ```
718
+
719
+ ### 2. Performance Optimization
720
+
721
+ Use React.memo and useMemo for expensive operations:
722
+
723
+ ```tsx
724
+ const ProductCard = React.memo(({ product, onAddToCart }) => {
725
+ const formattedPrice = React.useMemo(() =>
726
+ new Intl.NumberFormat('en-US', {
727
+ style: 'currency',
728
+ currency: 'USD'
729
+ }).format(product.price),
730
+ [product.price]
731
+ );
732
+
733
+ return (
734
+ <div className="product-card">
735
+ <h3>{product.name}</h3>
736
+ <p>{formattedPrice}</p>
737
+ <button onClick={() => onAddToCart(product.id, 1)}>
738
+ Add to Cart
739
+ </button>
740
+ </div>
741
+ );
742
+ });
743
+ ```
744
+
745
+ ### 3. TypeScript Best Practices
746
+
747
+ Leverage the provided types for better development experience:
748
+
749
+ ```tsx
750
+ import { Product, Category, useDataBridge } from '@dutchie/ecommerce-extensions-sdk';
751
+
752
+ interface ProductFilterProps {
753
+ products: Product[];
754
+ categories: Category[];
755
+ onFilterChange: (categoryId: string) => void;
756
+ }
757
+
758
+ const ProductFilter: React.FC<ProductFilterProps> = ({
759
+ products,
760
+ categories,
761
+ onFilterChange
762
+ }) => {
763
+ // Implementation
764
+ };
765
+ ```
766
+
767
+ ### 4. Context Usage
768
+
769
+ Always check for data availability before using:
770
+
771
+ ```tsx
772
+ const UserProfile = () => {
773
+ const { user, actions } = useDataBridge();
774
+
775
+ if (!user) {
776
+ return (
777
+ <div className="login-prompt">
778
+ <p>Please log in to view your profile</p>
779
+ <button onClick={actions.goToLogin}>Login</button>
780
+ </div>
781
+ );
782
+ }
783
+
784
+ return (
785
+ <div className="user-profile">
786
+ <h2>Welcome, {user.firstName}!</h2>
787
+ {/* Profile content */}
788
+ </div>
789
+ );
790
+ };
791
+ ```
792
+
793
+ ## Development
794
+
795
+ ### Setup
796
+
797
+ ```bash
798
+ # Install dependencies
799
+ pnpm install
800
+
801
+ # Start development mode
802
+ pnpm dev
803
+
804
+ # Build for production
805
+ pnpm build
806
+
807
+ # Run tests
808
+ pnpm test
809
+
810
+ # Format code
811
+ pnpm format
812
+
813
+ # Lint and fix
814
+ pnpm check
815
+ ```
816
+
817
+ ### Building Extensions
818
+
819
+ 1. Create your extension component
820
+ 2. Set the `DataBridgeVersion` property
821
+ 3. Export your component
822
+ 4. Build and deploy using the Dutchie Pro Deployment platform
823
+
824
+ ### Testing
825
+
826
+ The SDK includes utilities for testing your extensions:
827
+
828
+ ```tsx
829
+ import { render, screen } from '@testing-library/react';
830
+ import { DataBridgeContext } from '@dutchie/ecommerce-extensions-sdk';
831
+ import MyExtension from './MyExtension';
832
+
833
+ const mockDataBridge = {
834
+ menuContext: 'store-front' as const,
835
+ location: { id: '1', name: 'Test Dispensary' },
836
+ dataLoaders: {
837
+ products: jest.fn().mockResolvedValue([]),
838
+ // ... other loaders
839
+ },
840
+ actions: {
841
+ addToCart: jest.fn(),
842
+ // ... other actions
843
+ }
844
+ };
845
+
846
+ test('renders extension correctly', () => {
847
+ render(
848
+ <DataBridgeContext.Provider value={mockDataBridge}>
849
+ <MyExtension />
850
+ </DataBridgeContext.Provider>
851
+ );
852
+
853
+ expect(screen.getByText('Test Dispensary')).toBeInTheDocument();
854
+ });
855
+ ```
856
+
857
+ ## Support
858
+
859
+ For technical support and questions about the Dutchie Ecommerce Extensions SDK:
860
+
861
+ - 📧 Contact your Dutchie agency partner representative
862
+ - 📚 Refer to the Dutchie Pro platform documentation
863
+ - 🐛 Report issues through the official Dutchie support channels
864
+
865
+ ---
866
+
867
+ **License:** MIT
868
+ **Node Version:** >=16.0.0
@@ -0,0 +1,8 @@
1
+ import { CommerceComponentsDataInterface } from "../types/dutchie-data-bridge";
2
+ export declare const DutchieDataBridgeVersion: string;
3
+ export declare const DutchieDataBridgeContext: import("react").Context<CommerceComponentsDataInterface | undefined>;
4
+ export declare const useDutchieDataBridge: () => CommerceComponentsDataInterface;
5
+ export declare const useAsyncLoader: (fn: () => Promise<unknown>) => {
6
+ data: unknown;
7
+ isLoading: boolean;
8
+ };
@@ -0,0 +1,22 @@
1
+ import * as __WEBPACK_EXTERNAL_MODULE_react__ from "react";
2
+ import * as __WEBPACK_EXTERNAL_MODULE__package_json_1116eba2__ from "../../package.json";
3
+ const DutchieDataBridgeVersion = __WEBPACK_EXTERNAL_MODULE__package_json_1116eba2__["default"].version;
4
+ const DutchieDataBridgeContext = (0, __WEBPACK_EXTERNAL_MODULE_react__.createContext)(void 0);
5
+ const useDutchieDataBridge = ()=>{
6
+ const context = (0, __WEBPACK_EXTERNAL_MODULE_react__.useContext)(DutchieDataBridgeContext);
7
+ if (void 0 === context) throw new Error('useDutchieDataBridge must be used within a DutchieDataBridgeProvider');
8
+ return context;
9
+ };
10
+ const useAsyncLoader = (fn)=>{
11
+ const [data, setData] = (0, __WEBPACK_EXTERNAL_MODULE_react__.useState)(null);
12
+ (0, __WEBPACK_EXTERNAL_MODULE_react__.useEffect)(()=>{
13
+ fn().then(setData);
14
+ }, [
15
+ fn
16
+ ]);
17
+ return {
18
+ data,
19
+ isLoading: null === data
20
+ };
21
+ };
22
+ export { DutchieDataBridgeContext, DutchieDataBridgeVersion, useAsyncLoader, useDutchieDataBridge };
@@ -0,0 +1,3 @@
1
+ export * from './types/dutchie-data-bridge';
2
+ export * from './types/ecommerce-extension';
3
+ export * from './context/ecommerce-data-bridge';
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export * from "./types/dutchie-data-bridge.js";
2
+ export * from "./types/ecommerce-extension.js";
3
+ export * from "./context/ecommerce-data-bridge.js";
@@ -0,0 +1,109 @@
1
+ export type CommerceComponentsDataInterface = {
2
+ menuContext: CommerceComponentsMenuContext;
3
+ location?: Dispensary;
4
+ user?: User;
5
+ cart?: Cart;
6
+ dataLoaders: {
7
+ getAllLocations: () => Promise<Dispensary[] | []>;
8
+ };
9
+ actions: {
10
+ goToInfoPage: () => void;
11
+ goToStoreFront: () => void;
12
+ goToStoreLocator: () => void;
13
+ goToStoreBrowser: () => void;
14
+ openLogin: () => void;
15
+ openSignUp: () => void;
16
+ };
17
+ };
18
+ export type HoursSettingsForOrderType = {
19
+ enabled: boolean;
20
+ effectiveHours?: {
21
+ Monday?: DayHours;
22
+ Tuesday?: DayHours;
23
+ Wednesday?: DayHours;
24
+ Thursday?: DayHours;
25
+ Friday?: DayHours;
26
+ Saturday?: DayHours;
27
+ Sunday?: DayHours;
28
+ };
29
+ };
30
+ export type DayHours = {
31
+ active?: boolean;
32
+ start?: string;
33
+ end?: string;
34
+ };
35
+ export type DispensaryHoursSettings = {
36
+ inStorePickup?: HoursSettingsForOrderType;
37
+ curbsidePickup?: HoursSettingsForOrderType;
38
+ driveThruPickup?: HoursSettingsForOrderType;
39
+ delivery?: HoursSettingsForOrderType;
40
+ };
41
+ export type OrderTypeConfig = {
42
+ enableASAPOrdering?: boolean;
43
+ enableScheduledOrdering?: boolean;
44
+ enableAfterHoursOrdering?: boolean;
45
+ };
46
+ export type OrderTypesConfigV2 = {
47
+ inStorePickup?: OrderTypeConfig;
48
+ curbsidePickup?: OrderTypeConfig;
49
+ driveThruPickup?: OrderTypeConfig;
50
+ delivery?: OrderTypeConfig;
51
+ offerAnyPickupService?: boolean;
52
+ offerDeliveryService?: boolean;
53
+ };
54
+ export type DispensaryOrderTypes = {
55
+ pickup: boolean;
56
+ inStorePickup: boolean;
57
+ curbsidePickup: boolean;
58
+ driveThruPickup: boolean;
59
+ delivery: boolean;
60
+ kiosk: boolean;
61
+ };
62
+ export type DispensaryAddress = {
63
+ street1: string;
64
+ street2: string;
65
+ city: string;
66
+ state: string;
67
+ stateAbbreviation: string;
68
+ zip: string;
69
+ };
70
+ export type Dispensary = {
71
+ id: string;
72
+ status: string;
73
+ name: string;
74
+ cname: string;
75
+ chain: string;
76
+ phone: string;
77
+ email: string;
78
+ hours: DispensaryHoursSettings;
79
+ orderTypes: DispensaryOrderTypes;
80
+ images: {
81
+ logo: string;
82
+ };
83
+ address: DispensaryAddress;
84
+ links: {
85
+ website: string;
86
+ storeFrontRoot: string;
87
+ };
88
+ orderTypesConfig: OrderTypesConfigV2;
89
+ };
90
+ export type User = {
91
+ email: string;
92
+ firstName: string;
93
+ lastName: string;
94
+ birthday: string;
95
+ };
96
+ export type CartItem = {
97
+ productId: string;
98
+ name: string;
99
+ price: number;
100
+ quantity: number;
101
+ };
102
+ export type Cart = {
103
+ items: CartItem[];
104
+ total: number;
105
+ subtotal: number;
106
+ tax: number;
107
+ discount: number;
108
+ };
109
+ export type CommerceComponentsMenuContext = 'store-front' | 'kiosk';
File without changes
@@ -0,0 +1,16 @@
1
+ export type RemoteBoundaryComponent = React.FC & {
2
+ DataBridgeVersion: string;
3
+ };
4
+ export type RoutablePageRegistryEntry = {
5
+ path: string;
6
+ component: RemoteBoundaryComponent;
7
+ };
8
+ export type RemoteModuleRegistry = {
9
+ RouteablePages?: RoutablePageRegistryEntry[];
10
+ StoreFrontHeader?: RemoteBoundaryComponent;
11
+ StoreFrontNavigation?: RemoteBoundaryComponent;
12
+ StoreFrontFooter?: RemoteBoundaryComponent;
13
+ StoreFrontHero?: RemoteBoundaryComponent;
14
+ ProductDetailsPrimary?: RemoteBoundaryComponent;
15
+ ProductDetailsSecondary?: RemoteBoundaryComponent;
16
+ };
File without changes
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@dutchiesdk/ecommerce-extensions-sdk",
3
+ "private": false,
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
7
+ "version": "0.6.0",
8
+ "license": "MIT",
9
+ "type": "module",
10
+ "module": "./dist/esm/index.js",
11
+ "types": "./dist/index.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "import": "./dist/esm/index.js",
16
+ "require": "./dist/index.cjs"
17
+ }
18
+ },
19
+ "engines": {
20
+ "node": ">=16.0.0"
21
+ },
22
+ "files": [
23
+ "dist"
24
+ ],
25
+ "scripts": {
26
+ "build": "rslib build",
27
+ "check": "biome check --write",
28
+ "dev": "rslib build --watch",
29
+ "format": "biome format --write",
30
+ "test": "vitest run"
31
+ },
32
+ "devDependencies": {
33
+ "@biomejs/biome": "^1.9.4",
34
+ "@rsbuild/plugin-react": "^1.3.2",
35
+ "@rslib/core": "^0.9.2",
36
+ "@testing-library/jest-dom": "^6.6.3",
37
+ "@testing-library/react": "^16.3.0",
38
+ "@types/react": "^17.0.0",
39
+ "jsdom": "^26.1.0",
40
+ "react": "^17.0.0",
41
+ "typescript": "^5.8.3",
42
+ "vitest": "^3.2.2"
43
+ },
44
+ "peerDependencies": {
45
+ "react": "^17.0.0 || ^18.0.0",
46
+ "react-dom": "^17.0.0 || ^18.0.0"
47
+ },
48
+ "packageManager": "yarn@1.22.22+sha1.ac34549e6aa8e7ead463a7407e1c7390f61a6610"
49
+ }