@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.
Files changed (97) hide show
  1. package/README.md +26 -0
  2. package/dist/components/Checkout.d.ts.map +1 -1
  3. package/dist/contexts/CartContext.d.ts.map +1 -1
  4. package/dist/index10.mjs +144 -141
  5. package/dist/index101.mjs +1 -1
  6. package/dist/index102.mjs +3 -3
  7. package/dist/index103.mjs +3 -3
  8. package/dist/index111.mjs +1 -1
  9. package/dist/index20.mjs +2 -2
  10. package/dist/index21.mjs +1 -1
  11. package/dist/index28.mjs +11 -11
  12. package/dist/index29.mjs +1 -1
  13. package/dist/index3.mjs +83 -73
  14. package/dist/index30.mjs +1 -1
  15. package/dist/index31.mjs +1 -1
  16. package/dist/index32.mjs +1 -1
  17. package/dist/index37.mjs +1 -1
  18. package/dist/index41.mjs +36 -23
  19. package/dist/index42.mjs +44 -36
  20. package/dist/index43.mjs +99 -44
  21. package/dist/index44.mjs +112 -99
  22. package/dist/index45.mjs +44 -80
  23. package/dist/index46.mjs +64 -53
  24. package/dist/index47.mjs +66 -49
  25. package/dist/index48.mjs +54 -73
  26. package/dist/index49.mjs +52 -63
  27. package/dist/index50.mjs +14 -70
  28. package/dist/index51.mjs +13 -14
  29. package/dist/index52.mjs +58 -13
  30. package/dist/index53.mjs +101 -34
  31. package/dist/index54.mjs +99 -95
  32. package/dist/index55.mjs +22 -132
  33. package/dist/index58.mjs +2 -2
  34. package/dist/index59.mjs +4 -3
  35. package/dist/index60.mjs +4 -2
  36. package/dist/index61.mjs +2 -5
  37. package/dist/index62.mjs +30 -231
  38. package/dist/index63.mjs +42 -5
  39. package/dist/index64.mjs +228 -127
  40. package/dist/index65.mjs +4 -66
  41. package/dist/index66.mjs +124 -77
  42. package/dist/index67.mjs +65 -26
  43. package/dist/index68.mjs +84 -6
  44. package/dist/index69.mjs +26 -72
  45. package/dist/index70.mjs +8 -3
  46. package/dist/index71.mjs +75 -2
  47. package/dist/index72.mjs +3 -82
  48. package/dist/index73.mjs +2 -54
  49. package/dist/index74.mjs +82 -5
  50. package/dist/index75.mjs +53 -4
  51. package/dist/index76.mjs +5 -178
  52. package/dist/index77.mjs +5 -53
  53. package/dist/index78.mjs +178 -68
  54. package/dist/index79.mjs +50 -31
  55. package/dist/index80.mjs +69 -43
  56. package/dist/index81.mjs +1 -1
  57. package/dist/index82.mjs +1 -1
  58. package/dist/index83.mjs +5 -5
  59. package/dist/index85.mjs +2 -2
  60. package/dist/index87.mjs +2 -2
  61. package/dist/index89.mjs +1 -1
  62. package/dist/index91.mjs +4 -4
  63. package/dist/index92.mjs +3 -3
  64. package/dist/index93.mjs +1 -1
  65. package/dist/index94.mjs +3 -3
  66. package/dist/index99.mjs +1 -1
  67. package/dist/styles.css +1 -0
  68. package/package.json +14 -13
  69. package/src/components/CartItem.stories.tsx +94 -0
  70. package/src/components/CartItem.tsx +141 -0
  71. package/src/components/Checkout.stories.tsx +380 -0
  72. package/src/components/Checkout.tsx +954 -0
  73. package/src/components/DiscountCodeInput.stories.tsx +76 -0
  74. package/src/components/DiscountCodeInput.tsx +162 -0
  75. package/src/components/OrderConfirmation.stories.tsx +142 -0
  76. package/src/components/OrderConfirmation.tsx +301 -0
  77. package/src/components/ProductCard.stories.tsx +112 -0
  78. package/src/components/ProductCard.tsx +195 -0
  79. package/src/components/ProductGrid.stories.tsx +137 -0
  80. package/src/components/ProductGrid.tsx +141 -0
  81. package/src/components/ShoppingCart.stories.tsx +459 -0
  82. package/src/components/ShoppingCart.tsx +262 -0
  83. package/src/components/ui/badge.tsx +37 -0
  84. package/src/components/ui/button.tsx +71 -0
  85. package/src/components/ui/card.tsx +79 -0
  86. package/src/components/ui/form-input.tsx +78 -0
  87. package/src/components/ui/form-select.tsx +73 -0
  88. package/src/components/ui/modal.tsx +181 -0
  89. package/src/contexts/CartContext.tsx +305 -0
  90. package/src/hooks/usePaystackPayment.ts +137 -0
  91. package/src/index.ts +51 -0
  92. package/src/lib/utils.ts +45 -0
  93. package/src/paystack.svg +67 -0
  94. package/src/providers/StorefrontProvider.tsx +70 -0
  95. package/src/styles.css +1 -0
  96. package/src/test-utils/MockCartProvider.tsx +424 -0
  97. package/src/vite-env.d.ts +12 -0
