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,308 @@
1
+ 'use client';
2
+
3
+ import React, { useState } from 'react';
4
+ import { useRouter } from 'next/navigation';
5
+ import { PopulatedOrder } from '@/lib/Apis/models';
6
+ import { ReviewForm } from '@/components/ReviewForm';
7
+ import { ArrowLeft, Package } from 'lucide-react';
8
+ import { formatDistanceToNow } from 'date-fns';
9
+ import Image from 'next/image';
10
+ import { useCurrentOrders } from '@/hooks/useOrders';
11
+ import { useAuth } from '@/providers/AuthProvider';
12
+
13
+ interface SelectedProduct {
14
+ productId: string;
15
+ productName: string;
16
+ productVariantId: string;
17
+ variantName: string;
18
+ variantImage?: string;
19
+ }
20
+
21
+ export function OrderReviewsScreen() {
22
+ const router = useRouter();
23
+ const { isAuthenticated } = useAuth();
24
+ const { orders, isLoading, error, refetch } = useCurrentOrders();
25
+ const [selectedOrder, setSelectedOrder] = useState<PopulatedOrder | null>(null);
26
+ const [selectedProduct, setSelectedProduct] = useState<SelectedProduct | null>(null);
27
+
28
+ // If not authenticated, redirect to login
29
+ React.useEffect(() => {
30
+ if (!isLoading && !isAuthenticated) {
31
+ router.push('/login');
32
+ }
33
+ }, [isAuthenticated, isLoading, router]);
34
+
35
+ const handleOrderClick = (order: PopulatedOrder) => {
36
+ console.log('Selected order:', order.id || order._id, 'Status:', order.orderStatus);
37
+ console.log('Full order data:', JSON.stringify(order, null, 2));
38
+ setSelectedOrder(order);
39
+ };
40
+
41
+ const handleProductClick = (product: SelectedProduct) => {
42
+ console.log('Selected product:', product.productId, 'Variant:', product.productVariantId);
43
+ setSelectedProduct(product);
44
+ };
45
+
46
+ const handleReviewSuccess = () => {
47
+ console.log('Review submitted successfully!');
48
+ setSelectedProduct(null);
49
+ setSelectedOrder(null);
50
+ refetch();
51
+ };
52
+
53
+ if (isLoading) {
54
+ return (
55
+ <div className="container mx-auto px-4 py-8">
56
+ <div className="max-w-4xl mx-auto">
57
+ <div className="animate-pulse space-y-4">
58
+ <div className="h-8 bg-gray-200 rounded w-1/3" />
59
+ <div className="space-y-3">
60
+ {[1, 2, 3].map((i) => (
61
+ <div key={i} className="h-24 bg-gray-200 rounded" />
62
+ ))}
63
+ </div>
64
+ </div>
65
+ </div>
66
+ </div>
67
+ );
68
+ }
69
+
70
+ if (error) {
71
+ return (
72
+ <div className="container mx-auto px-4 py-8">
73
+ <div className="max-w-4xl mx-auto">
74
+ <div className="text-center py-12">
75
+ <p className="text-red-600 mb-4">{error.message}</p>
76
+ <button
77
+ onClick={() => router.back()}
78
+ className="text-[#E67E50] hover:underline"
79
+ >
80
+ Go Back
81
+ </button>
82
+ </div>
83
+ </div>
84
+ </div>
85
+ );
86
+ }
87
+
88
+ // Show review form if a product is selected
89
+ if (selectedProduct && selectedOrder) {
90
+ return (
91
+ <div className="container mx-auto px-4 py-8">
92
+ <div className="max-w-2xl mx-auto">
93
+ <button
94
+ onClick={() => setSelectedProduct(null)}
95
+ className="flex items-center gap-2 text-gray-600 hover:text-gray-900 mb-6"
96
+ >
97
+ <ArrowLeft className="size-5" />
98
+ Back to Products
99
+ </button>
100
+
101
+ <div className="bg-white rounded-lg border border-gray-200 p-6 mb-6">
102
+ <div className="flex items-center gap-4 mb-6">
103
+ {selectedProduct.variantImage && (
104
+ <div className="relative w-20 h-20 rounded-lg overflow-hidden bg-gray-100">
105
+ <Image
106
+ src={selectedProduct.variantImage}
107
+ alt={selectedProduct.productName}
108
+ fill
109
+ className="object-cover"
110
+ />
111
+ </div>
112
+ )}
113
+ <div>
114
+ <h3 className="font-semibold text-lg text-gray-900">
115
+ {selectedProduct.productName}
116
+ </h3>
117
+ <p className="text-sm text-gray-600">{selectedProduct.variantName}</p>
118
+ </div>
119
+ </div>
120
+
121
+ <ReviewForm
122
+ productId={selectedProduct.productId}
123
+ productVariantId={selectedProduct.productVariantId}
124
+ orderId={(() => {
125
+ const resolvedOrderId = selectedOrder.id || selectedOrder._id || '';
126
+ console.log('Passing orderId to ReviewForm:', resolvedOrderId);
127
+ console.log('Order userId:', selectedOrder.userId);
128
+ return resolvedOrderId;
129
+ })()}
130
+ onSuccess={handleReviewSuccess}
131
+ onCancel={() => setSelectedProduct(null)}
132
+ />
133
+ </div>
134
+ </div>
135
+ </div>
136
+ );
137
+ }
138
+
139
+ // Show order products if an order is selected
140
+ if (selectedOrder) {
141
+ return (
142
+ <div className="container mx-auto px-4 py-8">
143
+ <div className="max-w-4xl mx-auto">
144
+ <button
145
+ onClick={() => setSelectedOrder(null)}
146
+ className="flex items-center gap-2 text-gray-600 hover:text-gray-900 mb-6"
147
+ >
148
+ <ArrowLeft className="size-5" />
149
+ Back to Orders
150
+ </button>
151
+
152
+ <div className="bg-white rounded-lg border border-gray-200 p-6 mb-6">
153
+ <div className="flex items-center justify-between mb-4">
154
+ <div>
155
+ <h2 className="text-xl font-semibold text-gray-900">
156
+ Order #{(selectedOrder.id || selectedOrder._id || '').slice(-8)}
157
+ </h2>
158
+ <p className="text-sm text-gray-600">Status: {selectedOrder.orderStatus}</p>
159
+ </div>
160
+ </div>
161
+ </div>
162
+
163
+ <div className="space-y-4">
164
+ {selectedOrder.items?.map((item: any, index: number) => {
165
+ // Debug: Log the full item structure
166
+ console.log('Order item structure:', JSON.stringify(item, null, 2));
167
+
168
+ // CartItemPopulated structure:
169
+ // - productVariantId: string (the ID we need)
170
+ // - productVariantData: ProductVariant object
171
+ // - quantity: number
172
+ const variantData = item.productVariantData || item.variant;
173
+ const variantId = item.productVariantId || item._id || item.id;
174
+
175
+ // Extract images from media array (ProductVariant uses 'media' not 'images')
176
+ const variantImages = variantData?.media?.map((m: any) => m.url || m.src || m) || variantData?.images || [];
177
+ const firstImage = variantImages[0];
178
+
179
+ return (
180
+ <div
181
+ key={`${variantId}-${index}`}
182
+ className="bg-white rounded-lg border border-gray-200 p-4 cursor-pointer hover:border-[#E67E50] hover:shadow-md transition-all"
183
+ onClick={() => {
184
+ // Try to extract productId from various possible locations
185
+ const possibleProductId =
186
+ item.productId ||
187
+ item.product?._id ||
188
+ item.product?.id ||
189
+ variantData?.productId ||
190
+ variantData?.product?._id ||
191
+ variantData?.product?.id ||
192
+ variantId; // Fallback to variantId if no productId found
193
+
194
+ const productData = {
195
+ productId: possibleProductId,
196
+ productName: variantData?.name || item.product?.name || 'Product',
197
+ productVariantId: variantId,
198
+ variantName: variantData?.name || 'Default',
199
+ variantImage: firstImage,
200
+ };
201
+ console.log('Extracted product data:', productData);
202
+ handleProductClick(productData);
203
+ }}
204
+ >
205
+ <div className="flex items-center gap-4">
206
+ {firstImage && (
207
+ <div className="relative w-16 h-16 rounded-lg overflow-hidden bg-gray-100 flex-shrink-0">
208
+ <Image
209
+ src={firstImage}
210
+ alt={variantData?.name || 'Product'}
211
+ fill
212
+ className="object-cover"
213
+ />
214
+ </div>
215
+ )}
216
+ <div className="flex-1">
217
+ <h3 className="font-medium text-gray-900">{variantData?.name || 'Product'}</h3>
218
+ <p className="text-sm text-gray-600">{variantData?.description || ''}</p>
219
+ <p className="text-xs text-gray-500 mt-1">Quantity: {item.quantity}</p>
220
+ </div>
221
+ <div className="flex items-center gap-2 text-[#E67E50]">
222
+ <span className="text-sm font-medium">Review Now</span>
223
+ </div>
224
+ </div>
225
+ </div>
226
+ );
227
+ })}
228
+ </div>
229
+ </div>
230
+ </div>
231
+ );
232
+ }
233
+
234
+ // Show completed orders list
235
+ const completedOrders = orders.filter(order =>
236
+ order.orderStatus && ['Delivered', 'Picked Up', 'Fulfilled'].includes(order.orderStatus)
237
+ );
238
+
239
+ return (
240
+ <div className="container mx-auto px-4 py-8">
241
+ <div className="max-w-4xl mx-auto">
242
+ <div className="mb-8">
243
+ <h1 className="text-3xl font-bold text-gray-900 mb-2">Review Your Orders</h1>
244
+ <p className="text-gray-600">
245
+ Share your experience with products you've purchased
246
+ </p>
247
+ <p className="text-xs text-gray-500 mt-2">
248
+ You can only review orders that have been delivered or completed. Reviews help other customers make informed decisions!
249
+ </p>
250
+ </div>
251
+
252
+ {completedOrders.length === 0 ? (
253
+ <div className="text-center py-12 bg-white rounded-lg border border-gray-200">
254
+ <Package className="size-16 text-gray-300 mx-auto mb-4" />
255
+ <h3 className="text-lg font-semibold text-gray-900 mb-2">
256
+ No completed orders
257
+ </h3>
258
+ <p className="text-gray-600 mb-6">
259
+ Complete an order to leave a review
260
+ </p>
261
+ <button
262
+ onClick={() => router.push('/shop')}
263
+ className="px-6 py-3 bg-[#E67E50] text-white rounded-lg hover:bg-[#d66f40] transition-colors"
264
+ >
265
+ Start Shopping
266
+ </button>
267
+ </div>
268
+ ) : (
269
+ <div className="space-y-4">
270
+ {completedOrders.map((order) => (
271
+ <div
272
+ key={order.id || order._id}
273
+ className="bg-white rounded-lg border border-gray-200 p-6 cursor-pointer hover:border-[#E67E50] hover:shadow-md transition-all"
274
+ onClick={() => handleOrderClick(order)}
275
+ >
276
+ <div className="flex items-center justify-between">
277
+ <div className="flex-1">
278
+ <div className="flex items-center gap-3 mb-2">
279
+ <h3 className="font-semibold text-lg text-gray-900">
280
+ Order #{(order.id || order._id || '').slice(-8)}
281
+ </h3>
282
+ <span className="px-2 py-1 text-xs bg-green-100 text-green-700 rounded">
283
+ {order.orderStatus}
284
+ </span>
285
+ </div>
286
+ <p className="text-sm text-gray-600 mb-1">
287
+ {order.items?.length || 0} {order.items?.length === 1 ? 'item' : 'items'}
288
+ </p>
289
+ {order.createdAt && (
290
+ <p className="text-xs text-gray-500">
291
+ {formatDistanceToNow(new Date(order.createdAt), { addSuffix: true })}
292
+ </p>
293
+ )}
294
+ </div>
295
+ <div className="text-right">
296
+ <div className="flex items-center gap-2 text-[#E67E50]">
297
+ <span className="text-sm font-medium">Review Products</span>
298
+ </div>
299
+ </div>
300
+ </div>
301
+ </div>
302
+ ))}
303
+ </div>
304
+ )}
305
+ </div>
306
+ </div>
307
+ );
308
+ }
@@ -12,6 +12,7 @@ import { FilterChips } from '@/components/FilterChips';
12
12
  import { useRouter } from 'next/navigation';
