hey-pharmacist-ecommerce 1.1.30 → 1.1.32

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 (83) hide show
  1. package/dist/index.d.mts +1451 -1303
  2. package/dist/index.d.ts +1451 -1303
  3. package/dist/index.js +6162 -1563
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +5854 -1271
  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/AccountSettingsTab.tsx +0 -50
  10. package/src/components/CouponCodeInput.tsx +190 -0
  11. package/src/components/Header.tsx +5 -1
  12. package/src/components/Notification.tsx +1 -1
  13. package/src/components/NotificationBell.tsx +33 -0
  14. package/src/components/NotificationCard.tsx +211 -0
  15. package/src/components/NotificationDrawer.tsx +188 -0
  16. package/src/components/OrderCard.tsx +164 -99
  17. package/src/components/ProductCard.tsx +3 -3
  18. package/src/components/ProductReviewsSection.tsx +30 -0
  19. package/src/components/RatingDistribution.tsx +86 -0
  20. package/src/components/ReviewCard.tsx +59 -0
  21. package/src/components/ReviewForm.tsx +207 -0
  22. package/src/components/ReviewPromptBanner.tsx +98 -0
  23. package/src/components/ReviewsList.tsx +151 -0
  24. package/src/components/StarRating.tsx +98 -0
  25. package/src/components/TabNavigation.tsx +1 -1
  26. package/src/components/ui/Button.tsx +1 -1
  27. package/src/hooks/useDiscounts.ts +7 -0
  28. package/src/hooks/useOrders.ts +15 -0
  29. package/src/hooks/useReviews.ts +230 -0
  30. package/src/index.ts +25 -0
  31. package/src/lib/Apis/apis/discounts-api.ts +23 -72
  32. package/src/lib/Apis/apis/notifications-api.ts +196 -231
  33. package/src/lib/Apis/apis/products-api.ts +84 -0
  34. package/src/lib/Apis/apis/review-api.ts +283 -4
  35. package/src/lib/Apis/apis/stores-api.ts +180 -0
  36. package/src/lib/Apis/models/bulk-channel-toggle-dto.ts +52 -0
  37. package/src/lib/Apis/models/cart-body-populated.ts +3 -3
  38. package/src/lib/Apis/models/channel-settings-dto.ts +39 -0
  39. package/src/lib/Apis/models/{discount-paginated-response.ts → completed-order-dto.ts} +21 -16
  40. package/src/lib/Apis/models/create-discount-dto.ts +31 -92
  41. package/src/lib/Apis/models/create-review-dto.ts +4 -4
  42. package/src/lib/Apis/models/create-shippo-account-dto.ts +45 -0
  43. package/src/lib/Apis/models/create-store-dto.ts +6 -0
  44. package/src/lib/Apis/models/discount.ts +37 -98
  45. package/src/lib/Apis/models/discounts-insights-dto.ts +12 -0
  46. package/src/lib/Apis/models/index.ts +13 -7
  47. package/src/lib/Apis/models/{manual-discount.ts → manual-discount-dto.ts} +10 -10
  48. package/src/lib/Apis/models/manual-order-dto.ts +3 -3
  49. package/src/lib/Apis/models/populated-discount.ts +41 -101
  50. package/src/lib/Apis/models/preference-update-item.ts +59 -0
  51. package/src/lib/Apis/models/product-light-dto.ts +40 -0
  52. package/src/lib/Apis/models/{check-notifications-response-dto.ts → review-status-dto.ts} +8 -7
  53. package/src/lib/Apis/models/review.ts +9 -3
  54. package/src/lib/Apis/models/reviewable-order-dto.ts +58 -0
  55. package/src/lib/Apis/models/reviewable-product-dto.ts +81 -0
  56. package/src/lib/Apis/models/shippo-account-response-dto.ts +51 -0
  57. package/src/lib/Apis/models/store-entity.ts +6 -0
  58. package/src/lib/Apis/models/store.ts +6 -0
  59. package/src/lib/Apis/models/update-discount-dto.ts +31 -92
  60. package/src/lib/Apis/models/update-notification-settings-dto.ts +28 -0
  61. package/src/lib/Apis/models/update-review-dto.ts +4 -4
  62. package/src/lib/Apis/models/update-store-dto.ts +6 -0
  63. package/src/lib/Apis/models/{pick-type-class.ts → variant-light-dto.ts} +20 -14
  64. package/src/lib/utils/discount.ts +155 -0
  65. package/src/lib/validations/discount.ts +11 -0
  66. package/src/providers/CartProvider.tsx +2 -2
  67. package/src/providers/DiscountProvider.tsx +97 -0
  68. package/src/providers/EcommerceProvider.tsx +13 -5
  69. package/src/providers/NotificationCenterProvider.tsx +420 -0
  70. package/src/screens/CartScreen.tsx +1 -1
  71. package/src/screens/CheckoutScreen.tsx +39 -12
  72. package/src/screens/NotificationSettingsScreen.tsx +321 -0
  73. package/src/screens/OrderDetailScreen.tsx +283 -0
  74. package/src/screens/OrderReviewsScreen.tsx +308 -0
  75. package/src/screens/OrdersScreen.tsx +31 -7
  76. package/src/screens/ProductDetailScreen.tsx +24 -11
  77. package/src/screens/ProfileScreen.tsx +5 -0
  78. package/src/styles/globals.css +4 -0
  79. package/styles/base.css +6 -0
  80. package/styles/globals.css +3 -0
  81. package/src/lib/Apis/models/create-notification-dto.ts +0 -75
  82. package/src/lib/Apis/models/notification.ts +0 -93
  83. package/src/lib/Apis/models/single-notification-dto.ts +0 -99
