@instockng/storefront-ui 1.0.21 → 1.0.23

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 (223) hide show
  1. package/dist/components/Checkout.d.ts.map +1 -1
  2. package/dist/components/ShoppingCart.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/index10.mjs +53 -59
  6. package/dist/index100.mjs +3 -3
  7. package/dist/index101.mjs +1 -1
  8. package/dist/index102.mjs +1 -1
  9. package/dist/index103.mjs +1 -1
  10. package/dist/index104.mjs +2 -2
  11. package/dist/index105.mjs +2 -2
  12. package/dist/index106.mjs +3 -3
  13. package/dist/index107.mjs +1 -1
  14. package/dist/index108.mjs +3 -3
  15. package/dist/index109.mjs +2 -2
  16. package/dist/index110.mjs +2 -2
  17. package/dist/index111.mjs +1 -1
  18. package/dist/index112.mjs +3 -3
  19. package/dist/index113.mjs +1 -1
  20. package/dist/index114.mjs +1 -1
  21. package/dist/index115.mjs +3 -3
  22. package/dist/index116.mjs +2 -2
  23. package/dist/index117.mjs +4 -4
  24. package/dist/index118.mjs +1 -1
  25. package/dist/index119.mjs +3 -3
  26. package/dist/index12.mjs +456 -438
  27. package/dist/index120.mjs +4 -4
  28. package/dist/index121.mjs +1 -1
  29. package/dist/index122.mjs +1 -1
  30. package/dist/index123.mjs +1 -1
  31. package/dist/index124.mjs +1 -1
  32. package/dist/index125.mjs +1 -1
  33. package/dist/index126.mjs +1 -1
  34. package/dist/index127.mjs +1 -1
  35. package/dist/index128.mjs +2 -2
  36. package/dist/index129.mjs +3 -3
  37. package/dist/index132.mjs +1 -1
  38. package/dist/index134.mjs +2 -21
  39. package/dist/index135.mjs +2 -56
  40. package/dist/index136.mjs +2 -29
  41. package/dist/index137.mjs +2 -7
  42. package/dist/index138.mjs +16 -50
  43. package/dist/index139.mjs +32 -6
  44. package/dist/index140.mjs +37 -11
  45. package/dist/index141.mjs +18 -7
  46. package/dist/index142.mjs +18 -28
  47. package/dist/index143.mjs +2 -2
  48. package/dist/index144.mjs +20 -70
  49. package/dist/index145.mjs +42 -163
  50. package/dist/index147.mjs +30 -2
  51. package/dist/index148.mjs +18 -2
  52. package/dist/index149.mjs +2 -2
  53. package/dist/index150.mjs +2 -18
  54. package/dist/index151.mjs +2 -32
  55. package/dist/index152.mjs +2 -38
  56. package/dist/index153.mjs +67 -13
  57. package/dist/index154.mjs +2 -18
  58. package/dist/index155.mjs +53 -2
  59. package/dist/index156.mjs +2 -20
  60. package/dist/index157.mjs +29 -39
  61. package/dist/index158.mjs +152 -2
  62. package/dist/index159.mjs +2 -30
  63. package/dist/index160.mjs +2 -18
  64. package/dist/index161.mjs +19 -2
  65. package/dist/index162.mjs +2 -2
  66. package/dist/index163.mjs +2 -2
  67. package/dist/index164.mjs +26 -2
  68. package/dist/index165.mjs +2 -72
  69. package/dist/index166.mjs +2 -2
  70. package/dist/index167.mjs +20 -49
  71. package/dist/index168.mjs +23 -2
  72. package/dist/index169.mjs +2 -36
  73. package/dist/index170.mjs +2 -152
  74. package/dist/index171.mjs +2 -2
  75. package/dist/index172.mjs +23 -2
  76. package/dist/index173.mjs +2 -19
  77. package/dist/index174.mjs +23 -2
  78. package/dist/index176.mjs +2 -26
  79. package/dist/index177.mjs +2 -2
  80. package/dist/index178.mjs +23 -2
  81. package/dist/index179.mjs +2 -24
  82. package/dist/index180.mjs +15 -15
  83. package/dist/index181.mjs +2 -2
  84. package/dist/index182.mjs +2 -2
  85. package/dist/index183.mjs +2 -2
  86. package/dist/index184.mjs +2 -23
  87. package/dist/index185.mjs +2 -2
  88. package/dist/index186.mjs +2 -23
  89. package/dist/index187.mjs +2 -2
  90. package/dist/index188.mjs +2 -2
  91. package/dist/index189.mjs +127 -2
  92. package/dist/index190.mjs +2 -23
  93. package/dist/index191.mjs +74 -2
  94. package/dist/index192.mjs +71 -20
  95. package/dist/index193.mjs +21 -2
  96. package/dist/index194.mjs +56 -2
  97. package/dist/index195.mjs +29 -2
  98. package/dist/index196.mjs +7 -2
  99. package/dist/index197.mjs +52 -2
  100. package/dist/index198.mjs +6 -2
  101. package/dist/index199.mjs +12 -2
  102. package/dist/index2.mjs +10 -10
  103. package/dist/index20.mjs +2 -2
  104. package/dist/index200.mjs +7 -2
  105. package/dist/index201.mjs +28 -127
  106. package/dist/index202.mjs +2 -2
  107. package/dist/index203.mjs +69 -73
  108. package/dist/index204.mjs +164 -71
  109. package/dist/index205.mjs +2 -31
  110. package/dist/index206.mjs +2 -11
  111. package/dist/index207.mjs +2 -4
  112. package/dist/index208.mjs +2 -4
  113. package/dist/index209.mjs +2 -13
  114. package/dist/index21.mjs +2 -2
  115. package/dist/index210.mjs +2 -7
  116. package/dist/index211.mjs +107 -11
  117. package/dist/index212.mjs +2 -5
  118. package/dist/index213.mjs +2 -33
  119. package/dist/index214.mjs +2 -31
  120. package/dist/index215.mjs +2 -28
  121. package/dist/index216.mjs +241 -58
  122. package/dist/index218.mjs +33 -2
  123. package/dist/index219.mjs +65 -2
  124. package/dist/index220.mjs +25 -2
  125. package/dist/index221.mjs +2 -2
  126. package/dist/index222.mjs +37 -2
  127. package/dist/index225.mjs +2 -108
  128. package/dist/index226.mjs +2 -2
  129. package/dist/index227.mjs +2 -2
  130. package/dist/index228.mjs +2 -2
  131. package/dist/index229.mjs +2 -37
  132. package/dist/index231.mjs +2 -244
  133. package/dist/index232.mjs +2 -2
  134. package/dist/index233.mjs +4 -33
  135. package/dist/index234.mjs +2 -65
  136. package/dist/index235.mjs +2 -25
  137. package/dist/index236.mjs +31 -2
  138. package/dist/index237.mjs +11 -2
  139. package/dist/index238.mjs +4 -2
  140. package/dist/index239.mjs +4 -2
  141. package/dist/index240.mjs +13 -2
  142. package/dist/index241.mjs +7 -2
  143. package/dist/index242.mjs +12 -2
  144. package/dist/index243.mjs +5 -2
  145. package/dist/index244.mjs +33 -2
  146. package/dist/index245.mjs +31 -4
  147. package/dist/index246.mjs +28 -2
  148. package/dist/index247.mjs +61 -2
  149. package/dist/index248.mjs +2 -2
  150. package/dist/index249.mjs +2 -18
  151. package/dist/index25.mjs +53 -48
  152. package/dist/index250.mjs +13 -42
  153. package/dist/index251.mjs +47 -2
  154. package/dist/index252.mjs +2 -2
  155. package/dist/index257.mjs +1 -2
  156. package/dist/index258.mjs +3 -2
  157. package/dist/index259.mjs +2 -2
  158. package/dist/index260.mjs +2 -17
  159. package/dist/index261.mjs +15 -11
  160. package/dist/index262.mjs +13 -6
  161. package/dist/index263.mjs +6 -30
  162. package/dist/index264.mjs +30 -2
  163. package/dist/index3.mjs +133 -102
  164. package/dist/index32.mjs +26 -26
  165. package/dist/index37.mjs +2 -2
  166. package/dist/index38.mjs +1 -1
  167. package/dist/index4.mjs +50 -49
  168. package/dist/index42.mjs +1 -1
  169. package/dist/index47.mjs +17 -17
  170. package/dist/index48.mjs +2 -2
  171. package/dist/index49.mjs +59 -59
  172. package/dist/index50.mjs +50 -50
  173. package/dist/index51.mjs +48 -48
  174. package/dist/index52.mjs +56 -56
  175. package/dist/index53.mjs +39 -39
  176. package/dist/index54.mjs +55 -55
  177. package/dist/index55.mjs +15 -15
  178. package/dist/index56.mjs +10 -10
  179. package/dist/index57.mjs +23 -23
  180. package/dist/index58.mjs +64 -64
  181. package/dist/index59.mjs +73 -73
  182. package/dist/index60.mjs +21 -20
  183. package/dist/index61.mjs +2 -24
  184. package/dist/index63.mjs +23 -2
  185. package/dist/index66.mjs +2 -2
  186. package/dist/index75.mjs +2 -235
  187. package/dist/index76.mjs +233 -4
  188. package/dist/index77.mjs +5 -133
  189. package/dist/index78.mjs +129 -63
  190. package/dist/index79.mjs +67 -86
  191. package/dist/index80.mjs +85 -27
  192. package/dist/index81.mjs +28 -8
  193. package/dist/index82.mjs +8 -74
  194. package/dist/index83.mjs +74 -3
  195. package/dist/index84.mjs +4 -2
  196. package/dist/index85.mjs +2 -83
  197. package/dist/index86.mjs +81 -52
  198. package/dist/index87.mjs +53 -5
  199. package/dist/index88.mjs +5 -4
  200. package/dist/index89.mjs +4 -178
  201. package/dist/index90.mjs +174 -48
  202. package/dist/index91.mjs +51 -67
  203. package/dist/index92.mjs +69 -34
  204. package/dist/index93.mjs +31 -40
  205. package/dist/index94.mjs +43 -2
  206. package/dist/index96.mjs +1 -1
  207. package/dist/index97.mjs +1 -1
  208. package/dist/index98.mjs +6 -6
  209. package/dist/index99.mjs +4 -4
  210. package/dist/providers/MetaPixelProvider.d.ts +10 -5
  211. package/dist/providers/MetaPixelProvider.d.ts.map +1 -1
  212. package/dist/providers/StorefrontProvider.d.ts.map +1 -1
  213. package/dist/styles.css +1 -1
  214. package/package.json +12 -11
  215. package/src/components/CartItem.stories.tsx +5 -5
  216. package/src/components/Checkout.stories.tsx +16 -15
  217. package/src/components/Checkout.tsx +42 -0
  218. package/src/components/ShoppingCart.stories.tsx +17 -16
  219. package/src/components/ShoppingCart.tsx +3 -11
  220. package/src/contexts/CartContext.tsx +70 -6
  221. package/src/providers/MetaPixelProvider.tsx +24 -16
  222. package/src/providers/StorefrontProvider.tsx +4 -1
  223. package/src/test-utils/MockCartProvider.tsx +31 -25
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@instockng/storefront-ui",
3
- "version": "1.0.21",
3
+ "version": "1.0.23",
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,6 +20,13 @@
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 || true",
26
+ "prepublishOnly": "npm run build",
27
+ "storybook": "storybook dev -p 6006",
28
+ "build-storybook": "storybook build"
29
+ },
23
30
  "repository": {
24
31
  "type": "git",
25
32
  "url": "git+https://github.com/ola-wale/oms.git",
@@ -38,7 +45,7 @@
38
45
  "author": "Ola Wale",
39
46
  "license": "MIT",
40
47
  "dependencies": {
41
- "@instockng/api-client": "^1.0.2",
48
+ "@instockng/api-client": "^1.0.6",
42
49
  "class-variance-authority": "^0.7.1",
43
50
  "clsx": "^2.1.0",
44
51
  "react-facebook-pixel": "^1.0.4",
@@ -58,8 +65,8 @@
58
65
  "@storybook/test": "^8.6.14",
59
66
  "@tailwindcss/postcss": "^4.1.14",
60
67
  "@tanstack/react-query": "^5.17.19",
61
- "@types/react": "^19",
62
- "@types/react-dom": "^19",
68
+ "@types/react": "^19.0.1",
69
+ "@types/react-dom": "^19.0.1",
63
70
  "@vitejs/plugin-react": "^4.7.0",
64
71
  "autoprefixer": "^10.4.21",
65
72
  "lucide-react": "^0.536.0",
@@ -72,11 +79,5 @@
72
79
  "tailwindcss": "^4.1.14",
73
80
  "typescript": "^5.3.3",
74
81
  "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"
81
82
  }
82
- }
83
+ }
@@ -20,7 +20,7 @@ const mockCartItem: CartItemType = {
20
20
  variant: {
21
21
  createdAt: new Date().toISOString(),
22
22
  updatedAt: new Date().toISOString(),
23
- deletedAt: null,
23
+ deletedAt: null as any,
24
24
  isActive: true,
25
25
  id: 'variant-1',
26
26
  name: 'Medium - Blue',
@@ -38,10 +38,10 @@ const mockCartItem: CartItemType = {
38
38
  brandId: 'brand-1',
39
39
  createdAt: new Date().toISOString(),
40
40
  updatedAt: new Date().toISOString(),
41
- deletedAt: null,
41
+ deletedAt: null as any,
42
42
  isActive: true,
43
43
  description: 'Premium cotton t-shirt',
44
- quantityDiscounts: null,
44
+ quantityDiscounts: null as any,
45
45
  },
46
46
  },
47
47
  basePrice: 29.99,
@@ -69,10 +69,10 @@ export const NoImage: Story = {
69
69
  slug: 'cotton-shirt',
70
70
  createdAt: new Date().toISOString(),
71
71
  updatedAt: new Date().toISOString(),
72
- deletedAt: null,
72
+ deletedAt: null as any,
73
73
  isActive: true,
74
74
  description: 'Premium cotton t-shirt',
75
- quantityDiscounts: null,
75
+ quantityDiscounts: null as any,
76
76
  thumbnailUrl: null,
77
77
  },
78
78
  },