@@ -0,0 +1,459 @@
1
+ /**
2
+ * Shopping Cart Storybook Stories
3
+ *
4
+ * Demonstrates the cart context API - open/close cart from anywhere using useCart().
5
+ */
6
+
7
+ import type { Meta, StoryObj } from '@storybook/react';
8
+ import { useCart } from '../contexts/CartContext';
9
+ import { MockCartProvider } from '../test-utils/MockCartProvider';
10
+ import { fn } from '@storybook/test';
11
+ import { Button } from './ui/button';
12
+ import { ShoppingCart as ShoppingCartIcon } from 'lucide-react';
13
+ import type { Cart } from '@oms/api-client';
14
+
15
+ // Demo component showing how to use the cart context
16
+ const CartDemo = ({ title, description }: { title?: string; description?: string }) => {
17
+ const { open, cart } = useCart();
18
+
19
+ return (
20
+ <div className="min-h-screen bg-gray-50 p-8">
21
+ <div className="max-w-4xl mx-auto">
22
+ <h1 className="text-3xl font-bold mb-4">
23
+ {title || 'Shopping Cart Context Demo'}
24
+ </h1>
25
+ <p className="text-gray-600 mb-6">
26
+ {description || 'Click the button below to open the cart. The cart state is managed by CartContext - you can call open() from anywhere in your app!'}
27
+ </p>
28
+ <div className="flex gap-4 items-center">
29
+ <Button onClick={open} size="lg">
30
+ <ShoppingCartIcon className="h-5 w-5" />
31
+ Open Cart ({cart?.items?.length || 0} items)
32
+ </Button>
33
+ <div className="text-sm text-gray-500">
34
+ Try this: <code className="bg-gray-200 px-2 py-1 rounded">const {'{ open }'} = useCart();</code>
35
+ </div>
36
+ </div>
37
+ </div>
38
+ </div>
39
+ );
40
+ };
41
+
42
+ // Mock cart data
43
+ const mockCart: Cart = {
44
+ id: 'mock-cart-123',
45
+ expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24).toISOString(),
46
+ brand: {
47
+ id: 'mock-brand-456',
48
+ name: 'Demo Store',
49
+ slug: 'demo-store',
50
+ logoUrl: 'https://images.unsplash.com/photo-1599305445671-ac291c95aaa9?w=200&h=80&fit=crop',
51
+ siteUrl: 'https://demo-store.com',
52
+ domain: 'f',
53
+ createdAt: new Date().toISOString(),
54
+ updatedAt: new Date().toISOString(),
55
+ deletedAt: null,
56
+ },
57
+ customerPhone: null,
58
+ customerEmail: null,
59
+ customerFirstName: null,
60
+ customerLastName: null,
61
+ availablePaymentMethods: ['cod'],
62
+ deliveryZone: null,
63
+ recoveryAttempts: 0,
64
+ lastRecoveryAttemptAt: null,
65
+ recoveryDiscountCode: null,
66
+ items: [
67
+ {
68
+ id: 'item-1',
69
+ variant: {
70
+ id: 'variant-1',
71
+ productId: 'product-1',
72
+ name: 'Medium - Blue',
73
+ price: 2999,
74
+ sku: 'TSHIRT-MD-BLUE',
75
+ thumbnailUrl: 'https://images.unsplash.com/photo-1521572163474-6864f9cf17ab?w=400',
76
+ trackInventory: true,
77
+ lowStockThreshold: 10,
78
+ isActive: true,
79
+ createdAt: new Date().toISOString(),
80
+ updatedAt: new Date().toISOString(),
81
+ deletedAt: null,
82
+ product: {
83
+ id: 'product-1',
84
+ name: 'Premium Cotton T-Shirt',
85
+ slug: 'premium-cotton-t-shirt',
86
+ brandId: 'mock-brand-456',
87
+ isActive: true,
88
+ description: 'Comfortable premium cotton t-shirt',
89
+ thumbnailUrl: 'https://images.unsplash.com/photo-1521572163474-6864f9cf17ab?w=400',
90
+ createdAt: new Date().toISOString(),
91
+ updatedAt: new Date().toISOString(),
92
+ deletedAt: null,
93
+ quantityDiscounts: null,
94
+ },
95
+ },
96
+ quantity: 2,
97
+ basePrice: 2999,
98
+ discountPercent: 0,
99
+ finalPrice: 2599,
100
+ subtotal: 5198,
101
+ },
102
+ {
103
+ id: 'item-2',
104
+ variant: {
105
+ id: 'variant-2',
106
+ productId: 'product-2',
107
+ name: 'Large - Black',
108
+ price: 3499,
109
+ sku: 'JEANS-LG-BLACK',
110
+ thumbnailUrl: 'https://images.unsplash.com/photo-1542272604-787c3835535d?w=400',
111
+ trackInventory: true,
112
+ lowStockThreshold: 10,
113
+ isActive: true,
114
+ createdAt: new Date().toISOString(),
115
+ updatedAt: new Date().toISOString(),
116
+ deletedAt: null,
117
+ product: {
118
+ id: 'product-2',
119
+ name: 'Slim Fit Jeans',
120
+ slug: 'slim-fit-jeans',
121
+ brandId: 'mock-brand-456',
122
+ isActive: true,
123
+ description: 'Modern slim fit jeans',
124
+ thumbnailUrl: 'https://images.unsplash.com/photo-1542272604-787c3835535d?w=400',
125
+ createdAt: new Date().toISOString(),
126
+ updatedAt: new Date().toISOString(),
127
+ deletedAt: null,
128
+ quantityDiscounts: null,
129
+ },
130
+ },
131
+ quantity: 1,
132
+ basePrice: 3499,
133
+ discountPercent: 0,
134
+ finalPrice: 3499,
135
+ subtotal: 3499,
136
+ },
137
+ ],
138
+ pricing: {
139
+ subtotal: 8697,
140
+ deliveryCharge: 0,
141
+ discount: null,
142
+ total: 8697,
143
+ },
144
+ createdAt: new Date().toISOString(),
145
+ updatedAt: new Date().toISOString(),
146
+ convertedToOrderId: null,
147
+ wasRecovered: false,
148
+ recoveryUrl: 'https://demo-store.com?cartId=mock-cart-123',
149
+ };
150
+
151
+ // Create a wrapper type for our stories
152
+ type CartProviderStory = StoryObj<{
153
+ cart: Partial<Cart>;
154
+ shoppingCartProps: any;
155
+ }>;
156
+
157
+ const meta: Meta<{
158
+ cart: Partial<Cart>;
159
+ shoppingCartProps: any;
160
+ }> = {
161
+ title: 'Components/ShoppingCart',
162
+ parameters: {
163
+ layout: 'fullscreen',
164
+ },
165
+ tags: ['autodocs'],
166
+ };
167
+
168
+ export default meta;
169
+
170
+ // Default shopping cart
171
+ export const Default: CartProviderStory = {
172
+ render: (args) => (
173
+ <MockCartProvider
174
+ cart={args.cart}
175
+ shoppingCartProps={{
176
+ onCheckout: fn(),
177
+ onContinueShopping: fn(),
178
+ ...args.shoppingCartProps,
179
+ }}
180
+ >
181
+ <CartDemo />
182
+ </MockCartProvider>
183
+ ),
184
+ args: {
185
+ cart: mockCart,
186
+ shoppingCartProps: {},
187
+ },
188
+ };
189
+
190
+ // Cart with discount code input
191
+ export const WithDiscountCode: CartProviderStory = {
192
+ render: (args) => (
193
+ <MockCartProvider
194
+ cart={args.cart}
195
+ shoppingCartProps={{
196
+ showDiscountCode: true,
197
+ onCheckout: fn(),
198
+ onContinueShopping: fn(),
199
+ ...args.shoppingCartProps,
200
+ }}
201
+ >
202
+ <CartDemo
203
+ title="Cart with Discount Code"
204
+ description="The cart includes a discount code input. Try entering 'SAVE10' to see it in action!"
205
+ />
206
+ </MockCartProvider>
207
+ ),
208
+ args: {
209
+ cart: mockCart,
210
+ shoppingCartProps: { showDiscountCode: true },
211
+ },
212
+ };
213
+
214
+ // Without discount code input
215
+ export const WithoutDiscountCode: CartProviderStory = {
216
+ render: (args) => (
217
+ <MockCartProvider
218
+ cart={args.cart}
219
+ shoppingCartProps={{
220
+ showDiscountCode: false,
221
+ onCheckout: fn(),
222
+ onContinueShopping: fn(),
223
+ ...args.shoppingCartProps,
224
+ }}
225
+ >
226
+ <CartDemo
227
+ title="Cart without Discount Code"
228
+ description="This cart hides the discount code input field."
229
+ />
230
+ </MockCartProvider>
231
+ ),
232
+ args: {
233
+ cart: mockCart,
234
+ shoppingCartProps: { showDiscountCode: false },
235
+ },
236
+ };
237
+
238
+ // Custom button text
239
+ export const CustomButtonText: CartProviderStory = {
240
+ render: (args) => (
241
+ <MockCartProvider
242
+ cart={args.cart}
243
+ shoppingCartProps={{
244
+ checkoutButtonText: 'Complete Purchase',
245
+ continueShoppingText: 'Keep Browsing',
246
+ onCheckout: fn(),
247
+ onContinueShopping: fn(),
248
+ ...args.shoppingCartProps,
249
+ }}
250
+ >
251
+ <CartDemo
252
+ title="Custom Button Text"
253
+ description="The checkout and continue shopping buttons have custom text."
254
+ />
255
+ </MockCartProvider>
256
+ ),
257
+ args: {
258
+ cart: mockCart,
259
+ shoppingCartProps: {
260
+ checkoutButtonText: 'Complete Purchase',
261
+ continueShoppingText: 'Keep Browsing',
262
+ },
263
+ },
264
+ };
265
+
266
+ // Checkout button disabled
267
+ export const CheckoutDisabled: CartProviderStory = {
268
+ render: (args) => (
269
+ <MockCartProvider
270
+ cart={args.cart}
271
+ shoppingCartProps={{
272
+ disableCheckout: true,
273
+ onCheckout: fn(),
274
+ onContinueShopping: fn(),
275
+ ...args.shoppingCartProps,
276
+ }}
277
+ >
278
+ <CartDemo
279
+ title="Checkout Disabled"
280
+ description="The checkout button is disabled (useful when items are out of stock or other validation fails)."
281
+ />
282
+ </MockCartProvider>
283
+ ),
284
+ args: {
285
+ cart: mockCart,
286
+ shoppingCartProps: { disableCheckout: true },
287
+ },
288
+ };
289
+
290
+ // Empty cart state
291
+ export const EmptyCart: CartProviderStory = {
292
+ render: (args) => (
293
+ <MockCartProvider
294
+ cart={args.cart}
295
+ shoppingCartProps={{
296
+ emptyMessage: 'Your shopping cart is empty. Start adding items!',
297
+ onCheckout: fn(),
298
+ onContinueShopping: fn(),
299
+ ...args.shoppingCartProps,
300
+ }}
301
+ >
302
+ <CartDemo
303
+ title="Empty Cart"
304
+ description="The cart has no items. See the empty state message."
305
+ />
306
+ </MockCartProvider>
307
+ ),
308
+ args: {
309
+ cart: {
310
+ ...mockCart,
311
+ items: [],
312
+ pricing: {
313
+ subtotal: 0,
314
+ discount: null,
315
+ deliveryCharge: 0,
316
+ total: 0,
317
+ },
318
+ },
319
+ shoppingCartProps: {
320
+ emptyMessage: 'Your shopping cart is empty. Start adding items!',
321
+ },
322
+ },
323
+ };
324
+
325
+ // Cart with discount applied
326
+ export const WithDiscount: CartProviderStory = {
327
+ render: (args) => (
328
+ <MockCartProvider
329
+ cart={args.cart}
330
+ shoppingCartProps={{
331
+ showDiscountCode: true,
332
+ onCheckout: fn(),
333
+ onContinueShopping: fn(),
334
+ ...args.shoppingCartProps,
335
+ }}
336
+ >
337
+ <CartDemo
338
+ title="Cart with Discount Applied"
339
+ description="A 10% discount has been applied to this cart."
340
+ />
341
+ </MockCartProvider>
342
+ ),
343
+ args: {
344
+ cart: {
345
+ ...mockCart,
346
+ pricing: {
347
+ subtotal: 8697,
348
+ discount: {
349
+ code: 'SAVE10',
350
+ type: 'percentage',
351
+ value: 10,
352
+ amount: 870,
353
+ description: '10% off your order',
354
+ },
355
+ deliveryCharge: 0,
356
+ total: 7827,
357
+ },
358
+ },
359
+ shoppingCartProps: { showDiscountCode: true },
360
+ },
361
+ };
362
+
363
+ // Integrated checkout
364
+ export const WithIntegratedCheckout: CartProviderStory = {
365
+ render: (args) => (
366
+ <MockCartProvider
367
+ cart={args.cart}
368
+ shoppingCartProps={{
369
+ onOrderSuccess: fn(),
370
+ onCheckoutError: fn(),
371
+ ...args.shoppingCartProps,
372
+ }}
373
+ >
374
+ <CartDemo
375
+ title="Integrated Checkout"
376
+ description="This cart includes the checkout flow. Click 'Checkout' to see the checkout modal."
377
+ />
378
+ </MockCartProvider>
379
+ ),
380
+ args: {
381
+ cart: mockCart,
382
+ shoppingCartProps: {
383
+ onOrderSuccess: fn(),
384
+ onCheckoutError: fn(),
385
+ },
386
+ },
387
+ };
388
+
389
+ // Multiple open buttons example
390
+ const MultipleButtonsDemo = () => {
391
+ const { open, cart } = useCart();
392
+
393
+ return (
394
+ <div className="min-h-screen bg-gray-50 p-8">
395
+ <div className="max-w-4xl mx-auto space-y-8">
396
+ <div>
397
+ <h1 className="text-3xl font-bold mb-4">
398
+ Open Cart from Anywhere
399
+ </h1>
400
+ <p className="text-gray-600 mb-6">
401
+ Since the cart is managed by context, you can open it from any component in your app!
402
+ </p>
403
+ </div>
404
+
405
+ <div className="bg-white p-6 rounded-lg shadow">
406
+ <h2 className="text-xl font-semibold mb-4">Header Component</h2>
407
+ <Button onClick={open} variant="outline">
408
+ <ShoppingCartIcon className="h-4 w-4" />
409
+ Cart ({cart?.items?.length || 0})
410
+ </Button>
411
+ </div>
412
+
413
+ <div className="bg-white p-6 rounded-lg shadow">
414
+ <h2 className="text-xl font-semibold mb-4">Product Card</h2>
415
+ <p className="text-gray-600 mb-4">After adding to cart, you might want to show the cart:</p>
416
+ <Button onClick={open} size="lg">
417
+ <ShoppingCartIcon className="h-5 w-5" />
418
+ View Cart
419
+ </Button>
420
+ </div>
421
+
422
+ <div className="bg-white p-6 rounded-lg shadow">
423
+ <h2 className="text-xl font-semibold mb-4">Floating Action Button</h2>
424
+ <div className="fixed bottom-8 right-8">
425
+ <Button onClick={open} size="lg" className="rounded-full shadow-lg">
426
+ <ShoppingCartIcon className="h-6 w-6" />
427
+ </Button>
428
+ </div>
429
+ </div>
430
+
431
+ <div className="bg-blue-50 border border-blue-200 p-4 rounded-lg">
432
+ <p className="text-sm text-blue-800">
433
+ <strong>💡 Tip:</strong> All these buttons use the same <code className="bg-blue-100 px-1 rounded">open()</code> function from <code className="bg-blue-100 px-1 rounded">useCart()</code>.
434
+ No prop drilling needed!
435
+ </p>
436
+ </div>
437
+ </div>
438
+ </div>
439
+ );
440
+ };
441
+
442
+ export const OpenFromAnywhere: CartProviderStory = {
443
+ render: (args) => (
444
+ <MockCartProvider
445
+ cart={args.cart}
446
+ shoppingCartProps={{
447
+ onCheckout: fn(),
448
+ onContinueShopping: fn(),
449
+ ...args.shoppingCartProps,
450
+ }}
451
+ >
452
+ <MultipleButtonsDemo />
453
+ </MockCartProvider>
454
+ ),
455
+ args: {
456
+ cart: mockCart,
457
+ shoppingCartProps: {},
458
+ },
459
+ };