@@ -0,0 +1,321 @@
1
+ 'use client';
2
+
3
+ import React, { useState, useEffect } from 'react';
4
+ import { Loader2 } 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
+ types: Array<{
13
+ type: NotificationType;
14
+ label: string;
15
+ description: string;
16
+ isComingSoon?: boolean;
17
+ }>;
18
+ }
19
+
20
+ const NOTIFICATION_CATEGORIES: NotificationCategory[] = [
21
+ {
22
+ name: 'Order Updates',
23
+ types: [
24
+ {
25
+ type: PreferenceUpdateItemTypeEnum.ORDERCONFIRMATION,
26
+ label: 'Order Confirmed',
27
+ description: 'Get notified when your order is confirmed',
28
+ },
29
+ {
30
+ type: PreferenceUpdateItemTypeEnum.ORDERSHIPPED,
31
+ label: 'Order Shipped',
32
+ description: 'Track your order when it ships',
33
+ },
34
+ {
35
+ type: PreferenceUpdateItemTypeEnum.ORDERDELIVERED,
36
+ label: 'Order Delivered',
37
+ description: 'Know when your order arrives',
38
+ },
39
+ ],
40
+ },
41
+ {
42
+ name: 'Payments',
43
+ types: [
44
+ {
45
+ type: PreferenceUpdateItemTypeEnum.PAYMENTFAILED,
46
+ label: 'Payment Failed',
47
+ description: 'Alert when a payment fails',
48
+ },
49
+ {
50
+ type: PreferenceUpdateItemTypeEnum.REFUNDPROCESSED,
51
+ label: 'Refund Processed',
52
+ description: 'Confirmation when refunds are issued',
53
+ },
54
+ ],
55
+ },
56
+ {
57
+ name: 'Security',
58
+ types: [
59
+ {
60
+ type: PreferenceUpdateItemTypeEnum.PASSWORDRESET,
61
+ label: 'Password Reset',
62
+ description: 'Security alerts for password changes',
63
+ },
64
+ {
65
+ type: PreferenceUpdateItemTypeEnum.NEWDEVICELOGIN,
66
+ label: 'New Device Login',
67
+ description: 'Alert when logging in from a new device',
68
+ isComingSoon: true,
69
+ },
70
+ {
71
+ type: PreferenceUpdateItemTypeEnum.TWOFACODE,
72
+ label: 'Two-Factor Authentication',
73
+ description: 'Receive 2FA codes',
74
+ isComingSoon: true,
75
+ },
76
+ ],
77
+ },
78
+ {
79
+ name: 'Marketing',
80
+ types: [
81
+ {
82
+ type: PreferenceUpdateItemTypeEnum.ABANDONEDCARTREMINDER,
83
+ label: 'Abandoned Cart',
84
+ description: 'Reminders for items left in cart',
85
+ isComingSoon: true,
86
+ },
87
+ {
88
+ type: PreferenceUpdateItemTypeEnum.PRICEDROPALERT,
89
+ label: 'Price Drop Alert',
90
+ description: 'Notify when prices drop on saved items',
91
+ isComingSoon: true,
92
+ },
93
+ {
94
+ type: PreferenceUpdateItemTypeEnum.BACKINSTOCK,
95
+ label: 'Back in Stock',
96
+ description: 'Alert when out-of-stock items return',
97
+ isComingSoon: true,
98
+ },
99
+ ],
100
+ },
101
+ ];
102
+
103
+ // Custom Minimal Switch Component
104
+ const Switch = ({ checked, onChange, disabled }: { checked: boolean; onChange: (checked: boolean) => void; disabled?: boolean }) => (
105
+ <button
106
+ onClick={() => !disabled && onChange(!checked)}
107
+ className={`relative w-9 h-5 rounded-full transition-colors duration-200 ease-in-out focus:outline-none ${checked ? 'bg-black' : 'bg-slate-200'
108
+ } ${disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'}`}
109
+ >
110
+ <span
111
+ className={`absolute top-0.5 left-0.5 w-4 h-4 rounded-full bg-white shadow-sm transition-transform duration-200 ease-in-out ${checked ? 'translate-x-4' : 'translate-x-0'
112
+ }`}
113
+ />
114
+ </button>
115
+ );
116
+
117
+ export function NotificationSettingsScreen() {
118
+ const { settings, updateSettings, isLoading } = useNotificationCenter();
119
+ const [localSettings, setLocalSettings] = useState(settings);
120
+ const [isSaving, setIsSaving] = useState(false);
121
+ const [hasChanges, setHasChanges] = useState(false);
122
+
123
+ useEffect(() => {
124
+ if (settings) {
125
+ setLocalSettings(settings);
126
+ }
127
+ }, [settings]);
128
+
129
+ const getChannelSetting = (type: NotificationType, channel: 'email' | 'push' | 'inApp'): boolean => {
130
+ const activeSettings = hasChanges ? localSettings : settings;
131
+ if (!activeSettings?.preferences || !Array.isArray(activeSettings.preferences)) {
132
+ return false;
133
+ }
134
+
135
+ const normalize = (s: any) => s?.toString().replace(/[^a-zA-Z0-9]/g, '').toUpperCase() || '';
136
+ const targetType = normalize(type);
137
+
138
+ const pref = activeSettings.preferences.find(p => normalize(p.type) === targetType);
139
+
140
+ if (!pref) return false;
141
+
142
+ const val = (pref.settings as any)?.[channel];
143
+ return val === true || val === 'true' || val === 1 || val === '1';
144
+ };
145
+
146
+ const updateChannelSetting = (type: NotificationType, channel: 'email' | 'push' | 'inApp', value: boolean) => {
147
+ setLocalSettings(prev => {
148
+ const currentSettings = prev || settings || { preferences: [] };
149
+ const currentPrefs = Array.isArray(currentSettings.preferences) ? currentSettings.preferences : [];
150
+ const preferences = [...currentPrefs];
151
+
152
+ const normalize = (s: any) => s?.toString().replace(/[^a-zA-Z0-9]/g, '').toUpperCase() || '';
153
+ const targetType = normalize(type);
154
+
155
+ const existingIndex = preferences.findIndex(p => normalize(p.type) === targetType);
156
+
157
+ if (existingIndex >= 0) {
158
+ preferences[existingIndex] = {
159
+ ...preferences[existingIndex],
160
+ settings: {
161
+ ...preferences[existingIndex].settings,
162
+ [channel]: value,
163
+ },
164
+ };
165
+ } else {
166
+ preferences.push({
167
+ type,
168
+ settings: {
169
+ email: channel === 'email' ? value : true,
170
+ push: channel === 'push' ? value : true,
171
+ inApp: channel === 'inApp' ? value : true,
172
+ },
173
+ });
174
+ }
175
+
176
+ return { ...currentSettings, preferences };
177
+ });
178
+
179
+ setHasChanges(true);
180
+ };
181
+
182
+ const handleSave = async () => {
183
+ if (!localSettings) return;
184
+
185
+ setIsSaving(true);
186
+ try {
187
+ await updateSettings(localSettings);
188
+ setHasChanges(false);
189
+ } catch (error) {
190
+ console.error('Failed to save settings:', error);
191
+ alert('Failed to save settings. Please try again.');
192
+ } finally {
193
+ setIsSaving(false);
194
+ }
195
+ };
196
+
197
+ if (isLoading && !settings) {
198
+ return (
199
+ <div className="flex h-[50vh] flex-col items-center justify-center">
200
+ <Loader2 className="h-6 w-6 animate-spin text-slate-400" />
201
+ </div>
202
+ );
203
+ }
204
+
205
+ return (
206
+ <div className="mx-auto max-w-4xl px-6 py-8">
207
+
208
+ {/* Header */}
209
+ <div className="mb-6 flex items-end justify-between border-b border-slate-100 pb-4">
210
+ <div>
211
+ <h1 className="text-2xl font-semibold tracking-tight text-slate-900">Notifications</h1>
212
+ <p className="mt-2 text-sm text-slate-500">
213
+ Manage your notification preferences across all channels.
214
+ </p>
215
+ </div>
216
+ </div>
217
+
218
+ {/* Notification Table Header */}
219
+ <div className="hidden grid-cols-12 gap-4 border-b border-slate-100 pb-3 text-xs font-medium uppercase tracking-wider text-slate-400 sm:grid">
220
+ <div className="col-span-8">Topic</div>
221
+ <div className="col-span-2 text-center">Email</div>
222
+ <div className="col-span-2 text-center">Push</div>
223
+ {/* <div className="col-span-2 text-center">In-App</div> */}
224
+ </div>
225
+
226
+ {/* Content */}
227
+ <div className="space-y-8 py-4">
228
+ {NOTIFICATION_CATEGORIES.map((category) => (
229
+ <section key={category.name}>
230
+ <h2 className="mb-6 text-sm font-semibold text-slate-900">{category.name}</h2>
231
+
232
+ <div className="space-y-4">
233
+ {category.types.map((notifType) => (
234
+ <div key={notifType.type} className="group grid grid-cols-1 sm:grid-cols-12 gap-y-3 sm:gap-x-4 items-start sm:items-center">
235
+ {/* Info */}
236
+ <div className="col-span-8 pr-4">
237
+ <div className="flex items-center gap-2">
238
+ <span className={`text-sm font-medium ${notifType.isComingSoon ? 'text-slate-400' : 'text-slate-700'}`}>
239
+ {notifType.label}
240
+ </span>
241
+ {notifType.isComingSoon && (
242
+ <span className="rounded bg-slate-100 px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wider text-slate-500">
243
+ Soon
244
+ </span>
245
+ )}
246
+ </div>
247
+ <p className="mt-0.5 text-xs text-slate-500">{notifType.description}</p>
248
+ </div>
249
+
250
+ {/* Toggles */}
251
+ <div className="col-span-4 grid grid-cols-2 gap-4">
252
+ <div className="flex items-center justify-between sm:justify-center">
253
+ <span className="sm:hidden text-xs text-slate-400">Email</span>
254
+ <Switch
255
+ checked={getChannelSetting(notifType.type, 'email')}
256
+ onChange={(checked) => updateChannelSetting(notifType.type, 'email', checked)}
257
+ disabled={notifType.isComingSoon}
258
+ />
259
+ </div>
260
+ <div className="flex items-center justify-between sm:justify-center">
261
+ <span className="sm:hidden text-xs text-slate-400">Push</span>
262
+ <Switch
263
+ checked={getChannelSetting(notifType.type, 'push')}
264
+ onChange={(checked) => updateChannelSetting(notifType.type, 'push', checked)}
265
+ disabled={notifType.isComingSoon}
266
+ />
267
+ </div>
268
+ {/*
269
+ <div className="flex items-center justify-between sm:justify-center col-span-1">
270
+ <span className="sm:hidden text-xs text-slate-400">In-App</span>
271
+ <Switch
272
+ checked={getChannelSetting(notifType.type, 'inApp')}
273
+ onChange={(checked) => updateChannelSetting(notifType.type, 'inApp', checked)}
274
+ disabled={notifType.isComingSoon}
275
+ />
276
+ </div>
277
+ */}
278
+ </div>
279
+ </div>
280
+ ))}
281
+ </div>
282
+ </section>
283
+ ))}
284
+ </div>
285
+
286
+ <div className="mt-12 border-t border-slate-100 pt-8 text-center sm:text-left">
287
+ <Button
288
+ variant="ghost"
289
+ className="text-xs text-slate-400 hover:text-red-600 px-0"
290
+ onClick={() => {
291
+ if (confirm('Are you sure you want to disable all notifications?')) {
292
+ // Iterate and disable all settings (reuse logic from before or keep it simple)
293
+ // For brevity in this redesign, keeping it out or implementing simple logic:
294
+ const newPrefs = localSettings?.preferences ? [...localSettings.preferences] : [];
295
+ // Logic to clear all would go here or call a batch update
296
+ alert("Feature to disable all coming in next update");
297
+ }
298
+ }}
299
+ >
300
+ Disable all notifications
301
+ </Button>
302
+ </div>
303
+
304
+ {hasChanges && (
305
+ <div className="bg-white/80 backdrop-blur-sm fixed bottom-6 left-1/2 -translate-x-1/2 z-50 rounded-xl shadow-lg border border-slate-200 p-2 px-4 flex items-center gap-4 animate-in slide-in-from-bottom-2 fade-in duration-300">
306
+ <span className="text-xs font-medium text-slate-600">Unsaved changes</span>
307
+ <div className="h-4 w-px bg-slate-200" />
308
+ <Button
309
+ onClick={handleSave}
310
+ isLoading={isSaving}
311
+ disabled={isSaving}
312
+ variant="primary"
313
+ className="h-8 rounded-lg px-4 text-xs font-medium bg-black text-white bg-slate-800 hover:bg-slate-700"
314
+ >
315
+ Save
316
+ </Button>
317
+ </div>
318
+ )}
319
+ </div>
320
+ );
321
+ }
@@ -0,0 +1,283 @@
1
+ 'use client';
2
+
3
+ import React from 'react';
4
+ import { motion } from 'framer-motion';
5
+ import {
6
+ ChevronLeft,
7
+ Package,
8
+ Truck,
9
+ MapPin,
10
+ CreditCard,
11
+ Calendar,
12
+ ExternalLink,
13
+ Printer,
14
+ CheckCircle2,
15
+ Clock,
16
+ AlertCircle,
17
+ Info
18
+ } from 'lucide-react';
19
+ import { useRouter } from 'next/navigation';
20
+ import { useOrder } from '@/hooks/useOrders';
21
+ import { useBasePath } from '@/providers/BasePathProvider';
22
+ import { formatPrice, formatDate } from '@/lib/utils/format';
23
+ import { Badge } from '@/components/ui/Badge';
24
+ import { Button } from '@/components/ui/Button';
25
+ import Image from 'next/image';
26
+
27
+ interface OrderDetailScreenProps {
28
+ id: string;
29
+ }
30
+
31
+ export function OrderDetailScreen({ id }: OrderDetailScreenProps) {
32
+ const router = useRouter();
33
+ const { buildPath } = useBasePath();
34
+ const { order, isLoading, error } = useOrder(id);
35
+
36
+ if (isLoading) {
37
+ return (
38
+ <div className="min-h-screen bg-slate-50 flex flex-col items-center justify-center p-4">
39
+ <div className="w-16 h-16 border-4 border-primary/20 border-t-primary rounded-full animate-spin mb-4" />
40
+ <p className="text-muted font-medium animate-pulse">Retrieving order details...</p>
41
+ </div>
42
+ );
43
+ }
44
+
45
+ if (error || !order) {
46
+ return (
47
+ <div className="min-h-screen bg-slate-50 flex flex-col items-center justify-center p-4">
48
+ <div className="p-4 bg-red-50 rounded-full mb-4">
49
+ <AlertCircle className="w-10 h-10 text-red-500" />
50
+ </div>
51
+ <h1 className="text-xl font-bold text-secondary mb-2">Order Not Found</h1>
52
+ <p className="text-muted mb-6">We couldn't find the order you're looking for.</p>
53
+ <Button onClick={() => router.push(buildPath('/account'))}>
54
+ Back to Account
55
+ </Button>
56
+ </div>
57
+ );
58
+ }
59
+
60
+ const items = order.items || [];
61
+ const status = order.orderStatus || 'Pending';
62
+ const isDelivery = order.orderType === 'Delivery';
63
+
64
+ const getStatusVariant = (status: string): 'success' | 'warning' | 'primary' | 'danger' | 'gray' => {
65
+ switch (status.toLowerCase()) {
66
+ case 'pending': return 'warning';
67
+ case 'delivered':
68
+ case 'fulfilled':
69
+ case 'picked up': return 'success';
70
+ case 'shipped': return 'primary';
71
+ case 'cancelled': return 'danger';
72
+ default: return 'gray';
73
+ }
74
+ };
75
+
76
+ const shippingAddress = order.shippingInfo?.addressTo;
77
+ const pickupAddress = order.pickUpAddress;
78
+ const activeAddress = isDelivery ? shippingAddress : pickupAddress;
79
+
80
+ return (
81
+ <div className="min-h-screen bg-linear-to-b from-[#F8FAFC] to-[#EBF4FB] pb-20">
82
+ {/* Header / Navigation */}
83
+ <div className="container mx-auto px-4 pt-8 max-w-6xl">
84
+ <button
85
+ onClick={() => router.back()}
86
+ className="group flex items-center gap-2 text-muted hover:text-secondary transition-colors mb-6"
87
+ >
88
+ <div className="p-1.5 rounded-full bg-white shadow-xs group-hover:shadow-md transition-all">
89
+ <ChevronLeft className="w-4 h-4" />
90
+ </div>
91
+ <span className="text-sm font-semibold">Back to History</span>
92
+ </button>
93
+
94
+ <div className="flex flex-col md:flex-row md:items-end justify-between gap-6 mb-8">
95
+ <div>
96
+ <div className="flex items-center gap-3 mb-2">
97
+ <h1 className="text-3xl font-bold text-secondary">Order Details</h1>
98
+ <Badge variant={getStatusVariant(status)}>{status}</Badge>
99
+ <Badge variant="primary" className="bg-primary-100 text-primary-700 border-primary-200">
100
+ {order.orderType || 'Pickup'}
101
+ </Badge>
102
+ </div>
103
+ <div className="flex flex-wrap items-center gap-x-6 gap-y-2 text-sm text-muted">
104
+ <span className="flex items-center gap-1.5 font-medium">
105
+ <span className="opacity-60 text-xs uppercase tracking-widest font-bold">ID:</span>
106
+ <span className="text-secondary font-mono tracking-tight">#{id.toUpperCase()}</span>
107
+ </span>
108
+ <span className="flex items-center gap-1.5 font-medium">
109
+ <Calendar className="w-4 h-4 opacity-60" />
110
+ {formatDate(order.createdAt || new Date(), 'long')}
111
+ </span>
112
+ </div>
113
+ </div>
114
+ <div className="flex gap-3">
115
+ {order.payment?.hostedInvoiceUrl && (
116
+ <Button size="sm" onClick={() => window.open(order.payment?.hostedInvoiceUrl, '_blank')} className="bg-accent hover:bg-accent-dark border-none">
117
+ <ExternalLink className="w-4 h-4" />
118
+ Payment Details
119
+ </Button>
120
+ )}
121
+ </div>
122
+ </div>
123
+ </div>
124
+
125
+ <div className="container mx-auto px-4 max-w-6xl grid grid-cols-1 lg:grid-cols-3 gap-8">
126
+ {/* Left Column: Order Tracking & Items */}
127
+ <div className="lg:col-span-2 space-y-6">
128
+ {/* Items Section */}
129
+ <motion.div
130
+ initial={{ opacity: 0, y: 20 }}
131
+ animate={{ opacity: 1, y: 0 }}
132
+ className="bg-white rounded-3xl border border-slate-200 shadow-xs overflow-hidden"
133
+ >
134
+ <div className="p-6 border-b border-slate-100 bg-slate-50/50 flex items-center justify-between">
135
+ <div className="flex items-center gap-2">
136
+ <Package className="w-5 h-5 text-secondary" />
137
+ <h2 className="font-bold text-secondary">Order Items</h2>
138
+ </div>
139
+ <span className="text-xs font-bold bg-slate-200 text-muted px-2.5 py-1 rounded-full">
140
+ {items.length} {items.length === 1 ? 'Product' : 'Products'}
141
+ </span>
142
+ </div>
143
+ <div className="divide-y divide-slate-100">
144
+ {items.map((item, idx) => (
145
+ <div key={item._id || idx} className="p-6 flex gap-6 group hover:bg-slate-50/50 transition-colors">
146
+ <div className="relative w-20 h-20 bg-slate-100 rounded-2xl overflow-hidden shrink-0 border border-slate-100 group-hover:scale-105 transition-transform duration-300">
147
+ <Image
148
+ src={item.productVariantData?.media?.[0]?.file || '/placeholder-product.jpg'}
149
+ alt={item.productVariantData?.name || 'Item'}
150
+ fill
151
+ className="object-cover"
152
+ sizes="80px"
153
+ />
154
+ </div>
155
+ <div className="flex-1 flex flex-col justify-between py-1">
156
+ <div>
157
+ <h3 className="font-bold text-secondary text-lg group-hover:text-primary transition-colors leading-snug mb-1">
158
+ {item.productVariantData?.name}
159
+ </h3>
160
+ <div className="flex flex-wrap gap-x-4 gap-y-1 text-sm text-muted">
161
+ <span className="flex items-center gap-1.5">
162
+ <span className="w-1.5 h-1.5 rounded-full bg-slate-300" />
163
+ Quantity: <span className="text-secondary font-bold">{item.quantity}</span>
164
+ </span>
165
+ </div>
166
+ </div>
167
+ <div className="flex items-center justify-between">
168
+ <span className="text-muted text-sm">{formatPrice(item.productVariantData?.finalPrice || 0)} per unit</span>
169
+ <span className="font-black text-secondary">{formatPrice((item.productVariantData?.finalPrice || 0) * item.quantity)}</span>
170
+ </div>
171
+ </div>
172
+ </div>
173
+ ))}
174
+ </div>
175
+ </motion.div>
176
+
177
+ {/* Order Meta / Info */}
178
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
179
+ <div className="bg-white p-6 rounded-3xl border border-slate-200 shadow-xs">
180
+ <div className="flex items-center gap-2 mb-4">
181
+ <MapPin className="w-5 h-5 text-accent" />
182
+ <h3 className="font-bold text-secondary">{isDelivery ? 'Shipping Address' : 'Pickup Location'}</h3>
183
+ </div>
184
+ {activeAddress ? (
185
+ <div className="text-sm text-muted leading-relaxed">
186
+ <p className="font-bold text-secondary text-base mb-1">
187
+ {activeAddress.name}
188
+ </p>
189
+ <p>{activeAddress.street1}</p>
190
+ {activeAddress.street2 && <p>{activeAddress.street2}</p>}
191
+ <p>{activeAddress.city}, {activeAddress.state} {activeAddress.zip}</p>
192
+ <p>{activeAddress.country}</p>
193
+ </div>
194
+ ) : (
195
+ <p className="text-sm text-muted italic">No address recorded</p>
196
+ )}
197
+ </div>
198
+ <div className="bg-white p-6 rounded-3xl border border-slate-200 shadow-xs">
199
+ <div className="flex items-center gap-2 mb-4">
200
+ <CreditCard className="w-5 h-5 text-accent" />
201
+ <h3 className="font-bold text-secondary">Payment Method</h3>
202
+ </div>
203
+ <div className="text-sm text-muted leading-relaxed">
204
+ <p className="font-bold text-secondary text-base mb-1">
205
+ {order.payment?.paymentMethod ? order.payment.paymentMethod.replace('_', ' ').toUpperCase() : 'N/A'}
206
+ </p>
207
+ <p className="flex items-center gap-2 mt-2">
208
+ <span className="opacity-60">Status:</span>
209
+ <Badge variant={order.payment?.paymentStatus === 'Paid' ? 'success' : 'warning'}>
210
+ {order.payment?.paymentStatus || 'Processing'}
211
+ </Badge>
212
+ </p>
213
+ {order.payment?.transactionId && (
214
+ <p className="mt-3 text-[10px] font-bold text-slate-400 uppercase tracking-tighter">
215
+ Ref: {order.payment.transactionId}
216
+ </p>
217
+ )}
218
+ </div>
219
+ </div>
220
+ </div>
221
+ </div>
222
+
223
+ {/* Right Column: Order Summary */}
224
+ <div className="space-y-6">
225
+ <motion.div
226
+ initial={{ opacity: 0, x: 20 }}
227
+ animate={{ opacity: 1, x: 0 }}
228
+ className="bg-secondary p-8 rounded-[2rem] text-white shadow-xl shadow-secondary/20 sticky top-8"
229
+ >
230
+ <h2 className="text-xl font-bold mb-6 flex items-center gap-2">
231
+ Summary View
232
+ </h2>
233
+
234
+ <div className="space-y-4 mb-8">
235
+ <div className="flex justify-between items-center text-white/70">
236
+ <span className="text-sm font-medium">Subtotal</span>
237
+ <span className="font-bold text-white">{formatPrice(order.subTotal || 0)}</span>
238
+ </div>
239
+ <div className="flex justify-between items-center text-white/70">
240
+ <span className="text-sm font-medium">Shipping</span>
241
+ <span className="font-bold text-white">{formatPrice(order.shippingCost || 0)}</span>
242
+ </div>
243
+ <div className="flex justify-between items-center text-white/70">
244
+ <span className="text-sm font-medium">Tax</span>
245
+ <span className="font-bold text-white">{formatPrice(order.tax || 0)}</span>
246
+ </div>
247
+ {order.discountedAmount !== undefined && order.discountedAmount > 0 && (
248
+ <div className="flex justify-between items-center text-primary">
249
+ <span className="text-sm font-medium">Discount</span>
250
+ <span className="font-bold">-{formatPrice(order.discountedAmount)}</span>
251
+ </div>
252
+ )}
253
+
254
+ <div className="pt-4 border-t border-white/10 mt-4">
255
+ <div className="flex justify-between items-end">
256
+ <div>
257
+ <p className="text-xs font-black uppercase tracking-[0.2em] text-white/40 mb-1">Grand Total</p>
258
+ <p className="text-3xl font-black">{formatPrice(order.grandTotal || 0)}</p>
259
+ </div>
260
+ <div className="px-3 py-1 bg-white/10 rounded-lg text-[10px] font-black uppercase tracking-wider text-white/60">
261
+ USD
262
+ </div>
263
+ </div>
264
+ </div>
265
+ </div>
266
+
267
+ {order.orderStatus === 'Pending' && (
268
+ <div className="p-4 bg-white/5 rounded-2xl border border-white/10 mb-8">
269
+ <div className="flex items-start gap-3">
270
+ <Info className="w-5 h-5 text-primary shrink-0 mt-0.5" />
271
+ <div>
272
+ <p className="text-xs font-bold mb-1">Order is processing</p>
273
+ <p className="text-[11px] text-white/60 leading-relaxed">We've received your request and our pharmacists are reviewing it for safety and accuracy.</p>
274
+ </div>
275
+ </div>
276
+ </div>
277
+ )}
278
+ </motion.div>
279
+ </div>
280
+ </div>
281
+ </div>
282
+ );
283
+ }