hey-pharmacist-ecommerce 1.1.29 → 1.1.31

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 (104) hide show
  1. package/dist/index.d.mts +10957 -1331
  2. package/dist/index.d.ts +10957 -1331
  3. package/dist/index.js +12364 -5144
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +9353 -2205
  6. package/dist/index.mjs.map +1 -1
  7. package/package.json +4 -3
  8. package/src/components/AccountReviewsTab.tsx +97 -0
  9. package/src/components/CouponCodeInput.tsx +190 -0
  10. package/src/components/Header.tsx +5 -1
  11. package/src/components/Notification.tsx +1 -1
  12. package/src/components/NotificationBell.tsx +33 -0
  13. package/src/components/NotificationCard.tsx +211 -0
  14. package/src/components/NotificationDrawer.tsx +195 -0
  15. package/src/components/OrderCard.tsx +164 -99
  16. package/src/components/ProductReviewsSection.tsx +30 -0
  17. package/src/components/RatingDistribution.tsx +86 -0
  18. package/src/components/ReviewCard.tsx +59 -0
  19. package/src/components/ReviewForm.tsx +207 -0
  20. package/src/components/ReviewPromptBanner.tsx +98 -0
  21. package/src/components/ReviewsList.tsx +151 -0
  22. package/src/components/StarRating.tsx +98 -0
  23. package/src/hooks/useDiscounts.ts +7 -0
  24. package/src/hooks/useOrders.ts +15 -0
  25. package/src/hooks/useReviews.ts +230 -0
  26. package/src/hooks/useStoreCapabilities.ts +87 -0
  27. package/src/index.ts +29 -0
  28. package/src/lib/Apis/apis/auth-api.ts +19 -7
  29. package/src/lib/Apis/apis/categories-api.ts +97 -0
  30. package/src/lib/Apis/apis/discounts-api.ts +23 -72
  31. package/src/lib/Apis/apis/notifications-api.ts +196 -231
  32. package/src/lib/Apis/apis/products-api.ts +181 -0
  33. package/src/lib/Apis/apis/review-api.ts +283 -4
  34. package/src/lib/Apis/apis/shipping-api.ts +105 -0
  35. package/src/lib/Apis/apis/stores-api.ts +536 -0
  36. package/src/lib/Apis/apis/sub-categories-api.ts +97 -0
  37. package/src/lib/Apis/apis/users-api.ts +8 -8
  38. package/src/lib/Apis/models/address-created-request.ts +0 -12
  39. package/src/lib/Apis/models/address.ts +0 -12
  40. package/src/lib/Apis/models/api-key-info-dto.ts +49 -0
  41. package/src/lib/Apis/models/bulk-channel-toggle-dto.ts +52 -0
  42. package/src/lib/Apis/models/cart-body-populated.ts +3 -3
  43. package/src/lib/Apis/models/channel-settings-dto.ts +39 -0
  44. package/src/lib/Apis/models/{discount-paginated-response.ts → completed-order-dto.ts} +21 -16
  45. package/src/lib/Apis/models/create-address-dto.ts +0 -12
  46. package/src/lib/Apis/models/create-discount-dto.ts +31 -100
  47. package/src/lib/Apis/models/create-review-dto.ts +4 -4
  48. package/src/lib/Apis/models/create-shippo-account-dto.ts +45 -0
  49. package/src/lib/Apis/models/create-store-address-dto.ts +0 -12
  50. package/src/lib/Apis/models/create-store-dto-settings.ts +51 -0
  51. package/src/lib/Apis/models/create-store-dto.ts +13 -0
  52. package/src/lib/Apis/models/create-variant-dto.ts +0 -6
  53. package/src/lib/Apis/models/discount.ts +37 -106
  54. package/src/lib/Apis/models/discounts-insights-dto.ts +12 -0
  55. package/src/lib/Apis/models/index.ts +24 -7
  56. package/src/lib/Apis/models/{manual-discount.ts → manual-discount-dto.ts} +10 -10
  57. package/src/lib/Apis/models/manual-order-dto.ts +3 -3
  58. package/src/lib/Apis/models/populated-discount.ts +41 -109
  59. package/src/lib/Apis/models/preference-update-item.ts +59 -0
  60. package/src/lib/Apis/models/product-light-dto.ts +40 -0
  61. package/src/lib/Apis/models/product-variant.ts +0 -6
  62. package/src/lib/Apis/models/reorder-categories-dto.ts +27 -0
  63. package/src/lib/Apis/models/reorder-products-dto.ts +49 -0
  64. package/src/lib/Apis/models/{check-notifications-response-dto.ts → reorder-products-success-response-dto.ts} +7 -7
  65. package/src/lib/Apis/models/reorder-subcategories-dto.ts +33 -0
  66. package/src/lib/Apis/models/reorder-success-response-dto.ts +33 -0
  67. package/src/lib/Apis/models/review-status-dto.ts +34 -0
  68. package/src/lib/Apis/models/review.ts +9 -3
  69. package/src/lib/Apis/models/reviewable-order-dto.ts +58 -0
  70. package/src/lib/Apis/models/reviewable-product-dto.ts +81 -0
  71. package/src/lib/Apis/models/shipment-with-order.ts +18 -0
  72. package/src/lib/Apis/models/shipment.ts +18 -0
  73. package/src/lib/Apis/models/shippo-account-response-dto.ts +51 -0
  74. package/src/lib/Apis/models/store-api-keys-response-dto.ts +34 -0
  75. package/src/lib/Apis/models/store-capabilities-dto.ts +63 -0
  76. package/src/lib/Apis/models/store-entity.ts +13 -0
  77. package/src/lib/Apis/models/store.ts +13 -0
  78. package/src/lib/Apis/models/update-address-dto.ts +0 -12
  79. package/src/lib/Apis/models/update-api-keys-dto.ts +39 -0
  80. package/src/lib/Apis/models/update-discount-dto.ts +31 -100
  81. package/src/lib/Apis/models/update-manual-shipment-status-dto.ts +47 -0
  82. package/src/lib/Apis/models/update-notification-settings-dto.ts +28 -0
  83. package/src/lib/Apis/models/update-review-dto.ts +4 -4
  84. package/src/lib/Apis/models/update-store-dto.ts +13 -0
  85. package/src/lib/Apis/models/update-variant-dto.ts +0 -6
  86. package/src/lib/Apis/models/{pick-type-class.ts → variant-light-dto.ts} +20 -14
  87. package/src/lib/utils/discount.ts +155 -0
  88. package/src/lib/validations/discount.ts +11 -0
  89. package/src/providers/CartProvider.tsx +2 -2
  90. package/src/providers/DiscountProvider.tsx +97 -0
  91. package/src/providers/EcommerceProvider.tsx +13 -5
  92. package/src/providers/NotificationCenterProvider.tsx +436 -0
  93. package/src/screens/CartScreen.tsx +1 -1
  94. package/src/screens/CheckoutScreen.tsx +402 -290
  95. package/src/screens/NotificationSettingsScreen.tsx +413 -0
  96. package/src/screens/OrderDetailScreen.tsx +283 -0
  97. package/src/screens/OrderReviewsScreen.tsx +308 -0
  98. package/src/screens/OrdersScreen.tsx +31 -7
  99. package/src/screens/ProductDetailScreen.tsx +24 -11
  100. package/src/screens/ProfileScreen.tsx +5 -0
  101. package/src/screens/ResetPasswordScreen.tsx +10 -4
  102. package/src/lib/Apis/models/create-notification-dto.ts +0 -75
  103. package/src/lib/Apis/models/notification.ts +0 -93
  104. package/src/lib/Apis/models/single-notification-dto.ts +0 -99
