@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.
- package/dist/components/Checkout.d.ts.map +1 -1
- package/dist/components/ShoppingCart.d.ts.map +1 -1
- package/dist/contexts/CartContext.d.ts +2 -0
- package/dist/contexts/CartContext.d.ts.map +1 -1
- package/dist/index10.mjs +53 -59
- package/dist/index100.mjs +3 -3
- package/dist/index101.mjs +1 -1
- package/dist/index102.mjs +1 -1
- package/dist/index103.mjs +1 -1
- package/dist/index104.mjs +2 -2
- package/dist/index105.mjs +2 -2
- package/dist/index106.mjs +3 -3
- package/dist/index107.mjs +1 -1
- package/dist/index108.mjs +3 -3
- package/dist/index109.mjs +2 -2
- package/dist/index110.mjs +2 -2
- package/dist/index111.mjs +1 -1
- package/dist/index112.mjs +3 -3
- package/dist/index113.mjs +1 -1
- package/dist/index114.mjs +1 -1
- package/dist/index115.mjs +3 -3
- package/dist/index116.mjs +2 -2
- package/dist/index117.mjs +4 -4
- package/dist/index118.mjs +1 -1
- package/dist/index119.mjs +3 -3
- package/dist/index12.mjs +456 -438
- package/dist/index120.mjs +4 -4
- package/dist/index121.mjs +1 -1
- package/dist/index122.mjs +1 -1
- package/dist/index123.mjs +1 -1
- package/dist/index124.mjs +1 -1
- package/dist/index125.mjs +1 -1
- package/dist/index126.mjs +1 -1
- package/dist/index127.mjs +1 -1
- package/dist/index128.mjs +2 -2
- package/dist/index129.mjs +3 -3
- package/dist/index132.mjs +1 -1
- package/dist/index134.mjs +2 -21
- package/dist/index135.mjs +2 -56
- package/dist/index136.mjs +2 -29
- package/dist/index137.mjs +2 -7
- package/dist/index138.mjs +16 -50
- package/dist/index139.mjs +32 -6
- package/dist/index140.mjs +37 -11
- package/dist/index141.mjs +18 -7
- package/dist/index142.mjs +18 -28
- package/dist/index143.mjs +2 -2
- package/dist/index144.mjs +20 -70
- package/dist/index145.mjs +42 -163
- package/dist/index147.mjs +30 -2
- package/dist/index148.mjs +18 -2
- package/dist/index149.mjs +2 -2
- package/dist/index150.mjs +2 -18
- package/dist/index151.mjs +2 -32
- package/dist/index152.mjs +2 -38
- package/dist/index153.mjs +67 -13
- package/dist/index154.mjs +2 -18
- package/dist/index155.mjs +53 -2
- package/dist/index156.mjs +2 -20
- package/dist/index157.mjs +29 -39
- package/dist/index158.mjs +152 -2
- package/dist/index159.mjs +2 -30
- package/dist/index160.mjs +2 -18
- package/dist/index161.mjs +19 -2
- package/dist/index162.mjs +2 -2
- package/dist/index163.mjs +2 -2
- package/dist/index164.mjs +26 -2
- package/dist/index165.mjs +2 -72
- package/dist/index166.mjs +2 -2
- package/dist/index167.mjs +20 -49
- package/dist/index168.mjs +23 -2
- package/dist/index169.mjs +2 -36
- package/dist/index170.mjs +2 -152
- package/dist/index171.mjs +2 -2
- package/dist/index172.mjs +23 -2
- package/dist/index173.mjs +2 -19
- package/dist/index174.mjs +23 -2
- package/dist/index176.mjs +2 -26
- package/dist/index177.mjs +2 -2
- package/dist/index178.mjs +23 -2
- package/dist/index179.mjs +2 -24
- package/dist/index180.mjs +15 -15
- package/dist/index181.mjs +2 -2
- package/dist/index182.mjs +2 -2
- package/dist/index183.mjs +2 -2
- package/dist/index184.mjs +2 -23
- package/dist/index185.mjs +2 -2
- package/dist/index186.mjs +2 -23
- package/dist/index187.mjs +2 -2
- package/dist/index188.mjs +2 -2
- package/dist/index189.mjs +127 -2
- package/dist/index190.mjs +2 -23
- package/dist/index191.mjs +74 -2
- package/dist/index192.mjs +71 -20
- package/dist/index193.mjs +21 -2
- package/dist/index194.mjs +56 -2
- package/dist/index195.mjs +29 -2
- package/dist/index196.mjs +7 -2
- package/dist/index197.mjs +52 -2
- package/dist/index198.mjs +6 -2
- package/dist/index199.mjs +12 -2
- package/dist/index2.mjs +10 -10
- package/dist/index20.mjs +2 -2
- package/dist/index200.mjs +7 -2
- package/dist/index201.mjs +28 -127
- package/dist/index202.mjs +2 -2
- package/dist/index203.mjs +69 -73
- package/dist/index204.mjs +164 -71
- package/dist/index205.mjs +2 -31
- package/dist/index206.mjs +2 -11
- package/dist/index207.mjs +2 -4
- package/dist/index208.mjs +2 -4
- package/dist/index209.mjs +2 -13
- package/dist/index21.mjs +2 -2
- package/dist/index210.mjs +2 -7
- package/dist/index211.mjs +107 -11
- package/dist/index212.mjs +2 -5
- package/dist/index213.mjs +2 -33
- package/dist/index214.mjs +2 -31
- package/dist/index215.mjs +2 -28
- package/dist/index216.mjs +241 -58
- package/dist/index218.mjs +33 -2
- package/dist/index219.mjs +65 -2
- package/dist/index220.mjs +25 -2
- package/dist/index221.mjs +2 -2
- package/dist/index222.mjs +37 -2
- package/dist/index225.mjs +2 -108
- package/dist/index226.mjs +2 -2
- package/dist/index227.mjs +2 -2
- package/dist/index228.mjs +2 -2
- package/dist/index229.mjs +2 -37
- package/dist/index231.mjs +2 -244
- package/dist/index232.mjs +2 -2
- package/dist/index233.mjs +4 -33
- package/dist/index234.mjs +2 -65
- package/dist/index235.mjs +2 -25
- package/dist/index236.mjs +31 -2
- package/dist/index237.mjs +11 -2
- package/dist/index238.mjs +4 -2
- package/dist/index239.mjs +4 -2
- package/dist/index240.mjs +13 -2
- package/dist/index241.mjs +7 -2
- package/dist/index242.mjs +12 -2
- package/dist/index243.mjs +5 -2
- package/dist/index244.mjs +33 -2
- package/dist/index245.mjs +31 -4
- package/dist/index246.mjs +28 -2
- package/dist/index247.mjs +61 -2
- package/dist/index248.mjs +2 -2
- package/dist/index249.mjs +2 -18
- package/dist/index25.mjs +53 -48
- package/dist/index250.mjs +13 -42
- package/dist/index251.mjs +47 -2
- package/dist/index252.mjs +2 -2
- package/dist/index257.mjs +1 -2
- package/dist/index258.mjs +3 -2
- package/dist/index259.mjs +2 -2
- package/dist/index260.mjs +2 -17
- package/dist/index261.mjs +15 -11
- package/dist/index262.mjs +13 -6
- package/dist/index263.mjs +6 -30
- package/dist/index264.mjs +30 -2
- package/dist/index3.mjs +133 -102
- package/dist/index32.mjs +26 -26
- package/dist/index37.mjs +2 -2
- package/dist/index38.mjs +1 -1
- package/dist/index4.mjs +50 -49
- package/dist/index42.mjs +1 -1
- package/dist/index47.mjs +17 -17
- package/dist/index48.mjs +2 -2
- package/dist/index49.mjs +59 -59
- package/dist/index50.mjs +50 -50
- package/dist/index51.mjs +48 -48
- package/dist/index52.mjs +56 -56
- package/dist/index53.mjs +39 -39
- package/dist/index54.mjs +55 -55
- package/dist/index55.mjs +15 -15
- package/dist/index56.mjs +10 -10
- package/dist/index57.mjs +23 -23
- package/dist/index58.mjs +64 -64
- package/dist/index59.mjs +73 -73
- package/dist/index60.mjs +21 -20
- package/dist/index61.mjs +2 -24
- package/dist/index63.mjs +23 -2
- package/dist/index66.mjs +2 -2
- package/dist/index75.mjs +2 -235
- package/dist/index76.mjs +233 -4
- package/dist/index77.mjs +5 -133
- package/dist/index78.mjs +129 -63
- package/dist/index79.mjs +67 -86
- package/dist/index80.mjs +85 -27
- package/dist/index81.mjs +28 -8
- package/dist/index82.mjs +8 -74
- package/dist/index83.mjs +74 -3
- package/dist/index84.mjs +4 -2
- package/dist/index85.mjs +2 -83
- package/dist/index86.mjs +81 -52
- package/dist/index87.mjs +53 -5
- package/dist/index88.mjs +5 -4
- package/dist/index89.mjs +4 -178
- package/dist/index90.mjs +174 -48
- package/dist/index91.mjs +51 -67
- package/dist/index92.mjs +69 -34
- package/dist/index93.mjs +31 -40
- package/dist/index94.mjs +43 -2
- package/dist/index96.mjs +1 -1
- package/dist/index97.mjs +1 -1
- package/dist/index98.mjs +6 -6
- package/dist/index99.mjs +4 -4
- package/dist/providers/MetaPixelProvider.d.ts +10 -5
- package/dist/providers/MetaPixelProvider.d.ts.map +1 -1
- package/dist/providers/StorefrontProvider.d.ts.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +12 -11
- package/src/components/CartItem.stories.tsx +5 -5
- package/src/components/Checkout.stories.tsx +16 -15
- package/src/components/Checkout.tsx +42 -0
- package/src/components/ShoppingCart.stories.tsx +17 -16
- package/src/components/ShoppingCart.tsx +3 -11
- package/src/contexts/CartContext.tsx +70 -6
- package/src/providers/MetaPixelProvider.tsx +24 -16
- package/src/providers/StorefrontProvider.tsx +4 -1
- 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.
|
|
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.
|
|
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:
|
|
27
|
+
allowCOD: false,
|
|
28
28
|
allowOnline: true,
|
|
29
|
-
waybillOnly:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
251
|
-
|
|
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
|
|
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
|
-
|
|
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,
|
|
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}
|