13
13
  import { ManualOrderDTOOrderStatusEnum, PaymentPaymentStatusEnum } from '@/lib/Apis';
14
14
  import { useBasePath } from '@/providers/BasePathProvider';
15
+ import { ReviewPromptBanner } from '@/components/ReviewPromptBanner';
15
16
 
16
17
  const STATUS_FILTERS = ['All', ...Object.values(ManualOrderDTOOrderStatusEnum)];
17
18
  const PAYMENT_FILTERS = ['All', ...Object.values(PaymentPaymentStatusEnum)];
@@ -24,7 +25,7 @@ export function OrdersScreen() {
24
25
  const [page, setPage] = useState(1);
25
26
  const [selectedFilter, setSelectedFilter] = useState<StatusFilterType>('All');
26
27
  const [selectedPaymentFilter, setSelectedPaymentFilter] = useState<PaymentFilterType>('All');
27
- const { orders, isLoading, pagination } = useOrders(
28
+ const { orders, isLoading, pagination, deleteOrder } = useOrders(
28
29
  page,
29
30
  10,
30
31
  selectedFilter,
@@ -44,24 +45,41 @@ export function OrdersScreen() {
44
45
  });
45
46
  }, [orders, selectedFilter, selectedPaymentFilter]);
46
47
 
48
+ // Find the first completed order that hasn't been dismissed
49
+ const completedOrderForPrompt = useMemo(() => {
50
+ return filteredOrders.find(order =>
51
+ order.orderStatus && ['Delivered', 'Picked Up', 'Fulfilled'].includes(order.orderStatus)
52
+ );
53
+ }, [filteredOrders]);
54
+
47
55
  const hasOrders = filteredOrders.length > 0;
