@instockng/storefront-ui 1.0.20 → 1.0.22

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.
Files changed (117) hide show
  1. package/dist/components/ui/modal.d.ts +1 -1
  2. package/dist/components/ui/modal.d.ts.map +1 -1
  3. package/dist/contexts/CartContext.d.ts +2 -0
  4. package/dist/contexts/CartContext.d.ts.map +1 -1
  5. package/dist/index134.mjs +2 -2
  6. package/dist/index135.mjs +1 -1
  7. package/dist/index136.mjs +6 -6
  8. package/dist/index137.mjs +2 -2
  9. package/dist/index138.mjs +2 -2
  10. package/dist/index140.mjs +2 -2
  11. package/dist/index141.mjs +2 -2
  12. package/dist/index142.mjs +1 -1
  13. package/dist/index144.mjs +8 -8
  14. package/dist/index145.mjs +8 -8
  15. package/dist/index150.mjs +1 -1
  16. package/dist/index151.mjs +1 -1
  17. package/dist/index152.mjs +1 -1
  18. package/dist/index153.mjs +1 -1
  19. package/dist/index154.mjs +1 -1
  20. package/dist/index156.mjs +1 -1
  21. package/dist/index157.mjs +1 -1
  22. package/dist/index159.mjs +2 -2
  23. package/dist/index160.mjs +1 -1
  24. package/dist/index167.mjs +1 -1
  25. package/dist/index169.mjs +2 -2
  26. package/dist/index170.mjs +5 -5
  27. package/dist/index173.mjs +1 -1
  28. package/dist/index186.mjs +1 -1
  29. package/dist/index190.mjs +1 -1
  30. package/dist/index192.mjs +1 -1
  31. package/dist/index203.mjs +1 -1
  32. package/dist/index204.mjs +1 -1
  33. package/dist/index205.mjs +2 -2
  34. package/dist/index206.mjs +2 -108
  35. package/dist/index207.mjs +2 -2
  36. package/dist/index208.mjs +2 -2
  37. package/dist/index209.mjs +2 -2
  38. package/dist/index210.mjs +2 -2
  39. package/dist/index211.mjs +2 -2
  40. package/dist/index212.mjs +2 -2
  41. package/dist/index213.mjs +108 -2
  42. package/dist/index214.mjs +2 -2
  43. package/dist/index216.mjs +3 -3
  44. package/dist/index217.mjs +2 -2
  45. package/dist/index218.mjs +244 -2
  46. package/dist/index219.mjs +2 -244
  47. package/dist/index220.mjs +33 -2
  48. package/dist/index221.mjs +60 -28
  49. package/dist/index222.mjs +20 -60
  50. package/dist/index223.mjs +2 -25
  51. package/dist/index224.mjs +2 -2
  52. package/dist/index229.mjs +2 -2
  53. package/dist/index230.mjs +2 -2
  54. package/dist/index234.mjs +2 -31
  55. package/dist/index235.mjs +2 -11
  56. package/dist/index236.mjs +30 -3
  57. package/dist/index237.mjs +10 -3
  58. package/dist/index238.mjs +4 -13
  59. package/dist/index239.mjs +4 -7
  60. package/dist/index240.mjs +13 -12
  61. package/dist/index241.mjs +7 -5
  62. package/dist/index242.mjs +12 -33
  63. package/dist/index243.mjs +5 -31
  64. package/dist/index244.mjs +32 -27
  65. package/dist/index245.mjs +28 -58
  66. package/dist/index246.mjs +28 -2
  67. package/dist/index247.mjs +61 -2
  68. package/dist/index248.mjs +2 -2
  69. package/dist/index249.mjs +18 -2
  70. package/dist/index25.mjs +48 -48
  71. package/dist/index250.mjs +42 -13
  72. package/dist/index251.mjs +2 -47
  73. package/dist/index252.mjs +2 -2
  74. package/dist/index253.mjs +2 -2
  75. package/dist/index254.mjs +2 -2
  76. package/dist/index255.mjs +91 -2
  77. package/dist/index256.mjs +2 -91
  78. package/dist/index258.mjs +1 -1
  79. package/dist/index264.mjs +1 -1
  80. package/dist/index3.mjs +133 -102
  81. package/dist/index32.mjs +26 -26
  82. package/dist/index37.mjs +72 -52
  83. package/dist/index38.mjs +1 -1
  84. package/dist/index4.mjs +53 -48
  85. package/dist/index42.mjs +1 -1
  86. package/dist/index47.mjs +17 -17
  87. package/dist/index48.mjs +2 -2
  88. package/dist/index62.mjs +19 -149
  89. package/dist/index63.mjs +149 -19
  90. package/dist/index66.mjs +1 -1
  91. package/dist/index73.mjs +1 -1
  92. package/dist/index75.mjs +2 -235
  93. package/dist/index76.mjs +233 -4
  94. package/dist/index77.mjs +5 -133
  95. package/dist/index78.mjs +129 -63
  96. package/dist/index79.mjs +67 -86
  97. package/dist/index80.mjs +85 -27
  98. package/dist/index81.mjs +28 -8
  99. package/dist/index82.mjs +8 -74
  100. package/dist/index83.mjs +74 -3
  101. package/dist/index84.mjs +4 -2
  102. package/dist/index85.mjs +2 -83
  103. package/dist/index86.mjs +81 -52
  104. package/dist/index87.mjs +53 -5
  105. package/dist/index88.mjs +5 -4
  106. package/dist/index89.mjs +4 -178
  107. package/dist/index90.mjs +174 -48
  108. package/dist/index91.mjs +51 -67
  109. package/dist/index92.mjs +69 -34
  110. package/dist/index93.mjs +31 -40
  111. package/dist/index94.mjs +43 -2
  112. package/dist/providers/MetaPixelProvider.d.ts +10 -5
  113. package/dist/providers/MetaPixelProvider.d.ts.map +1 -1
  114. package/package.json +9 -10
  115. package/src/components/ui/modal.tsx +1 -1
  116. package/src/contexts/CartContext.tsx +69 -6
  117. package/src/providers/MetaPixelProvider.tsx +46 -30
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@instockng/storefront-ui",
3
- "version": "1.0.20",
3
+ "version": "1.0.22",
4
4
  "description": "Pre-built UI components for OMS e-commerce sites",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -20,13 +20,6 @@