@@ -0,0 +1,413 @@
1
+ 'use client';
2
+
3
+ import React, { useState, useEffect } from 'react';
4
+ import { ChevronDown, ChevronRight, Loader2, Bell, Shield, CreditCard, Package, Settings, Zap } from 'lucide-react';
5
+ import { useNotificationCenter } from '@/providers/NotificationCenterProvider';
6
+ import { NotificationType, PreferenceUpdateItemTypeEnum } from '@/providers/NotificationCenterProvider';
7
+ import { motion, AnimatePresence } from 'framer-motion';
8
+ import { Button } from '@/components/ui/Button';
9
+
10
+ interface NotificationCategory {
11
+ name: string;
12
+ icon: React.ElementType;
13
+ types: Array<{
14
+ type: NotificationType;
15
+ label: string;
16
+ description: string;
17
+ isComingSoon?: boolean;
18
+ }>;
19
+ }
20
+
21
+ const NOTIFICATION_CATEGORIES: NotificationCategory[] = [
22
+ {
23
+ name: 'Order Updates',
24
+ icon: Package,
25
+ types: [
26
+ {
27
+ type: PreferenceUpdateItemTypeEnum.ORDERCONFIRMATION,
28
+ label: 'Order Confirmed',
29
+ description: 'Get notified when your order is confirmed',
30
+ },
31
+ {
32
+ type: PreferenceUpdateItemTypeEnum.ORDERSHIPPED,
33
+ label: 'Order Shipped',
34
+ description: 'Track your order when it ships',
35
+ },
36
+ {
37
+ type: PreferenceUpdateItemTypeEnum.ORDERDELIVERED,
38
+ label: 'Order Delivered',
39
+ description: 'Know when your order arrives',
40
+ },
41
+ ],
42
+ },
43
+ {
44
+ name: 'Payments',
45
+ icon: CreditCard,
46
+ types: [
47
+ {
48
+ type: PreferenceUpdateItemTypeEnum.PAYMENTFAILED,
49
+ label: 'Payment Failed',
50
+ description: 'Alert when a payment fails',
51
+ },
52
+ {
53
+ type: PreferenceUpdateItemTypeEnum.REFUNDPROCESSED,
54
+ label: 'Refund Processed',
55
+ description: 'Confirmation when refunds are issued',
56
+ },
57
+ ],
58
+ },
59
+ {
60
+ name: 'Security',
61
+ icon: Shield,
62
+ types: [
63
+ {
64
+ type: PreferenceUpdateItemTypeEnum.PASSWORDRESET,
65
+ label: 'Password Reset',
66
+ description: 'Security alerts for password changes',
67
+ },
68
+ {
69
+ type: PreferenceUpdateItemTypeEnum.NEWDEVICELOGIN,
70
+ label: 'New Device Login',
71
+ description: 'Alert when logging in from a new device',
72
+ isComingSoon: true,
73
+ },
74
+ {
75
+ type: PreferenceUpdateItemTypeEnum.TWOFACODE,
76
+ label: 'Two-Factor Authentication',
77
+ description: 'Receive 2FA codes',
78
+ isComingSoon: true,
79
+ },
80
+ ],
81
+ },
82
+ {
83
+ name: 'Marketing',
84
+ icon: Bell,
85
+ types: [
86
+ {
87
+ type: PreferenceUpdateItemTypeEnum.ABANDONEDCARTREMINDER,
88
+ label: 'Abandoned Cart',
89
+ description: 'Reminders for items left in cart',
90
+ isComingSoon: true,
91
+ },
92
+ {
93
+ type: PreferenceUpdateItemTypeEnum.PRICEDROPALERT,
94
+ label: 'Price Drop Alert',
95
+ description: 'Notify when prices drop on saved items',
96
+ isComingSoon: true,
97
+ },
98
+ {
99
+ type: PreferenceUpdateItemTypeEnum.BACKINSTOCK,
100
+ label: 'Back in Stock',
101
+ description: 'Alert when out-of-stock items return',
102
+ isComingSoon: true,
103
+ },
104
+ ],
105
+ },
106
+ ];
107
+
108
+ export function NotificationSettingsScreen() {
109
+ const { settings, updateSettings, isLoading } = useNotificationCenter();
110
+ const [localSettings, setLocalSettings] = useState(settings);
111
+ const [expandedCategories, setExpandedCategories] = useState<Set<string>>(
112
+ new Set(NOTIFICATION_CATEGORIES.map(cat => cat.name))
113
+ );
114
+ const [isSaving, setIsSaving] = useState(false);
115
+ const [hasChanges, setHasChanges] = useState(false);
116
+
117
+ useEffect(() => {
118
+ console.log('NotificationSettingsScreen: settings updated:', settings);
119
+ if (settings) {
120
+ setLocalSettings(settings);
121
+ }
122
+ }, [settings]);
123
+
124
+ const toggleCategory = (categoryName: string) => {
125
+ setExpandedCategories(prev => {
126
+ const next = new Set(prev);
127
+ if (next.has(categoryName)) {
128
+ next.delete(categoryName);
129
+ } else {
130
+ next.add(categoryName);
131
+ }
132
+ return next;
133
+ });
134
+ };
135
+
136
+ const getChannelSetting = (type: NotificationType, channel: 'email' | 'push' | 'inApp'): boolean => {
137
+ const activeSettings = hasChanges ? localSettings : settings;
138
+ if (!activeSettings?.preferences || !Array.isArray(activeSettings.preferences)) {
139
+ return false;
140
+ }
141
+
142
+ const normalize = (s: any) => s?.toString().replace(/[^a-zA-Z0-9]/g, '').toUpperCase() || '';
143
+ const targetType = normalize(type);
144
+
145
+ const pref = activeSettings.preferences.find(p => normalize(p.type) === targetType);
146
+
147
+ if (!pref) return false;
148
+
149
+ // Handle potential boolean-like values from backend safely
150
+ const val = (pref.settings as any)?.[channel];
151
+ return val === true || val === 'true' || val === 1 || val === '1';
152
+ };
153
+
154
+ const updateChannelSetting = (type: NotificationType, channel: 'email' | 'push' | 'inApp', value: boolean) => {
155
+ setLocalSettings(prev => {
156
+ // If we're starting local changes, base them on the current provider settings
157
+ const currentSettings = prev || settings || { preferences: [] };
158
+ const currentPrefs = Array.isArray(currentSettings.preferences) ? currentSettings.preferences : [];
159
+ const preferences = [...currentPrefs];
160
+
161
+ const normalize = (s: any) => s?.toString().replace(/[^a-zA-Z0-9]/g, '').toUpperCase() || '';
162
+ const targetType = normalize(type);
163
+
164
+ const existingIndex = preferences.findIndex(p => normalize(p.type) === targetType);
165
+
166
+ if (existingIndex >= 0) {
167
+ preferences[existingIndex] = {
168
+ ...preferences[existingIndex],
169
+ settings: {
170
+ ...preferences[existingIndex].settings,
171
+ [channel]: value,
172
+ },
173
+ };
174
+ } else {
175
+ preferences.push({
176
+ type,
177
+ settings: {
178
+ email: channel === 'email' ? value : true,
179
+ push: channel === 'push' ? value : true,
180
+ inApp: channel === 'inApp' ? value : true,
181
+ },
182
+ });
183
+ }
184
+
185
+ return { ...currentSettings, preferences };
186
+ });
187
+
188
+ setHasChanges(true);
189
+ };
190
+
191
+ const handleSave = async () => {
192
+ if (!localSettings) return;
193
+
194
+ setIsSaving(true);
195
+ try {
196
+ await updateSettings(localSettings);
197
+ setHasChanges(false);
198
+ } catch (error) {
199
+ console.error('Failed to save settings:', error);
200
+ alert('Failed to save settings. Please try again.');
201
+ } finally {
202
+ setIsSaving(false);
203
+ }
204
+ };
205
+
206
+ const disableAllChannel = (channel: 'email' | 'push') => {
207
+ setLocalSettings(prev => {
208
+ const currentSettings = prev || { preferences: [] };
209
+ const prefIsArray = Array.isArray(currentSettings.preferences);
210
+ const preferences = NOTIFICATION_CATEGORIES.flatMap(cat =>
211
+ cat.types.filter(t => !t.isComingSoon).map(t => t.type)
212
+ ).map(type => {
213
+ const existing = prefIsArray ? currentSettings.preferences.find(p => p.type === type) : undefined;
214
+ return {
215
+ type,
216
+ settings: {
217
+ email: channel === 'email' ? false : (existing?.settings?.email ?? true),
218
+ push: channel === 'push' ? false : (existing?.settings?.push ?? true),
219
+ inApp: existing?.settings?.inApp ?? true,
220
+ },
221
+ };
222
+ });
223
+
224
+ return { ...currentSettings, preferences };
225
+ });
226
+
227
+ setHasChanges(true);
228
+ };
229
+
230
+ if (isLoading && !settings) {
231
+ return (
232
+ <div className="flex flex-col items-center justify-center min-h-[400px] bg-linear-to-b from-[#F8FAFC] to-[#EBF4FB]">
233
+ <Loader2 className="w-10 h-10 text-primary animate-spin mb-4" />
234
+ <p className="text-muted font-medium">Loading preferences...</p>
235
+ </div>
236
+ );
237
+ }
238
+
239
+ return (
240
+ <div className="min-h-screen bg-linear-to-b from-[#F8FAFC] to-[#EBF4FB]">
241
+ {/* Header Area */}
242
+ <div className="container mx-auto px-4 py-8">
243
+ <div className="max-w-4xl mx-auto">
244
+ <h1 className="text-2xl font-semibold text-secondary">Notification Preferences</h1>
245
+ <p className="text-sm text-muted">
246
+ Manage how you receive notifications for different events
247
+ </p>
248
+ </div>
249
+ </div>
250
+
251
+ <div className="max-w-4xl mx-auto px-4 pb-32">
252
+
253
+
254
+ {/* Categories */}
255
+ <div className="space-y-4 mb-8">
256
+ {NOTIFICATION_CATEGORIES.map((category) => (
257
+ <div key={category.name} className="bg-white rounded-2xl border border-slate-200 shadow-xs overflow-hidden">
258
+ {/* Category Header */}
259
+ <button
260
+ onClick={() => toggleCategory(category.name)}
261
+ className="w-full flex items-center justify-between p-6 hover:bg-slate-50 transition-colors"
262
+ >
263
+ <div className="flex items-center gap-3">
264
+ <div className="p-2 bg-slate-100 rounded-lg group-hover:bg-white transition-colors">
265
+ <category.icon className="w-5 h-5 text-secondary" />
266
+ </div>
267
+ <h2 className="text-lg font-semibold text-secondary">{category.name}</h2>
268
+ </div>
269
+ {expandedCategories.has(category.name) ? (
270
+ <ChevronDown className="w-5 h-5 text-muted" />
271
+ ) : (
272
+ <ChevronRight className="w-5 h-5 text-muted" />
273
+ )}
274
+ </button>
275
+
276
+ <AnimatePresence>
277
+ {expandedCategories.has(category.name) && (
278
+ <motion.div
279
+ initial={{ height: 0, opacity: 0 }}
280
+ animate={{ height: 'auto', opacity: 1 }}
281
+ exit={{ height: 0, opacity: 0 }}
282
+ transition={{ duration: 0.2 }}
283
+ className="border-t border-slate-100"
284
+ >
285
+ <div className="p-6 space-y-4">
286
+ {category.types.map((notifType) => (
287
+ <div
288
+ key={notifType.type}
289
+ className={`rounded-xl border border-slate-200 bg-slate-50 p-4 transition-all hover:bg-white hover:shadow-sm ${notifType.isComingSoon ? 'opacity-50 grayscale select-none' : ''}`}
290
+ >
291
+ <div className="flex flex-col sm:flex-row sm:items-center justify-between gap-6">
292
+ <div className="flex-1">
293
+ <div className="flex items-center gap-2">
294
+ <h3 className="font-semibold text-secondary">{notifType.label}</h3>
295
+ {notifType.isComingSoon && (
296
+ <span className="text-[10px] font-bold uppercase tracking-wider bg-slate-200 text-muted px-2 py-0.5 rounded-full">
297
+ Soon
298
+ </span>
299
+ )}
300
+ </div>
301
+ <p className="text-xs text-muted mt-1 leading-relaxed">{notifType.description}</p>
302
+ </div>
303
+
304
+ {!notifType.isComingSoon && (
305
+ <div className="flex items-center gap-6 sm:border-l sm:border-slate-200 sm:pl-6">
306
+ {/* Email */}
307
+ <label className="flex items-center gap-2 cursor-pointer group/label">
308
+ <input
309
+ type="checkbox"
310
+ checked={getChannelSetting(notifType.type, 'email')}
311
+ onChange={(e) =>
312
+ updateChannelSetting(notifType.type, 'email', e.target.checked)
313
+ }
314
+ className="w-4 h-4 text-primary border-slate-300 rounded-sm focus:ring-primary focus:ring-offset-0"
315
+ />
316
+ <span className="text-[10px] font-bold text-muted uppercase tracking-wider group-hover/label:text-primary transition-colors">Email</span>
317
+ </label>
318
+
319
+ {/* Push */}
320
+ <label className="flex items-center gap-2 cursor-pointer group/label">
321
+ <input
322
+ type="checkbox"
323
+ checked={getChannelSetting(notifType.type, 'push')}
324
+ onChange={(e) =>
325
+ updateChannelSetting(notifType.type, 'push', e.target.checked)
326
+ }
327
+ className="w-4 h-4 text-primary border-slate-300 rounded-sm focus:ring-primary focus:ring-offset-0"
328
+ />
329
+ <span className="text-[10px] font-bold text-muted uppercase tracking-wider group-hover/label:text-primary transition-colors">Push</span>
330
+ </label>
331
+
332
+ {/* In-App */}
333
+ <label className="flex items-center gap-2 cursor-pointer group/label">
334
+ <input
335
+ type="checkbox"
336
+ checked={getChannelSetting(notifType.type, 'inApp')}
337
+ onChange={(e) =>
338
+ updateChannelSetting(notifType.type, 'inApp', e.target.checked)
339
+ }
340
+ className="w-4 h-4 text-primary border-slate-300 rounded-sm focus:ring-primary focus:ring-offset-0"
341
+ />
342
+ <span className="text-[10px] font-bold text-muted uppercase tracking-wider group-hover/label:text-primary transition-colors">In-App</span>
343
+ </label>
344
+ </div>
345
+ )}
346
+ </div>
347
+ </div>
348
+ ))}
349
+ </div>
350
+ </motion.div>
351
+ )}
352
+ </AnimatePresence>
353
+ </div>
354
+ ))}
355
+ </div>
356
+
357
+ {/* Quick Actions */}
358
+ <div className="bg-white rounded-2xl p-6 border border-slate-200 mb-8 shadow-xs">
359
+ <div className="flex items-center gap-2 mb-4">
360
+ <Zap className="h-5 w-5 text-accent" />
361
+ <h3 className="text-lg font-semibold text-secondary">Quick Actions</h3>
362
+ </div>
363
+ <div className="flex flex-wrap gap-3">
364
+ <Button
365
+ variant="outline-solid"
366
+ size="sm"
367
+ onClick={() => disableAllChannel('email')}
368
+ className="rounded-xl border-slate-200 hover:bg-slate-50"
369
+ >
370
+ Disable All Emails
371
+ </Button>
372
+ <Button
373
+ variant="outline-solid"
374
+ size="sm"
375
+ onClick={() => disableAllChannel('push')}
376
+ className="rounded-xl border-slate-200 hover:bg-slate-50"
377
+ >
378
+ Disable All Push
379
+ </Button>
380
+ </div>
381
+ </div>
382
+
383
+ {/* Save Button */}
384
+ <AnimatePresence>
385
+ {hasChanges && (
386
+ <motion.div
387
+ initial={{ opacity: 0, y: 100 }}
388
+ animate={{ opacity: 1, y: 0 }}
389
+ exit={{ opacity: 0, y: 100 }}
390
+ className="fixed bottom-10 left-0 right-0 flex justify-center z-50 pointer-events-none"
391
+ >
392
+ <div className="pointer-events-auto px-6 py-4 bg-secondary shadow-lg rounded-3xl flex items-center gap-8 max-w-xl mx-auto transform hover:scale-[1.02] transition-all duration-300 ease-out border border-white/10">
393
+ <div className="hidden sm:block">
394
+ <p className="text-sm font-bold text-white leading-tight tracking-wide italic">Changes Pending</p>
395
+ <p className="text-[11px] text-white/60 font-medium">Click to synchronize with account</p>
396
+ </div>
397
+ <Button
398
+ onClick={handleSave}
399
+ isLoading={isSaving}
400
+ disabled={isSaving}
401
+ variant="secondary"
402
+ className="px-8 py-3 bg-accent border-none text-white rounded-2xl font-bold text-xs uppercase tracking-widest hover:bg-accent-dark transition-all active:scale-95 shadow-lg shadow-accent/25 min-w-[180px]"
403
+ >
404
+ {isSaving ? 'Saving...' : 'Sync Changes'}
405
+ </Button>
406
+ </div>
407
+ </motion.div>
408
+ )}
409
+ </AnimatePresence>
410
+ </div>
411
+ </div>
412
+ );
413
+ }