48
56
  const MAX_VISIBLE_FILTERS = 4;
49
57
 
50
58
  return (
51
- <div className="min-h-screen bg-white">
59
+ <div className="min-h-screen bg-linear-to-b from-[#F8FAFC] to-[#EBF4FB]">
52
60
  <div className="container mx-auto px-4 py-8 max-w-6xl">
53
61
  <motion.div
54
62
  initial={{ opacity: 0, y: 24 }}
55
63
  animate={{ opacity: 1, y: 0 }}
56
64
  className="space-y-6"
57
65
  >
58
- <div className="mb-6">
59
- <h1 className="text-2xl font-bold text-slate-900">Orders</h1>
60
- <p className="text-sm text-gray-500 mt-1">
61
- {filteredOrders.length} {filteredOrders.length === 1 ? 'order' : 'orders'}
66
+ <div className="mb-8">
67
+ <h1 className="text-3xl font-black text-secondary tracking-tight">Order History</h1>
68
+ <p className="text-sm font-medium text-muted mt-1 flex items-center gap-2">
69
+ <span className="w-1.5 h-1.5 rounded-full bg-accent animate-pulse" />
70
+ {filteredOrders.length} {filteredOrders.length === 1 ? 'record found' : 'records found'} for your account
62
71
  </p>
63
72
  </div>
64
73
 
74
+ {/* Review Prompt Banner */}
75
+ {!isLoading && completedOrderForPrompt && (
76
+ <ReviewPromptBanner
77
+ orderId={completedOrderForPrompt.id || completedOrderForPrompt._id || ''}
78
+ orderNumber={(completedOrderForPrompt.id || completedOrderForPrompt._id || '').slice(-8)}
79
+ itemCount={completedOrderForPrompt.items?.length || 0}
80
+ />
81
+ )}
82
+
65
83
  {/* Filters */}
66
84
  <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
67
85
  <FilterChips
@@ -96,7 +114,13 @@ export function OrdersScreen() {
96
114
  {isLoading ? (
97
115
  Array.from({ length: 3 }).map((_, index) => <OrderCardSkeleton key={index} />)
98
116
  ) : hasOrders ? (
99
- filteredOrders.map((order) => <OrderCard key={order.id} order={order} />)
117
+ filteredOrders.map((order) => (
118
+ <OrderCard
119
+ key={order.id || order._id}
120
+ order={order}
121
+ onDelete={deleteOrder}
122
+ />
123
+ ))
100
124
  ) : (
101
125
  <EmptyState
102
126
  icon={Package}
@@ -36,6 +36,8 @@ import { useWishlist } from '@/providers/WishlistProvider';
36
36
  import { ProductsApi, ProductVariant, ProductVariantInventoryStatusEnum } from '@/lib/Apis';
37
37
  import { useBasePath } from '@/providers/BasePathProvider';
38
38
  import { Category } from '@/lib/types';
39
+ import { useProductReviews } from '@/hooks/useReviews';
40
+ import { ProductReviewsSection } from '@/components/ProductReviewsSection';
39
41
 
40
42
  const safeFormatDate = (date?: Date | string, format: 'long' | 'short' = 'long'): string => {
41
43
  if (!date) return 'N/A';
@@ -60,6 +62,7 @@ export function ProductDetailScreen({ productId }: ProductDetailScreenProps) {
60
62
  const { addToCart } = useCart();
61
63
  const { isAuthenticated } = useAuth();
62
64
  const notification = useNotification();
65
+ const { reviews, isLoading: reviewsLoading } = useProductReviews(productId);
63
66
 
64
67
  // State declarations
65
68
  const [selectedVariant, setSelectedVariant] = useState<ProductVariant | null>(null);
@@ -97,6 +100,24 @@ export function ProductDetailScreen({ productId }: ProductDetailScreenProps) {
97
100
  return productData;
98
101
  }, [productData, selectedVariant, initialProductData]);
99
102
 
103
+ // Calculate average rating and review count from actual reviews
104
+ const reviewStats = useMemo(() => {
105
+ if (!reviews || reviews.length === 0) {
106
+ return {
107
+ averageRating: product?.rating || 0,
108
+ reviewCount: 0,
109
+ };
110
+ }
111
+
112
+ const totalRating = reviews.reduce((sum, review) => sum + (review.rating || 0), 0);
113
+ const averageRating = totalRating / reviews.length;
114
+
115
+ return {
116
+ averageRating: Math.round(averageRating * 10) / 10, // Round to 1 decimal place
117
+ reviewCount: reviews.length,
118
+ };
119
+ }, [reviews, product?.rating]);
120
+
100
121
  const getVariantImages = () => {
101
122
  if (selectedVariant?.media?.length) {
102
123
  return selectedVariant.media.map((media: any) => ({
@@ -450,7 +471,7 @@ export function ProductDetailScreen({ productId }: ProductDetailScreenProps) {
450
471
  {[...Array(5)].map((_, i) => (
451
472
  <Star
452
473
  key={i}
453
- className={`size-4 ${i < Math.floor(product.rating)
474
+ className={`size-4 ${i < Math.floor(reviewStats.averageRating)
454
475
  ? 'text-[#E67E50] fill-[#E67E50]'
455
476
  : 'text-gray-300'
456
477
  }`}
@@ -458,7 +479,7 @@ export function ProductDetailScreen({ productId }: ProductDetailScreenProps) {
458
479
  ))}
459
480
  </div>
460
481
  <span className="font-['Poppins',sans-serif] text-[14px] text-muted">
461
- {product.rating} ({product.reviewCount ? product.reviewCount : 0} reviews)
482
+ {reviewStats.averageRating} ({reviewStats.reviewCount} {reviewStats.reviewCount === 1 ? 'review' : 'reviews'})
462
483
  </span>
463
484
  </div>
464
485
  {selectedVariant && (
@@ -754,15 +775,7 @@ export function ProductDetailScreen({ productId }: ProductDetailScreenProps) {
754
775
  )}
755
776
 
756
777
  {activeTab === 'reviews' && (
757
- <div className="text-center py-8">
758
- <Star className="size-12 text-[#E67E50] mx-auto mb-4" />
759
- <h3 className="font-['Poppins',sans-serif] font-semibold text-secondary mb-2">
760
- {product.rating} out of 5 stars
761
- </h3>
762
- <p className="font-['Poppins',sans-serif] text-[14px] text-muted">
763
- Based on {product.reviewCount} customer reviews
764
- </p>
765
- </div>
778
+ <ProductReviewsSection productId={productId} />
766
779
  )}
767
780
  </div>
768
781
  </div>
@@ -11,6 +11,7 @@ import {
11
11
  Settings,
12
12
  LogOut,
13
13
  ChevronDown,
14
+ Star,
14
15
  } from 'lucide-react';
15
16
  import { useAuth } from '@/providers/AuthProvider';
16
17
  import { useBasePath } from '@/providers/BasePathProvider';
@@ -22,10 +23,12 @@ import { AccountSavedItemsTab } from '@/components/AccountSavedItemsTab';
22
23
  import { AccountPaymentTab } from '@/components/AccountPaymentTab';
23
24
  import { AccountAddressesTab } from '@/components/AccountAddressesTab';
24
25
  import { AccountSettingsTab } from '@/components/AccountSettingsTab';
26
+ import { AccountReviewsTab } from '@/components/AccountReviewsTab';
25
27
 
26
28
  const tabs = [
27
29
  { id: 'overview', label: 'Overview', icon: User },
28
30
  { id: 'orders', label: 'Orders', icon: Package },
31
+ { id: 'reviews', label: 'My Reviews', icon: Star },
29
32
  { id: 'saved-items', label: 'Saved Items', icon: Heart },
30
33
  // { id: 'payment', label: 'Payment', icon: CreditCard },
31
34
  { id: 'addresses', label: 'Addresses', icon: MapPin },
@@ -75,6 +78,8 @@ export default function AccountPage() {
75
78
  return <AccountOverviewTab />;
76
79
  case 'orders':
77
80
  return <AccountOrdersTab />;
81
+ case 'reviews':
82
+ return <AccountReviewsTab />;
78
83
  case 'saved-items':
79
84
  return <AccountSavedItemsTab />;
80
85
  // case 'payment':
@@ -142,6 +142,10 @@
142
142
  body {
143
143
  @apply bg-gray-50 text-gray-900 antialiased;
144
144
  }
145
+
146
+ button {
147
+ cursor: pointer;
148
+ }
145
149
  }
146
150
 
147
151
  @layer utilities {
package/styles/base.css CHANGED
@@ -32,3 +32,9 @@ body {
32
32
  ::-webkit-scrollbar-thumb:hover { background-color: rgb(156 163 175); }
33
33
 
34
34
 
35
+
36
+
37
+ /* Global button cursor */
38
+ button {
39
+ cursor: pointer;
40
+ }
@@ -1,3 +1,6 @@
1
1
  @import "../src/styles/globals.css";
2
2
 
3
3
 
4
+ button {
5
+ cursor: pointer !important;
6
+ }
@@ -1,75 +0,0 @@
1
- /* tslint:disable */
2
- /* eslint-disable */
3
- /**
4
- * Hey Pharamcist API
5
- * No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen)
6
- *
7
- * OpenAPI spec version: 1.0
8
- *
9
- *
10
- * NOTE: This class is auto generated by the swagger code generator program.
11
- * https://github.com/swagger-api/swagger-codegen.git
12
- * Do not edit the class manually.
13
- */
14
- /**
15
- *
16
- * @export
17
- * @interface CreateNotificationDto
18
- */
19
- export interface CreateNotificationDto {
20
- _id?: string;
21
- /**
22
- *
23
- * @type {string}
24
- * @memberof CreateNotificationDto
25
- */
26
- title: string;
27
- /**
28
- *
29
- * @type {string}
30
- * @memberof CreateNotificationDto
31
- */
32
- content: string;
33
- /**
34
- *
35
- * @type {string}
36
- * @memberof CreateNotificationDto
37
- */
38
- notificationType: string;
39
- /**
40
- *
41
- * @type {string}
42
- * @memberof CreateNotificationDto
43
- */
44
- notificationLink: string;
45
- /**
46
- *
47
- * @type {Array<string>}
48
- * @memberof CreateNotificationDto
49
- */
50
- sentTo: Array<string>;
51
- /**
52
- *
53
- * @type {Array<string>}
54
- * @memberof CreateNotificationDto
55
- */
56
- readBy: Array<string>;
57
- /**
58
- *
59
- * @type {Array<string>}
60
- * @memberof CreateNotificationDto
61
- */
62
- clearedBy: Array<string>;
63
- /**
64
- *
65
- * @type {boolean}
66
- * @memberof CreateNotificationDto
67
- */
68
- isAdminNotification: boolean;
69
- /**
70
- *
71
- * @type {string}
72
- * @memberof CreateNotificationDto
73
- */
74
- storeId: string;
75
- }