20
20
  "src",
21
21
  "README.md"
22
22
  ],
23
- "scripts": {
24
- "type-check": "tsc --noEmit",
25
- "build": "vite build && vite build --config vite.config.css.ts && tsc --project tsconfig.build.json",
26
- "prepublishOnly": "npm run build",
27
- "storybook": "storybook dev -p 6006",
28
- "build-storybook": "storybook build"
29
- },
30
23
  "repository": {
31
24
  "type": "git",
32
25
  "url": "git+https://github.com/ola-wale/oms.git",
@@ -45,7 +38,7 @@
45
38
  "author": "Ola Wale",
46
39
  "license": "MIT",
47
40
  "dependencies": {
48
- "@instockng/api-client": "^1.0.2",
41
+ "@instockng/api-client": "^1.0.3",
49
42
  "class-variance-authority": "^0.7.1",
50
43
  "clsx": "^2.1.0",
51
44
  "react-facebook-pixel": "^1.0.4",
@@ -79,5 +72,11 @@
79
72
  "tailwindcss": "^4.1.14",
80
73
  "typescript": "^5.3.3",
81
74
  "vite": "^5.4.20"
75
+ },
76
+ "scripts": {
77
+ "type-check": "tsc --noEmit",
78
+ "build": "vite build && vite build --config vite.config.css.ts && tsc --project tsconfig.build.json || true",
79
+ "storybook": "storybook dev -p 6006",
80
+ "build-storybook": "storybook build"
82
81
  }
83
- }
82
+ }
@@ -46,7 +46,7 @@ export function Modal({
46
46
  size = 'md',
47
47
  closeOnOverlayClick = true,
48
48
  showCloseButton = true,
49
- }: ModalProps): JSX.Element | null {
49
+ }: ModalProps): React.ReactElement | null {
50
50
  const modalRef = useRef<HTMLDivElement>(null);
51
51
  const [isAnimating, setIsAnimating] = useState(false);
52
52
  const [shouldRender, setShouldRender] = useState(isOpen);
@@ -49,6 +49,8 @@ interface CartContextValue {
49
49
  applyDiscount: (code: string) => Promise<void>;
50
50
  /** Remove discount code */
51
51
  removeDiscount: () => Promise<void>;
52
+ /** Track InitiateCheckout event (call when user starts checkout flow) */
53
+ trackCheckoutInitiated: () => void;
52
54
  /** Clear cart (removes from localStorage) */
53
55
  clearCart: () => void;
54
56
  /** Clear cart and create a new one immediately */
@@ -92,7 +94,26 @@ export function CartProvider({ children, brandSlug, initialCartId, shoppingCartP
92
94
  const isHandlingErrorRef = useRef(false);
93
95
 
94
96
  // Get Meta Pixel tracking methods
95
- const { trackAddToCart, trackPurchase } = useMetaPixel();
97
+ const { trackAddToCart, trackInitiateCheckout, trackPurchase } = useMetaPixel();
98
+
99
+ /**
100
+ * Extract fbc (Facebook Click ID) and fbp (Facebook Browser ID) cookies for attribution
101
+ */
102
+ const getFbCookies = useCallback(() => {
103
+ if (typeof document === 'undefined') return { fbc: undefined, fbp: undefined };
104
+
105
+ const getCookie = (name: string) => {
106
+ const value = `; ${document.cookie}`;
107
+ const parts = value.split(`; ${name}=`);
108
+ if (parts.length === 2) return parts.pop()?.split(';').shift();
109
+ return undefined;
110
+ };
111
+
112
+ return {
113
+ fbc: getCookie('_fbc'),
114
+ fbp: getCookie('_fbp'),
115
+ };
116
+ }, []);
96
117
 
97
118
  // Set mounted flag on client
98
119
  useEffect(() => {
@@ -226,11 +247,15 @@ export function CartProvider({ children, brandSlug, initialCartId, shoppingCartP
226
247
  item_price: Number(item.priceAtPurchase),
227
248
  })) || [];
228
249
 
250
+ // Generate event ID matching backend format: order_{orderId}
251
+ const eventID = `order_${order.id}`;
252
+
229
253
  trackPurchase(
230
254
  Number(order.totalPrice),
231
255
  'NGN',
232
256
  order.id,
233
- items
257
+ items,
258
+ eventID
234
259
  );
235
260
  },
236
261
  });
@@ -245,13 +270,34 @@ export function CartProvider({ children, brandSlug, initialCartId, shoppingCartP
245
270
  const addItem = useCallback(
246
271
  async (productSlug: string, productName: string, price: number, sku: string, quantity: number) => {
247
272
  if (!cartId) throw new Error('No cart ID');
248
- await addItemMutation.mutateAsync({ sku, quantity });
249
273
 
250
- // Track AddToCart event with Meta Pixel
251
- trackAddToCart(productSlug, productName, price, quantity);
274
+ // Get Facebook cookies for attribution
275
+ const { fbc, fbp } = getFbCookies();
276
+
277
+ // Add item to cart via API
278
+ const updatedCart = await addItemMutation.mutateAsync({ sku, quantity, fbc, fbp });
279
+
280
+ // Skip tracking if error response
281
+ if ('error' in updatedCart) return;
282
+
283
+ // Find the newly added item (last item with matching SKU)
284
+ const addedItem = updatedCart.items?.findLast(
285
+ (item) => item.variant?.sku === sku
286
+ );
287
+
288
+ if (addedItem) {
289
+ // Generate event ID matching backend format: cart_{cartId}_item_{itemId}
290
+ const eventID = `cart_${cartId}_item_${addedItem.id}`;
291
+
292
+ // Track AddToCart event with Meta Pixel and matching event ID
293
+ trackAddToCart(productSlug, productName, price, quantity, eventID);
294
+ } else {
295
+ // Fallback without event ID if item not found
296
+ trackAddToCart(productSlug, productName, price, quantity);
297
+ }
252
298
  },
253
299
  // eslint-disable-next-line react-hooks/exhaustive-deps
254
- [cartId, trackAddToCart]
300
+ [cartId, trackAddToCart, getFbCookies]
255
301
  );
256
302
 
257
303
  const updateItem = useCallback(
@@ -310,6 +356,22 @@ export function CartProvider({ children, brandSlug, initialCartId, shoppingCartP
310
356
  setIsOpen(false);
311
357
  }, []);
312
358
 
359
+ const trackCheckoutInitiated = useCallback(() => {
360
+ if (!cart || !cartId) return;
361
+
362
+ // Get cart total from pricing
363
+ const cartTotal = Number(cart.pricing?.total || 0);
364
+
365
+ // Calculate item count
366
+ const itemCount = cart.items?.reduce((sum, item) => sum + item.quantity, 0) || 0;
367
+
368
+ // Generate event ID matching backend format: cart_{cartId}_checkout
369
+ const eventID = `cart_${cartId}_checkout`;
370
+
371
+ // Track InitiateCheckout event with Meta Pixel
372
+ trackInitiateCheckout(cartTotal, itemCount, eventID);
373
+ }, [cart, cartId, trackInitiateCheckout]);
374
+
313
375
  const value: CartContextValue = {
314
376
  cart: cart || null,
315
377
  cartId,
@@ -322,6 +384,7 @@ export function CartProvider({ children, brandSlug, initialCartId, shoppingCartP
322
384
  removeItem,
323
385
  applyDiscount,
324
386
  removeDiscount,
387
+ trackCheckoutInitiated,
325
388
  clearCart,
326
389
  clearAndCreateNewCart,
327
390
  refetch,
@@ -7,7 +7,6 @@
7
7
  */
8
8
 
9
9
  import { createContext, useContext, useEffect, useState, type ReactNode } from 'react';
10
- import ReactPixel from 'react-facebook-pixel';
11
10
 
12
11
  interface PurchaseItem {
13
12
  id: string; // SKU
@@ -19,7 +18,7 @@ interface MetaPixelContextValue {
19
18
  /**
20
19
  * Track a custom event
21
20
  */
22
- track: (event: string, data?: Record<string, unknown>) => void;
21
+ track: (event: string, data?: Record<string, unknown>, eventID?: string) => void;
23
22
 
24
23
  /**
25
24
  * Track a page view
@@ -29,7 +28,7 @@ interface MetaPixelContextValue {
29
28
  /**
30
29
  * Track product view
31
30
  */
32
- trackProductView: (productId: string, productName: string, price: number) => void;
31
+ trackProductView: (productId: string, productName: string, price: number, eventID?: string) => void;
33
32
 
34
33
  /**
35
34
  * Track add to cart event
@@ -37,13 +36,17 @@ interface MetaPixelContextValue {
37
36
  * @param productName - Product name
38
37
  * @param price - Unit price (not total)
39
38
  * @param quantity - Quantity added
39
+ * @param eventID - Optional event ID for deduplication with CAPI
40
40
  */
41
- trackAddToCart: (productId: string, productName: string, price: number, quantity: number) => void;
41
+ trackAddToCart: (productId: string, productName: string, price: number, quantity: number, eventID?: string) => void;
42
42
 
43
43
  /**
44
44
  * Track begin checkout event
45
+ * @param cartTotal - Total cart value
46
+ * @param itemCount - Number of items
47
+ * @param eventID - Optional event ID for deduplication with CAPI
45
48
  */
46
- trackInitiateCheckout: (cartTotal: number, itemCount: number) => void;
49
+ trackInitiateCheckout: (cartTotal: number, itemCount: number, eventID?: string) => void;
47
50
 
48
51
  /**
49
52
  * Track purchase event
@@ -51,8 +54,9 @@ interface MetaPixelContextValue {
51
54
  * @param currency - Currency code (e.g., 'NGN', 'USD')
52
55
  * @param orderId - Order ID or order number
53
56
  * @param items - Array of purchased items with SKU, quantity, and price
57
+ * @param eventID - Optional event ID for deduplication with CAPI
54
58
  */
55
- trackPurchase: (orderTotal: number, currency: string, orderId: string, items: PurchaseItem[]) => void;
59
+ trackPurchase: (orderTotal: number, currency: string, orderId: string, items: PurchaseItem[], eventID?: string) => void;
56
60
 
57
61
  /**
58
62
  * Whether the pixel is initialized
@@ -90,6 +94,7 @@ export interface MetaPixelProviderProps {
90
94
  */
91
95
  export function MetaPixelProvider({ children, pixelId, debug }: MetaPixelProviderProps) {
92
96
  const [isInitialized, setIsInitialized] = useState(false);
97
+ const [reactPixel, setReactPixel] = useState<any>(null);
93
98
  const isDevelopment = typeof process !== 'undefined' && process.env.NODE_ENV === 'development';
94
99
  const shouldDebug = debug ?? isDevelopment;
95
100
 
@@ -104,61 +109,72 @@ export function MetaPixelProvider({ children, pixelId, debug }: MetaPixelProvide
104
109
  return;
105
110
  }
106
111
 
107
- // Initialize Meta Pixel (client-side only)
108
- const options = {
109
- autoConfig: true,
110
- debug: shouldDebug,
111
- };
112
+ // Dynamically import react-facebook-pixel only on client
113
+ import('react-facebook-pixel').then((module) => {
114
+ const ReactPixel = module.default;
112
115
 
113
- ReactPixel.init(pixelId, undefined, options);
114
- setIsInitialized(true);
116
+ // Initialize Meta Pixel (client-side only)
117
+ const options = {
118
+ autoConfig: true,
119
+ debug: shouldDebug,
120
+ };
115
121
 
116
- if (shouldDebug) {
117
- console.log('[Meta Pixel] Initialized with ID:', pixelId);
118
- }
122
+ ReactPixel.init(pixelId, undefined, options);
123
+ setReactPixel(ReactPixel);
124
+ setIsInitialized(true);
125
+
126
+ if (shouldDebug) {
127
+ console.log('[Meta Pixel] Initialized with ID:', pixelId);
128
+ }
129
+ }).catch((error) => {
130
+ console.error('[Meta Pixel] Failed to load react-facebook-pixel:', error);
131
+ });
119
132
  }, [pixelId, shouldDebug]);
120
133
 
121
- const track = (event: string, data?: Record<string, unknown>) => {
122
- if (!isInitialized) {
134
+ const track = (event: string, data?: Record<string, unknown>, eventID?: string) => {
135
+ if (!isInitialized || !reactPixel) {
123
136
  if (shouldDebug) {
124
137
  console.log('[Meta Pixel] Track (not initialized):', event, data);
125
138
  }
126
139
  return;
127
140
  }
128
141
 
129
- ReactPixel.track(event, data);
142
+ // Include eventID for deduplication with CAPI
143
+ const eventData = eventID ? { ...data, eventID } : data;
144
+
145
+ reactPixel.track(event, eventData);
130
146
 
131
147
  if (shouldDebug) {
132
- console.log('[Meta Pixel] Track:', event, data);
148
+ console.log('[Meta Pixel] Track:', event, eventData, eventID ? `(ID: ${eventID})` : '');
133
149
  }
134
150
  };
135
151
 
136
152
  const trackPageView = () => {
137
- if (!isInitialized) {
153
+ if (!isInitialized || !reactPixel) {
138
154
  if (shouldDebug) {
139
155
  console.log('[Meta Pixel] PageView (not initialized)');
140
156
  }
141
157
  return;
142
158
  }
143
159
 
144
- ReactPixel.pageView();
160
+ reactPixel.pageView();
145
161
 
146
162
  if (shouldDebug) {
147
163
  console.log('[Meta Pixel] PageView');
148
164
  }
149
165
  };
150
166
 
151
- const trackProductView = (productId: string, productName: string, price: number) => {
167
+ const trackProductView = (productId: string, productName: string, price: number, eventID?: string) => {
152
168
  track('ViewContent', {
153
169
  content_ids: [productId],
154
170
  content_name: productName,
155
171
  content_type: 'product',
156
172
  value: price,
157
173
  currency: 'NGN',
158
- });
174
+ }, eventID);
159
175
  };
160
176
 
161
- const trackAddToCart = (productId: string, productName: string, price: number, quantity: number) => {
177
+ const trackAddToCart = (productId: string, productName: string, price: number, quantity: number, eventID?: string) => {
162
178
  track('AddToCart', {
163
179
  content_ids: [productId],
164
180
  content_name: productName,
@@ -166,18 +182,18 @@ export function MetaPixelProvider({ children, pixelId, debug }: MetaPixelProvide
166
182
  value: price,
167
183
  currency: 'NGN',
168
184
  num_items: quantity,
169
- });
185
+ }, eventID);
170
186
  };
171
187
 
172
- const trackInitiateCheckout = (cartTotal: number, itemCount: number) => {
188
+ const trackInitiateCheckout = (cartTotal: number, itemCount: number, eventID?: string) => {
173
189
  track('InitiateCheckout', {
174
190
  value: cartTotal,
175
191
  currency: 'NGN',
176
192
  num_items: itemCount,
177
- });
193
+ }, eventID);
178
194
  };
179
195
 
180
- const trackPurchase = (orderTotal: number, currency: string, orderId: string, items: PurchaseItem[]) => {
196
+ const trackPurchase = (orderTotal: number, currency: string, orderId: string, items: PurchaseItem[], eventID?: string) => {
181
197
  track('Purchase', {
182
198
  value: orderTotal,
183
199
  currency,
@@ -188,7 +204,7 @@ export function MetaPixelProvider({ children, pixelId, debug }: MetaPixelProvide
188
204
  item_price: item.item_price,
189
205
  })),
190
206
  order_id: orderId,
191
- });
207
+ }, eventID);
192
208
  };
193
209
 
194
210
  const value: MetaPixelContextValue = {