@instockng/storefront-ui 1.0.10 → 1.0.11
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 +26 -0
- package/dist/components/Checkout.d.ts.map +1 -1
- package/dist/contexts/CartContext.d.ts.map +1 -1
- package/dist/index10.mjs +144 -141
- package/dist/index101.mjs +1 -1
- package/dist/index102.mjs +3 -3
- package/dist/index103.mjs +3 -3
- package/dist/index111.mjs +1 -1
- package/dist/index20.mjs +2 -2
- package/dist/index21.mjs +1 -1
- package/dist/index28.mjs +11 -11
- package/dist/index29.mjs +1 -1
- package/dist/index3.mjs +83 -73
- package/dist/index30.mjs +1 -1
- package/dist/index31.mjs +1 -1
- package/dist/index32.mjs +1 -1
- package/dist/index37.mjs +1 -1
- package/dist/index41.mjs +36 -23
- package/dist/index42.mjs +44 -36
- package/dist/index43.mjs +99 -44
- package/dist/index44.mjs +112 -99
- package/dist/index45.mjs +44 -80
- package/dist/index46.mjs +64 -53
- package/dist/index47.mjs +66 -49
- package/dist/index48.mjs +54 -73
- package/dist/index49.mjs +52 -63
- package/dist/index50.mjs +14 -70
- package/dist/index51.mjs +13 -14
- package/dist/index52.mjs +58 -13
- package/dist/index53.mjs +101 -34
- package/dist/index54.mjs +99 -95
- package/dist/index55.mjs +22 -132
- package/dist/index58.mjs +2 -2
- package/dist/index59.mjs +4 -3
- package/dist/index60.mjs +4 -2
- package/dist/index61.mjs +2 -5
- package/dist/index62.mjs +30 -231
- package/dist/index63.mjs +42 -5
- package/dist/index64.mjs +228 -127
- package/dist/index65.mjs +4 -66
- package/dist/index66.mjs +124 -77
- package/dist/index67.mjs +65 -26
- package/dist/index68.mjs +84 -6
- package/dist/index69.mjs +26 -72
- package/dist/index70.mjs +8 -3
- package/dist/index71.mjs +75 -2
- package/dist/index72.mjs +3 -82
- package/dist/index73.mjs +2 -54
- package/dist/index74.mjs +82 -5
- package/dist/index75.mjs +53 -4
- package/dist/index76.mjs +5 -178
- package/dist/index77.mjs +5 -53
- package/dist/index78.mjs +178 -68
- package/dist/index79.mjs +50 -31
- package/dist/index80.mjs +69 -43
- package/dist/index81.mjs +1 -1
- package/dist/index82.mjs +1 -1
- package/dist/index83.mjs +5 -5
- package/dist/index85.mjs +2 -2
- package/dist/index87.mjs +2 -2
- package/dist/index89.mjs +1 -1
- package/dist/index91.mjs +4 -4
- package/dist/index92.mjs +3 -3
- package/dist/index93.mjs +1 -1
- package/dist/index94.mjs +3 -3
- package/dist/index99.mjs +1 -1
- package/dist/styles.css +1 -0
- package/package.json +14 -13
- package/src/components/CartItem.stories.tsx +94 -0
- package/src/components/CartItem.tsx +141 -0
- package/src/components/Checkout.stories.tsx +380 -0
- package/src/components/Checkout.tsx +954 -0
- package/src/components/DiscountCodeInput.stories.tsx +76 -0
- package/src/components/DiscountCodeInput.tsx +162 -0
- package/src/components/OrderConfirmation.stories.tsx +142 -0
- package/src/components/OrderConfirmation.tsx +301 -0
- package/src/components/ProductCard.stories.tsx +112 -0
- package/src/components/ProductCard.tsx +195 -0
- package/src/components/ProductGrid.stories.tsx +137 -0
- package/src/components/ProductGrid.tsx +141 -0
- package/src/components/ShoppingCart.stories.tsx +459 -0
- package/src/components/ShoppingCart.tsx +262 -0
- package/src/components/ui/badge.tsx +37 -0
- package/src/components/ui/button.tsx +71 -0
- package/src/components/ui/card.tsx +79 -0
- package/src/components/ui/form-input.tsx +78 -0
- package/src/components/ui/form-select.tsx +73 -0
- package/src/components/ui/modal.tsx +181 -0
- package/src/contexts/CartContext.tsx +305 -0
- package/src/hooks/usePaystackPayment.ts +137 -0
- package/src/index.ts +51 -0
- package/src/lib/utils.ts +45 -0
- package/src/paystack.svg +67 -0
- package/src/providers/StorefrontProvider.tsx +70 -0
- package/src/styles.css +1 -0
- package/src/test-utils/MockCartProvider.tsx +424 -0
- package/src/vite-env.d.ts +12 -0
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checkout Storybook Stories
|
|
3
|
+
*
|
|
4
|
+
* Demonstrates the checkout flow with mocked cart and delivery zone data.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
8
|
+
import { useState } from 'react';
|
|
9
|
+
import { fn } from '@storybook/test';
|
|
10
|
+
import { Checkout, type CheckoutProps } from './Checkout';
|
|
11
|
+
import { Button } from './ui/button';
|
|
12
|
+
import { MockCartProvider } from '../test-utils/MockCartProvider';
|
|
13
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
14
|
+
import type { Cart } from '@oms/api-client';
|
|
15
|
+
|
|
16
|
+
// Mock delivery zones data
|
|
17
|
+
const mockDeliveryZones = [
|
|
18
|
+
{
|
|
19
|
+
id: 'state-1',
|
|
20
|
+
name: 'Lagos',
|
|
21
|
+
zones: [
|
|
22
|
+
{
|
|
23
|
+
id: 'zone-1',
|
|
24
|
+
name: 'Ikeja',
|
|
25
|
+
deliveryCost: 1500,
|
|
26
|
+
freeShippingThreshold: 10000,
|
|
27
|
+
allowCOD: true,
|
|
28
|
+
allowOnline: true,
|
|
29
|
+
waybillOnly: false,
|
|
30
|
+
estimatedDays: 2,
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
id: 'zone-2',
|
|
34
|
+
name: 'Victoria Island',
|
|
35
|
+
deliveryCost: 2000,
|
|
36
|
+
freeShippingThreshold: 15000,
|
|
37
|
+
allowCOD: true,
|
|
38
|
+
allowOnline: true,
|
|
39
|
+
waybillOnly: false,
|
|
40
|
+
estimatedDays: 1,
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: 'zone-3',
|
|
44
|
+
name: 'Lekki',
|
|
45
|
+
deliveryCost: 2500,
|
|
46
|
+
freeShippingThreshold: 15000,
|
|
47
|
+
allowCOD: true,
|
|
48
|
+
allowOnline: true,
|
|
49
|
+
waybillOnly: false,
|
|
50
|
+
estimatedDays: 2,
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
id: 'state-2',
|
|
56
|
+
name: 'Abuja',
|
|
57
|
+
zones: [
|
|
58
|
+
{
|
|
59
|
+
id: 'zone-4',
|
|
60
|
+
name: 'Wuse',
|
|
61
|
+
deliveryCost: 3000,
|
|
62
|
+
freeShippingThreshold: 20000,
|
|
63
|
+
allowCOD: true,
|
|
64
|
+
allowOnline: true,
|
|
65
|
+
waybillOnly: false,
|
|
66
|
+
estimatedDays: 3,
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
id: 'zone-5',
|
|
70
|
+
name: 'Garki',
|
|
71
|
+
deliveryCost: 3000,
|
|
72
|
+
freeShippingThreshold: 20000,
|
|
73
|
+
allowCOD: true,
|
|
74
|
+
allowOnline: true,
|
|
75
|
+
waybillOnly: false,
|
|
76
|
+
estimatedDays: 3,
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
},
|
|
80
|
+
];
|
|
81
|
+
|
|
82
|
+
// Create a query client with mocked delivery zones data
|
|
83
|
+
const createMockQueryClient = () => {
|
|
84
|
+
const queryClient = new QueryClient({
|
|
85
|
+
defaultOptions: {
|
|
86
|
+
queries: {
|
|
87
|
+
retry: false,
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Pre-populate the cache with mock delivery zones
|
|
93
|
+
queryClient.setQueryData(['deliveryZones', 'mock-brand-456'], mockDeliveryZones);
|
|
94
|
+
|
|
95
|
+
return queryClient;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// Mock cart data
|
|
99
|
+
const mockCart: Cart = {
|
|
100
|
+
id: 'mock-cart-123',
|
|
101
|
+
expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24).toISOString(),
|
|
102
|
+
brand: {
|
|
103
|
+
id: 'mock-brand-456',
|
|
104
|
+
name: 'Demo Store',
|
|
105
|
+
slug: 'demo-store',
|
|
106
|
+
logoUrl: 'https://images.unsplash.com/photo-1599305445671-ac291c95aaa9?w=200&h=80&fit=crop',
|
|
107
|
+
siteUrl: 'https://demo-store.com',
|
|
108
|
+
domain: 'demo-store.com',
|
|
109
|
+
createdAt: new Date().toISOString(),
|
|
110
|
+
updatedAt: new Date().toISOString(),
|
|
111
|
+
deletedAt: null,
|
|
112
|
+
},
|
|
113
|
+
customerPhone: null,
|
|
114
|
+
customerEmail: null,
|
|
115
|
+
customerFirstName: null,
|
|
116
|
+
customerLastName: null,
|
|
117
|
+
availablePaymentMethods: ['cod'],
|
|
118
|
+
deliveryZone: null,
|
|
119
|
+
recoveryAttempts: 0,
|
|
120
|
+
lastRecoveryAttemptAt: null,
|
|
121
|
+
recoveryDiscountCode: null,
|
|
122
|
+
items: [
|
|
123
|
+
{
|
|
124
|
+
id: 'item-1',
|
|
125
|
+
variant: {
|
|
126
|
+
id: 'variant-1',
|
|
127
|
+
productId: 'product-1',
|
|
128
|
+
name: 'Medium - Blue',
|
|
129
|
+
price: 2999,
|
|
130
|
+
sku: 'TSHIRT-MD-BLUE',
|
|
131
|
+
thumbnailUrl: 'https://images.unsplash.com/photo-1521572163474-6864f9cf17ab?w=400',
|
|
132
|
+
trackInventory: true,
|
|
133
|
+
lowStockThreshold: 10,
|
|
134
|
+
isActive: true,
|
|
135
|
+
createdAt: new Date().toISOString(),
|
|
136
|
+
updatedAt: new Date().toISOString(),
|
|
137
|
+
deletedAt: null,
|
|
138
|
+
product: {
|
|
139
|
+
id: 'product-1',
|
|
140
|
+
name: 'Premium Cotton T-Shirt',
|
|
141
|
+
slug: 'cotton-shirt',
|
|
142
|
+
brandId: 'mock-brand-456',
|
|
143
|
+
isActive: true,
|
|
144
|
+
description: 'Comfortable premium cotton t-shirt',
|
|
145
|
+
thumbnailUrl: 'https://images.unsplash.com/photo-1521572163474-6864f9cf17ab?w=400',
|
|
146
|
+
createdAt: new Date().toISOString(),
|
|
147
|
+
updatedAt: new Date().toISOString(),
|
|
148
|
+
deletedAt: null,
|
|
149
|
+
quantityDiscounts: null,
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
quantity: 2,
|
|
153
|
+
basePrice: 2999,
|
|
154
|
+
discountPercent: 0,
|
|
155
|
+
finalPrice: 2599,
|
|
156
|
+
subtotal: 5198,
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
id: 'item-2',
|
|
160
|
+
variant: {
|
|
161
|
+
id: 'variant-2',
|
|
162
|
+
productId: 'product-2',
|
|
163
|
+
name: 'Large - Black',
|
|
164
|
+
price: 3499,
|
|
165
|
+
sku: 'JEANS-LG-BLACK',
|
|
166
|
+
thumbnailUrl: 'https://images.unsplash.com/photo-1542272604-787c3835535d?w=400',
|
|
167
|
+
trackInventory: true,
|
|
168
|
+
lowStockThreshold: 10,
|
|
169
|
+
isActive: true,
|
|
170
|
+
createdAt: new Date().toISOString(),
|
|
171
|
+
updatedAt: new Date().toISOString(),
|
|
172
|
+
deletedAt: null,
|
|
173
|
+
product: {
|
|
174
|
+
id: 'product-2',
|
|
175
|
+
name: 'Slim Fit Jeans',
|
|
176
|
+
brandId: 'mock-brand-456',
|
|
177
|
+
slug: 'cotton-shirt',
|
|
178
|
+
isActive: true,
|
|
179
|
+
description: 'Modern slim fit jeans',
|
|
180
|
+
thumbnailUrl: 'https://images.unsplash.com/photo-1542272604-787c3835535d?w=400',
|
|
181
|
+
createdAt: new Date().toISOString(),
|
|
182
|
+
updatedAt: new Date().toISOString(),
|
|
183
|
+
deletedAt: null,
|
|
184
|
+
quantityDiscounts: null,
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
quantity: 1,
|
|
188
|
+
basePrice: 3499,
|
|
189
|
+
discountPercent: 0,
|
|
190
|
+
finalPrice: 3499,
|
|
191
|
+
subtotal: 3499,
|
|
192
|
+
},
|
|
193
|
+
],
|
|
194
|
+
pricing: {
|
|
195
|
+
subtotal: 8697,
|
|
196
|
+
deliveryCharge: 0,
|
|
197
|
+
discount: null,
|
|
198
|
+
total: 8697,
|
|
199
|
+
},
|
|
200
|
+
createdAt: new Date().toISOString(),
|
|
201
|
+
updatedAt: new Date().toISOString(),
|
|
202
|
+
convertedToOrderId: null,
|
|
203
|
+
wasRecovered: false,
|
|
204
|
+
recoveryUrl: 'https://demo-store.com?cartId=mock-cart-123',
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const meta = {
|
|
208
|
+
title: 'Components/Checkout',
|
|
209
|
+
component: Checkout,
|
|
210
|
+
parameters: {
|
|
211
|
+
layout: 'centered',
|
|
212
|
+
},
|
|
213
|
+
tags: ['autodocs'],
|
|
214
|
+
} satisfies Meta<typeof Checkout>;
|
|
215
|
+
|
|
216
|
+
export default meta;
|
|
217
|
+
|
|
218
|
+
// Wrapper component to handle modal state and provide mock data
|
|
219
|
+
const CheckoutWrapper = (args: any) => {
|
|
220
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
221
|
+
const queryClient = createMockQueryClient();
|
|
222
|
+
|
|
223
|
+
return (
|
|
224
|
+
<QueryClientProvider client={queryClient}>
|
|
225
|
+
<MockCartProvider cart={mockCart} renderCart={false}>
|
|
226
|
+
<div className="flex flex-col items-center gap-4 p-8">
|
|
227
|
+
<div className="text-center">
|
|
228
|
+
<h2 className="text-2xl font-bold mb-2">Checkout Flow Demo</h2>
|
|
229
|
+
<p className="text-gray-600 mb-4">
|
|
230
|
+
Click the button below to start the checkout process with mock data
|
|
231
|
+
</p>
|
|
232
|
+
</div>
|
|
233
|
+
<Button onClick={() => setIsOpen(true)} size="lg">
|
|
234
|
+
Start Checkout
|
|
235
|
+
</Button>
|
|
236
|
+
</div>
|
|
237
|
+
<Checkout
|
|
238
|
+
{...args}
|
|
239
|
+
isOpen={isOpen}
|
|
240
|
+
onClose={() => setIsOpen(false)}
|
|
241
|
+
onSuccess={(orderId) => {
|
|
242
|
+
args.onSuccess?.(orderId);
|
|
243
|
+
}}
|
|
244
|
+
onError={(error) => {
|
|
245
|
+
console.error('Checkout error:', error);
|
|
246
|
+
alert(`Checkout failed: ${error.message}`);
|
|
247
|
+
args.onError?.(error);
|
|
248
|
+
}}
|
|
249
|
+
/>
|
|
250
|
+
</MockCartProvider>
|
|
251
|
+
</QueryClientProvider>
|
|
252
|
+
);
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
const mockRefundPolicy = {
|
|
256
|
+
title: '🤔 Curious about Refunds?',
|
|
257
|
+
policies: [
|
|
258
|
+
'2 day return window from delivery date',
|
|
259
|
+
'Items must be unused and in original packaging',
|
|
260
|
+
'Refunds processed as soon as item is received',
|
|
261
|
+
'Refunds issued to original payment method',
|
|
262
|
+
'Contact support@example.com to initiate refund',
|
|
263
|
+
],
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
// Type for story args that excludes modal-controlled props
|
|
267
|
+
type CheckoutStoryArgs = Omit<CheckoutProps, 'isOpen' | 'onClose'>;
|
|
268
|
+
|
|
269
|
+
// Default checkout with mock data
|
|
270
|
+
export const Default: StoryObj<CheckoutStoryArgs> = {
|
|
271
|
+
render: (args) => <CheckoutWrapper {...args} />,
|
|
272
|
+
args: {
|
|
273
|
+
onSuccess: fn(),
|
|
274
|
+
onError: fn(),
|
|
275
|
+
refundPolicy: mockRefundPolicy,
|
|
276
|
+
},
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
// Checkout with pre-filled customer data
|
|
280
|
+
export const WithPrefilledData: StoryObj<CheckoutStoryArgs> = {
|
|
281
|
+
render: (args) => <CheckoutWrapper {...args} />,
|
|
282
|
+
args: {
|
|
283
|
+
...Default.args,
|
|
284
|
+
initialData: {
|
|
285
|
+
firstName: 'John',
|
|
286
|
+
lastName: 'Doe',
|
|
287
|
+
email: 'john.doe@example.com',
|
|
288
|
+
phone: '08012345678',
|
|
289
|
+
address: '123 Main Street',
|
|
290
|
+
city: 'Ikeja',
|
|
291
|
+
deliveryZoneId: 'zone-1',
|
|
292
|
+
paymentMethod: 'online',
|
|
293
|
+
},
|
|
294
|
+
},
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
// Without refund policy accordion
|
|
298
|
+
export const NoRefundPolicy: StoryObj<CheckoutStoryArgs> = {
|
|
299
|
+
render: (args) => <CheckoutWrapper {...args} />,
|
|
300
|
+
args: {
|
|
301
|
+
...Default.args,
|
|
302
|
+
refundPolicy: undefined,
|
|
303
|
+
},
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
// Cash on delivery pre-selected
|
|
307
|
+
export const CashOnDeliverySelected: StoryObj<CheckoutStoryArgs> = {
|
|
308
|
+
render: (args) => <CheckoutWrapper {...args} />,
|
|
309
|
+
args: {
|
|
310
|
+
...Default.args,
|
|
311
|
+
initialData: {
|
|
312
|
+
paymentMethod: 'cod',
|
|
313
|
+
},
|
|
314
|
+
},
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
// Cart with discount applied
|
|
318
|
+
export const WithDiscount: StoryObj<CheckoutStoryArgs> = {
|
|
319
|
+
render: (args) => {
|
|
320
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
321
|
+
const queryClient = createMockQueryClient();
|
|
322
|
+
|
|
323
|
+
const cartWithDiscount: Cart = {
|
|
324
|
+
...mockCart,
|
|
325
|
+
pricing: {
|
|
326
|
+
subtotal: 8697,
|
|
327
|
+
discount: {
|
|
328
|
+
code: 'SAVE10',
|
|
329
|
+
type: 'percentage',
|
|
330
|
+
value: 10,
|
|
331
|
+
amount: 870,
|
|
332
|
+
description: '10% off your order',
|
|
333
|
+
},
|
|
334
|
+
deliveryCharge: 0,
|
|
335
|
+
total: 7827,
|
|
336
|
+
},
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
return (
|
|
340
|
+
<QueryClientProvider client={queryClient}>
|
|
341
|
+
<MockCartProvider cart={cartWithDiscount} renderCart={false}>
|
|
342
|
+
<div className="flex flex-col items-center gap-4 p-8">
|
|
343
|
+
<div className="text-center">
|
|
344
|
+
<h2 className="text-2xl font-bold mb-2">Checkout with Discount</h2>
|
|
345
|
+
<p className="text-gray-600 mb-4">10% discount applied to cart</p>
|
|
346
|
+
</div>
|
|
347
|
+
<Button onClick={() => setIsOpen(true)} size="lg">
|
|
348
|
+
Start Checkout
|
|
349
|
+
</Button>
|
|
350
|
+
</div>
|
|
351
|
+
<Checkout
|
|
352
|
+
{...args}
|
|
353
|
+
isOpen={isOpen}
|
|
354
|
+
onClose={() => setIsOpen(false)}
|
|
355
|
+
onSuccess={(orderId) => {
|
|
356
|
+
args.onSuccess?.(orderId);
|
|
357
|
+
}}
|
|
358
|
+
onError={(error) => {
|
|
359
|
+
console.error('Checkout error:', error);
|
|
360
|
+
alert(`Checkout failed: ${error.message}`);
|
|
361
|
+
args.onError?.(error);
|
|
362
|
+
}}
|
|
363
|
+
/>
|
|
364
|
+
</MockCartProvider>
|
|
365
|
+
</QueryClientProvider>
|
|
366
|
+
);
|
|
367
|
+
},
|
|
368
|
+
args: {
|
|
369
|
+
...Default.args,
|
|
370
|
+
},
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
// Custom submit button text
|
|
374
|
+
export const CustomButtonText: StoryObj<CheckoutStoryArgs> = {
|
|
375
|
+
render: (args) => <CheckoutWrapper {...args} />,
|
|
376
|
+
args: {
|
|
377
|
+
...Default.args,
|
|
378
|
+
submitButtonText: 'Complete Purchase',
|
|
379
|
+
},
|
|
380
|
+
};
|