@@ -24,9 +24,9 @@ const mockDeliveryZones = [
24
24
  name: 'Ikeja',
25
25
  deliveryCost: 1500,
26
26
  freeShippingThreshold: 10000,
27
- allowCOD: true,
27
+ allowCOD: false,
28
28
  allowOnline: true,
29
- waybillOnly: false,
29
+ waybillOnly: true,
30
30
  estimatedDays: 2,
31
31
  },
32
32
  {
@@ -106,19 +106,20 @@ const mockCart: Cart = {
106
106
  logoUrl: 'https://images.unsplash.com/photo-1599305445671-ac291c95aaa9?w=200&h=80&fit=crop',
107
107
  siteUrl: 'https://demo-store.com',
108
108
  domain: 'demo-store.com',
109
+ metaPixelId: null as any,
109
110
  createdAt: new Date().toISOString(),
110
111
  updatedAt: new Date().toISOString(),
111
- deletedAt: null,
112
+ deletedAt: null as any,
112
113
  },
113
- customerPhone: null,
114
- customerEmail: null,
115
- customerFirstName: null,
116
- customerLastName: null,
114
+ customerPhone: null as any,
115
+ customerEmail: null as any,
116
+ customerFirstName: null as any,
117
+ customerLastName: null as any,
117
118
  availablePaymentMethods: ['cod'],
118
- deliveryZone: null,
119
+ deliveryZone: null as any,
119
120
  recoveryAttempts: 0,
120
- lastRecoveryAttemptAt: null,
121
- recoveryDiscountCode: null,
121
+ lastRecoveryAttemptAt: null as any,
122
+ recoveryDiscountCode: null as any,
122
123
  items: [
123
124
  {
124
125
  id: 'item-1',
@@ -134,7 +135,7 @@ const mockCart: Cart = {
134
135
  isActive: true,
135
136
  createdAt: new Date().toISOString(),
136
137
  updatedAt: new Date().toISOString(),
137
- deletedAt: null,
138
+ deletedAt: null as any,
138
139
  product: {
139
140
  id: 'product-1',
140
141
  name: 'Premium Cotton T-Shirt',
@@ -145,8 +146,8 @@ const mockCart: Cart = {
145
146
  thumbnailUrl: 'https://images.unsplash.com/photo-1521572163474-6864f9cf17ab?w=400',
146
147
  createdAt: new Date().toISOString(),
147
148
  updatedAt: new Date().toISOString(),
148
- deletedAt: null,
149
- quantityDiscounts: null,
149
+ deletedAt: null as any,
150
+ quantityDiscounts: null as any,
150
151
  },
151
152
  },
152
153
  quantity: 2,
@@ -194,12 +195,12 @@ const mockCart: Cart = {
194
195
  pricing: {
195
196
  subtotal: 8697,
196
197
  deliveryCharge: 0,
197
- discount: null,
198
+ discount: null as any,
198
199
  total: 8697,
199
200
  },
200
201
  createdAt: new Date().toISOString(),
201
202
  updatedAt: new Date().toISOString(),
202
- convertedToOrderId: null,
203
+ convertedToOrderId: null as any,
203
204
  wasRecovered: false,
204
205
  recoveryUrl: 'https://demo-store.com?cartId=mock-cart-123',
205
206
  };
@@ -205,6 +205,10 @@ export function Checkout({
205
205
  return states?.find((state) => state.id === selectedStateId);
206
206
  }, [states, selectedStateId]);
207
207
 
208
+ const selectedDeliveryZone = useMemo(() => {
209
+ return selectedState?.zones.find((zone) => zone.id === selectedDeliveryZoneId);
210
+ }, [selectedState, selectedDeliveryZoneId]);
211
+
208
212
  // Use checkout mutation from cart context
209
213
  const checkout = checkoutMutation;
210
214
 
@@ -651,6 +655,44 @@ export function Checkout({
651
655
  <div className="w-full overflow-hidden">
652
656
  {/* Sliding Steps Container */}
653
657
  <div className="relative w-full">
658
+ {/* Delivery Zone Notices */}
659
+ {selectedDeliveryZone && (currentStep === 'delivery' || currentStep === 'payment') && (
660
+ <div className="space-y-3 p-4 pb-0">
661
+ {/* Waybill Delivery Notice */}
662
+ {selectedDeliveryZone.waybillOnly && (
663
+ <div className="bg-amber-50 border border-amber-200 rounded-lg p-4">
664
+ <div className="flex items-start gap-3">
665
+ <Package className="h-5 w-5 text-amber-600 flex-shrink-0 mt-0.5" />
666
+ <div className="space-y-1">
667
+ <p className="text-sm font-semibold text-amber-900">
668
+ Waybill Delivery
669
+ </p>
670
+ <p className="text-sm text-amber-800">
671
+ We deliver orders to this location using waybill service. You'll need to pick up your order at a motor park near you.
672
+ </p>
673
+ </div>
674
+ </div>
675
+ </div>
676
+ )}
677
+
678
+ {/* COD Not Available Notice */}
679
+ {!selectedDeliveryZone?.allowCOD && (
680
+ <div className="bg-blue-50 border border-blue-200 rounded-lg p-4">
681
+ <div className="flex items-start gap-3">
682
+ <Banknote className="h-5 w-5 text-blue-600 flex-shrink-0 mt-0.5" />
683
+ <div className="space-y-1">
684
+ <p className="text-sm font-semibold text-blue-900">
685
+ Cash on Delivery Unavailable
686
+ </p>
687
+ <p className="text-sm text-blue-800">
688
+ We're unable to offer cash on delivery to this location. Please use online payment to complete your order.
689
+ </p>
690
+ </div>
691
+ </div>
692
+ </div>
693
+ )}
694
+ </div>
695
+ )}
654
696
  <div
655
697
  className={cn(
656
698
  'flex transition-transform duration-300 ease-in-out w-full',
@@ -52,17 +52,18 @@ const mockCart: Cart = {
52
52
  domain: 'f',
53
53
  createdAt: new Date().toISOString(),
54
54
  updatedAt: new Date().toISOString(),
55
- deletedAt: null,
55
+ metaPixelId: null as any,
56
+ deletedAt: null as any,
56
57
  },
57
- customerPhone: null,
58
- customerEmail: null,
59
- customerFirstName: null,
60
- customerLastName: null,
58
+ customerPhone: null as any,
59
+ customerEmail: null as any,
60
+ customerFirstName: null as any,
61
+ customerLastName: null as any,
61
62
  availablePaymentMethods: ['cod'],
62
- deliveryZone: null,
63
+ deliveryZone: null as any,
63
64
  recoveryAttempts: 0,
64
- lastRecoveryAttemptAt: null,
65
- recoveryDiscountCode: null,
65
+ lastRecoveryAttemptAt: null as any,
66
+ recoveryDiscountCode: null as any,
66
67
  items: [
67
68
  {
68
69
  id: 'item-1',
@@ -78,7 +79,7 @@ const mockCart: Cart = {
78
79
  isActive: true,
79
80
  createdAt: new Date().toISOString(),
80
81
  updatedAt: new Date().toISOString(),
81
- deletedAt: null,
82
+ deletedAt: null as any,
82
83
  product: {
83
84
  id: 'product-1',
84
85
  name: 'Premium Cotton T-Shirt',
@@ -89,7 +90,7 @@ const mockCart: Cart = {
89
90
  thumbnailUrl: 'https://images.unsplash.com/photo-1521572163474-6864f9cf17ab?w=400',
90
91
  createdAt: new Date().toISOString(),
91
92
  updatedAt: new Date().toISOString(),
92
- deletedAt: null,
93
+ deletedAt: null as any,
93
94
  quantityDiscounts: null,
94
95
  },
95
96
  },
@@ -113,7 +114,7 @@ const mockCart: Cart = {
113
114
  isActive: true,
114
115
  createdAt: new Date().toISOString(),
115
116
  updatedAt: new Date().toISOString(),
116
- deletedAt: null,
117
+ deletedAt: null as any,
117
118
  product: {
118
119
  id: 'product-2',
119
120
  name: 'Slim Fit Jeans',
@@ -124,8 +125,8 @@ const mockCart: Cart = {
124
125
  thumbnailUrl: 'https://images.unsplash.com/photo-1542272604-787c3835535d?w=400',
125
126
  createdAt: new Date().toISOString(),
126
127
  updatedAt: new Date().toISOString(),
127
- deletedAt: null,
128
- quantityDiscounts: null,
128
+ deletedAt: null as any,
129
+ quantityDiscounts: null as any,
129
130
  },
130
131
  },
131
132
  quantity: 1,
@@ -138,12 +139,12 @@ const mockCart: Cart = {
138
139
  pricing: {
139
140
  subtotal: 8697,
140
141
  deliveryCharge: 0,
141
- discount: null,
142
+ discount: null as any,
142
143
  total: 8697,
143
144
  },
144
145
  createdAt: new Date().toISOString(),
145
146
  updatedAt: new Date().toISOString(),
146
- convertedToOrderId: null,
147
+ convertedToOrderId: null as any,
147
148
  wasRecovered: false,
148
149
  recoveryUrl: 'https://demo-store.com?cartId=mock-cart-123',
149
150
  };
@@ -311,7 +312,7 @@ export const EmptyCart: CartProviderStory = {
311
312
  items: [],
312
313
  pricing: {
313
314
  subtotal: 0,
314
- discount: null,
315
+ discount: null as any,
315
316
  deliveryCharge: 0,
316
317
  total: 0,
317
318
  },
@@ -15,7 +15,6 @@ import { Checkout } from './Checkout';
15
15
  import { Button } from './ui/button';
16
16
  import { X, Package, Loader2 } from 'lucide-react';
17
17
  import { formatCurrency, cn } from '../lib/utils';
18
- import { useMetaPixel } from '../providers/MetaPixelProvider';
19
18
 
20
19
  export interface ShoppingCartProps {
21
20
  /** Whether the cart is open */
@@ -59,10 +58,7 @@ export function ShoppingCart({
59
58
  emptyMessage = 'Your cart is empty',
60
59
  }: ShoppingCartProps) {
61
60
  // Get cart from CartProvider context
62
- const { cart, isLoading, error } = useCart();
63
-
64
- // Get Meta Pixel tracking
65
- const { trackInitiateCheckout } = useMetaPixel();
61
+ const { cart, isLoading, error, trackCheckoutInitiated } = useCart();
66
62
 
67
63
  // State for checkout modal
68
64
  const [isCheckoutOpen, setIsCheckoutOpen] = useState(false);
@@ -95,12 +91,8 @@ export function ShoppingCart({
95
91
  const discount = cart?.pricing.discount?.amount || 0;
96
92
 
97
93
  const handleCheckout = () => {
98
- // Track InitiateCheckout with Meta Pixel
99
- if (cart) {
100
- const itemCount = cart.items?.reduce((sum, item) => sum + item.quantity, 0) || 0;
101
- const cartTotal = cart.pricing?.total || 0;
102
- trackInitiateCheckout(cartTotal, itemCount);
103
- }
94
+ // Track InitiateCheckout with Meta Pixel (includes event ID for CAPI deduplication)
95
+ trackCheckoutInitiated();
104
96
 
105
97
  setIsCheckoutOpen(true);
106
98
  onCheckout?.();
@@ -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,35 @@ 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 matchingItems = updatedCart.items?.filter(
285
+ (item) => item.variant?.sku === sku
286
+ );
287
+ const addedItem = matchingItems?.[matchingItems.length - 1];
288
+
289
+ if (addedItem) {
290
+ // Generate event ID matching backend format: cart_{cartId}_item_{itemId}
291
+ const eventID = `cart_${cartId}_item_${addedItem.id}`;
292
+
293
+ // Track AddToCart event with Meta Pixel and matching event ID
294
+ trackAddToCart(productSlug, productName, price, quantity, eventID);
295
+ } else {
296
+ // Fallback without event ID if item not found
297
+ trackAddToCart(productSlug, productName, price, quantity);
298
+ }
252
299
  },
253
300
  // eslint-disable-next-line react-hooks/exhaustive-deps
254
- [cartId, trackAddToCart]
301
+ [cartId, trackAddToCart, getFbCookies]
255
302
  );
256
303
 
257
304
  const updateItem = useCallback(
@@ -310,6 +357,22 @@ export function CartProvider({ children, brandSlug, initialCartId, shoppingCartP
310
357
  setIsOpen(false);
311
358
  }, []);
312
359
 
360
+ const trackCheckoutInitiated = useCallback(() => {
361
+ if (!cart || !cartId) return;
362
+
363
+ // Get cart total from pricing
364
+ const cartTotal = Number(cart.pricing?.total || 0);
365
+
366
+ // Calculate item count
367
+ const itemCount = cart.items?.reduce((sum, item) => sum + item.quantity, 0) || 0;
368
+
369
+ // Generate event ID matching backend format: cart_{cartId}_checkout
370
+ const eventID = `cart_${cartId}_checkout`;
371
+
372
+ // Track InitiateCheckout event with Meta Pixel
373
+ trackInitiateCheckout(cartTotal, itemCount, eventID);
374
+ }, [cart, cartId, trackInitiateCheckout]);
375
+
313
376
  const value: CartContextValue = {
314
377
  cart: cart || null,
315
378
  cartId,
@@ -322,6 +385,7 @@ export function CartProvider({ children, brandSlug, initialCartId, shoppingCartP
322
385
  removeItem,
323
386
  applyDiscount,
324
387
  removeDiscount,
388
+ trackCheckoutInitiated,
325
389
  clearCart,
326
390
  clearAndCreateNewCart,
327
391
  refetch,
@@ -18,7 +18,7 @@ interface MetaPixelContextValue {
18
18
  /**
19
19
  * Track a custom event
20
20
  */
21
- track: (event: string, data?: Record<string, unknown>) => void;
21
+ track: (event: string, data?: Record<string, unknown>, eventID?: string) => void;
22
22
 
23
23
  /**
24
24
  * Track a page view
@@ -28,7 +28,7 @@ interface MetaPixelContextValue {
28
28
  /**
29
29
  * Track product view
30
30
  */
31
- trackProductView: (productId: string, productName: string, price: number) => void;
31
+ trackProductView: (productId: string, productName: string, price: number, eventID?: string) => void;
32
32
 
33
33
  /**
34
34
  * Track add to cart event
@@ -36,13 +36,17 @@ interface MetaPixelContextValue {
36
36
  * @param productName - Product name
37
37
  * @param price - Unit price (not total)
38
38
  * @param quantity - Quantity added
39
+ * @param eventID - Optional event ID for deduplication with CAPI
39
40
  */
40
- trackAddToCart: (productId: string, productName: string, price: number, quantity: number) => void;
41
+ trackAddToCart: (productId: string, productName: string, price: number, quantity: number, eventID?: string) => void;
41
42
 
42
43
  /**
43
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
44
48
  */
45
- trackInitiateCheckout: (cartTotal: number, itemCount: number) => void;
49
+ trackInitiateCheckout: (cartTotal: number, itemCount: number, eventID?: string) => void;
46
50
 
47
51
  /**
48
52
  * Track purchase event
@@ -50,8 +54,9 @@ interface MetaPixelContextValue {
50
54
  * @param currency - Currency code (e.g., 'NGN', 'USD')
51
55
  * @param orderId - Order ID or order number
52
56
  * @param items - Array of purchased items with SKU, quantity, and price
57
+ * @param eventID - Optional event ID for deduplication with CAPI
53
58
  */
54
- trackPurchase: (orderTotal: number, currency: string, orderId: string, items: PurchaseItem[]) => void;
59
+ trackPurchase: (orderTotal: number, currency: string, orderId: string, items: PurchaseItem[], eventID?: string) => void;
55
60
 
56
61
  /**
57
62
  * Whether the pixel is initialized
@@ -126,7 +131,7 @@ export function MetaPixelProvider({ children, pixelId, debug }: MetaPixelProvide
126
131
  });
127
132
  }, [pixelId, shouldDebug]);
128
133
 
129
- const track = (event: string, data?: Record<string, unknown>) => {
134
+ const track = (event: string, data?: Record<string, unknown>, eventID?: string) => {
130
135
  if (!isInitialized || !reactPixel) {
131
136
  if (shouldDebug) {
132
137
  console.log('[Meta Pixel] Track (not initialized):', event, data);
@@ -134,10 +139,13 @@ export function MetaPixelProvider({ children, pixelId, debug }: MetaPixelProvide
134
139
  return;
135
140
  }
136
141
 
137
- reactPixel.track(event, data);
142
+ // Include eventID for deduplication with CAPI
143
+ const eventData = eventID ? { ...data, eventID } : data;
144
+
145
+ reactPixel.track(event, eventData);
138
146
 
139
147
  if (shouldDebug) {
140
- console.log('[Meta Pixel] Track:', event, data);
148
+ console.log('[Meta Pixel] Track:', event, eventData, eventID ? `(ID: ${eventID})` : '');
141
149
  }
142
150
  };
143
151
 
@@ -156,17 +164,17 @@ export function MetaPixelProvider({ children, pixelId, debug }: MetaPixelProvide
156
164
  }
157
165
  };
158
166
 
159
- const trackProductView = (productId: string, productName: string, price: number) => {
167
+ const trackProductView = (productId: string, productName: string, price: number, eventID?: string) => {
160
168
  track('ViewContent', {
161
169
  content_ids: [productId],
162
170
  content_name: productName,
163
171
  content_type: 'product',
164
172
  value: price,
165
173
  currency: 'NGN',
166
- });
174
+ }, eventID);
167
175
  };
168
176
 
169
- const trackAddToCart = (productId: string, productName: string, price: number, quantity: number) => {
177
+ const trackAddToCart = (productId: string, productName: string, price: number, quantity: number, eventID?: string) => {
170
178
  track('AddToCart', {
171
179
  content_ids: [productId],
172
180
  content_name: productName,
@@ -174,18 +182,18 @@ export function MetaPixelProvider({ children, pixelId, debug }: MetaPixelProvide
174
182
  value: price,
175
183
  currency: 'NGN',
176
184
  num_items: quantity,
177
- });
185
+ }, eventID);
178
186
  };
179
187
 
180
- const trackInitiateCheckout = (cartTotal: number, itemCount: number) => {
188
+ const trackInitiateCheckout = (cartTotal: number, itemCount: number, eventID?: string) => {
181
189
  track('InitiateCheckout', {
182
190
  value: cartTotal,
183
191
  currency: 'NGN',
184
192
  num_items: itemCount,
185
- });
193
+ }, eventID);
186
194
  };
187
195
 
188
- const trackPurchase = (orderTotal: number, currency: string, orderId: string, items: PurchaseItem[]) => {
196
+ const trackPurchase = (orderTotal: number, currency: string, orderId: string, items: PurchaseItem[], eventID?: string) => {
189
197
  track('Purchase', {
190
198
  value: orderTotal,
191
199
  currency,
@@ -196,7 +204,7 @@ export function MetaPixelProvider({ children, pixelId, debug }: MetaPixelProvide
196
204
  item_price: item.item_price,
197
205
  })),
198
206
  order_id: orderId,
199
- });
207
+ }, eventID);
200
208
  };
201
209
 
202
210
  const value: MetaPixelContextValue = {
@@ -82,8 +82,11 @@ export function StorefrontProvider({
82
82
  initialCartId,
83
83
  shoppingCartProps,
84
84
  }: StorefrontProviderProps) {
85
+ // Storefront-ui doesn't use authenticated endpoints, so provide a no-op getAuthToken
86
+ const getAuthToken = async () => '';
87
+
85
88
  return (
86
- <ApiClientProvider {...(apiUrl && { baseURL: apiUrl })}>
89
+ <ApiClientProvider getAuthToken={getAuthToken} {...(apiUrl && { baseURL: apiUrl })}>
87
90
  <StorefrontProviderInner
88
91
  brandSlug={brandSlug}
89
92
  initialCartId={initialCartId}