hey-pharmacist-ecommerce 1.1.27 → 1.1.29

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 (61) hide show
  1. package/dist/index.d.mts +344 -640
  2. package/dist/index.d.ts +344 -640
  3. package/dist/index.js +1814 -835
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +1814 -837
  6. package/dist/index.mjs.map +1 -1
  7. package/package.json +1 -1
  8. package/src/components/AccountOrdersTab.tsx +1 -1
  9. package/src/components/AccountSettingsTab.tsx +88 -6
  10. package/src/components/CartItem.tsx +1 -1
  11. package/src/components/Header.tsx +8 -2
  12. package/src/components/OrderCard.tsx +4 -4
  13. package/src/components/ProductCard.tsx +59 -42
  14. package/src/components/QuickViewModal.tsx +13 -13
  15. package/src/hooks/useAddresses.ts +4 -1
  16. package/src/hooks/usePaymentMethods.ts +26 -31
  17. package/src/hooks/useProducts.ts +63 -64
  18. package/src/hooks/useWishlistProducts.ts +4 -5
  19. package/src/index.ts +2 -0
  20. package/src/lib/Apis/api.ts +0 -1
  21. package/src/lib/Apis/apis/auth-api.ts +18 -29
  22. package/src/lib/Apis/apis/products-api.ts +845 -405
  23. package/src/lib/Apis/models/category-populated.ts +0 -12
  24. package/src/lib/Apis/models/category-sub-category-populated.ts +2 -2
  25. package/src/lib/Apis/models/category.ts +0 -18
  26. package/src/lib/Apis/models/{table-cell-dto.ts → change-password-dto.ts} +6 -6
  27. package/src/lib/Apis/models/create-product-dto.ts +30 -23
  28. package/src/lib/Apis/models/create-sub-category-dto.ts +6 -0
  29. package/src/lib/Apis/models/create-variant-dto.ts +29 -29
  30. package/src/lib/Apis/models/index.ts +5 -7
  31. package/src/lib/Apis/models/paginated-products-dto.ts +6 -6
  32. package/src/lib/Apis/models/product-summary.ts +69 -0
  33. package/src/lib/Apis/models/product-variant.ts +34 -65
  34. package/src/lib/Apis/models/product.ts +138 -0
  35. package/src/lib/Apis/models/products-insights-dto.ts +12 -0
  36. package/src/lib/Apis/models/single-product-media.ts +0 -12
  37. package/src/lib/Apis/models/sub-category.ts +6 -12
  38. package/src/lib/Apis/models/update-product-dto.ts +30 -19
  39. package/src/lib/Apis/models/update-sub-category-dto.ts +6 -0
  40. package/src/lib/Apis/models/{update-product-variant-dto.ts → update-variant-dto.ts} +51 -45
  41. package/src/lib/Apis/models/{shallow-parent-category-dto.ts → variant-id-inventory-body.ts} +5 -11
  42. package/src/lib/api-adapter/config.ts +53 -0
  43. package/src/lib/validations/address.ts +1 -1
  44. package/src/providers/FavoritesProvider.tsx +5 -5
  45. package/src/providers/WishlistProvider.tsx +4 -4
  46. package/src/screens/CartScreen.tsx +1 -1
  47. package/src/screens/ChangePasswordScreen.tsx +2 -6
  48. package/src/screens/CheckoutScreen.tsx +40 -11
  49. package/src/screens/EditProfileScreen.tsx +5 -1
  50. package/src/screens/ForgotPasswordScreen.tsx +153 -0
  51. package/src/screens/ProductDetailScreen.tsx +51 -60
  52. package/src/screens/RegisterScreen.tsx +31 -31
  53. package/src/screens/ResetPasswordScreen.tsx +202 -0
  54. package/src/screens/SearchResultsScreen.tsx +264 -26
  55. package/src/screens/ShopScreen.tsx +42 -45
  56. package/src/screens/WishlistScreen.tsx +35 -31
  57. package/src/lib/Apis/apis/product-variants-api.ts +0 -552
  58. package/src/lib/Apis/models/create-single-variant-product-dto.ts +0 -154
  59. package/src/lib/Apis/models/extended-product-dto.ts +0 -206
  60. package/src/lib/Apis/models/frequently-bought-product-dto.ts +0 -71
  61. package/src/lib/Apis/models/table-dto.ts +0 -34
@@ -1,87 +1,204 @@
1
1
  'use client';
2
2
 
3
- import { useEffect, useState } from 'react';
3
+ import { useEffect, useState, useCallback, useMemo } from 'react';
4
4
  import { useSearchParams } from 'next/navigation';
5
5
  import { ProductsApi } from '@/lib/Apis';
6
- import { ExtendedProductDTO } from '@/lib/Apis';
6
+ import { Product } from '@/lib/Apis';
7
7
  import { ProductCard } from '@/components/ProductCard';
8
8
  import { Skeleton } from '@/components/ui/Skeleton';
9
9
  import { Input } from '@/components/ui/Input';
10
- import { Search, X } from 'lucide-react';
10
+ import { Search, X, TrendingUp, Clock } from 'lucide-react';
11
11
  import Link from 'next/link';
12
12
  import { AXIOS_CONFIG } from '@/lib/Apis/wrapper';
13
13
  import { useWishlist } from '@/providers/WishlistProvider';
14
14
  import { useRouter } from 'next/navigation';
15
15
  import { useBasePath } from '@/providers/BasePathProvider';
16
+ import { motion, AnimatePresence } from 'framer-motion';
16
17
 
17
18
  export default function SearchPage() {
18
19
  const router = useRouter();
19
20
  const { buildPath } = useBasePath();
20
21
  const searchParams = useSearchParams();
21
22
  const searchQuery = searchParams.get('q') || '';
22
- const [products, setProducts] = useState<ExtendedProductDTO[]>([]);
23
+ const [products, setProducts] = useState<Product[]>([]);
23
24
  const [isLoading, setIsLoading] = useState(true);
24
25
  const [searchInput, setSearchInput] = useState(searchQuery);
25
26
  const [hasSearched, setHasSearched] = useState(false);
26
27
  const { isInWishlist } = useWishlist();
28
+ const [suggestions, setSuggestions] = useState<string[]>([]);
29
+ const [showSuggestions, setShowSuggestions] = useState(false);
30
+ const [recentSearches, setRecentSearches] = useState<string[]>([]);
31
+
32
+ // Load recent searches from localStorage
33
+ useEffect(() => {
34
+ const stored = localStorage.getItem('recent_searches');
35
+ if (stored) {
36
+ try {
37
+ setRecentSearches(JSON.parse(stored));
38
+ } catch (e) {
39
+ console.error('Failed to parse recent searches', e);
40
+ }
41
+ }
42
+ }, []);
43
+
44
+ // Save search query to recent searches
45
+ const saveRecentSearch = useCallback((query: string) => {
46
+ if (!query.trim()) return;
47
+
48
+ setRecentSearches((prev) => {
49
+ const updated = [query, ...prev.filter(s => s !== query)].slice(0, 5);
50
+ localStorage.setItem('recent_searches', JSON.stringify(updated));
51
+ return updated;
52
+ });
53
+ }, []);
54
+
55
+ // Generate suggestions based on product data
56
+ const generateSuggestions = useMemo(() => {
57
+ if (!searchInput.trim() || searchInput.length < 2) {
58
+ return [];
59
+ }
60
+
61
+ const query = searchInput.toLowerCase().trim();
62
+ const suggestionSet = new Set<string>();
63
+
64
+ // Extract suggestions from product names, brands, categories, and tags
65
+ products.forEach((product) => {
66
+ // Product name matches
67
+ if (product.name?.toLowerCase().includes(query)) {
68
+ suggestionSet.add(product.name);
69
+ }
70
+
71
+ // Brand matches
72
+ if (product.brand?.toLowerCase().includes(query)) {
73
+ suggestionSet.add(product.brand);
74
+ }
75
+
76
+ // Tag matches
77
+ product.tags?.forEach((tag) => {
78
+ if (tag.toLowerCase().includes(query)) {
79
+ suggestionSet.add(tag);
80
+ }
81
+ });
82
+ });
83
+
84
+ return Array.from(suggestionSet).slice(0, 5);
85
+ }, [searchInput, products]);
86
+
87
+ useEffect(() => {
88
+ setSuggestions(generateSuggestions);
89
+ }, [generateSuggestions]);
90
+
91
+ // Sanitize search input to handle special characters
92
+ const sanitizeSearchInput = useCallback((input: string): string => {
93
+ // Remove leading/trailing whitespace
94
+ let sanitized = input.trim();
95
+
96
+ // Replace multiple spaces with single space
97
+ sanitized = sanitized.replace(/\s+/g, ' ');
98
+
99
+ // Escape special regex characters for safe processing
100
+ // but keep the original input for search API
101
+ return sanitized;
102
+ }, []);
27
103
 
28
104
  useEffect(() => {
29
105
  const fetchSearchResults = async () => {
30
- if (!searchQuery.trim()) {
106
+ const sanitizedQuery = sanitizeSearchInput(searchQuery);
107
+
108
+ if (!sanitizedQuery) {
31
109
  setProducts([]);
32
110
  setIsLoading(false);
111
+ setHasSearched(false);
33
112
  return;
34
113
  }
35
114
 
36
115
  try {
37
116
  setIsLoading(true);
38
117
  const api = new ProductsApi(AXIOS_CONFIG);
39
- const response = await api.getAllProductsForStore(
40
- searchQuery,
41
- undefined, // productType
42
- undefined, // categoryId
118
+
119
+ // Search for products using getAllProducts
120
+ let response = await api.getAllProducts(
121
+ sanitizedQuery, // searchTerm
43
122
  undefined, // maxPrice
44
123
  undefined, // minPrice
45
124
  undefined, // brandFilter
46
- 'in-stock', // availability
125
+ undefined, // availability
47
126
  'relevance', // sort
48
- true, // includeNoVariantProducts
127
+ undefined, // subCategoryId
128
+ undefined, // categoryId
49
129
  true, // isActive
50
130
  20, // limit
51
131
  1 // page
52
132
  );
53
133
 
134
+ console.log('Search API Response:', {
135
+ query: sanitizedQuery,
136
+ status: response.status,
137
+ dataLength: response.data?.data?.length,
138
+ totalItems: response.data?.totalItems
139
+ });
140
+
54
141
  if (response.data?.data) {
55
- const transformedProducts = response.data.data.map((item: ExtendedProductDTO) => ({
142
+ const transformedProducts = response.data.data.map((item: Product) => ({
56
143
  ...item,
57
144
  id: item._id || '',
58
145
  }));
59
146
 
60
147
  setProducts(transformedProducts);
148
+
149
+ // Save to recent searches if results found
150
+ if (transformedProducts.length > 0) {
151
+ saveRecentSearch(sanitizedQuery);
152
+ }
61
153
  } else {
62
154
  setProducts([]);
63
155
  }
64
156
  setHasSearched(true);
65
157
  } catch (error) {
66
158
  console.error('Error fetching search results:', error);
159
+ if (error instanceof Error) {
160
+ console.error('Error details:', error.message);
161
+ }
67
162
  setProducts([]);
163
+ setHasSearched(true);
68
164
  } finally {
69
165
  setIsLoading(false);
70
166
  }
71
167
  };
72
168
 
73
169
  fetchSearchResults();
74
- }, [searchQuery]);
170
+ }, [searchQuery, sanitizeSearchInput, saveRecentSearch]);
75
171
 
76
172
  const handleSearch = (e: React.FormEvent) => {
77
173
  e.preventDefault();
78
- if (searchInput.trim()) {
79
- window.location.href = `/search?q=${encodeURIComponent(searchInput.trim())}`;
174
+ const sanitized = sanitizeSearchInput(searchInput);
175
+ if (sanitized) {
176
+ setShowSuggestions(false);
177
+ router.push(buildPath(`/search?q=${encodeURIComponent(sanitized)}`));
80
178
  }
81
179
  };
82
180
 
83
181
  const clearSearch = () => {
84
182
  setSearchInput('');
183
+ setShowSuggestions(false);
184
+ };
185
+
186
+ const handleSuggestionClick = (suggestion: string) => {
187
+ setSearchInput(suggestion);
188
+ setShowSuggestions(false);
189
+ router.push(buildPath(`/search?q=${encodeURIComponent(suggestion)}`));
190
+ };
191
+
192
+ const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
193
+ const value = e.target.value;
194
+ setSearchInput(value);
195
+ setShowSuggestions(value.trim().length >= 2);
196
+ };
197
+
198
+ const handleInputFocus = () => {
199
+ if (searchInput.trim().length >= 2) {
200
+ setShowSuggestions(true);
201
+ }
85
202
  };
86
203
 
87
204
  return (
@@ -93,9 +210,12 @@ export default function SearchPage() {
93
210
  <Input
94
211
  type="text"
95
212
  value={searchInput}
96
- onChange={(e) => setSearchInput(e.target.value)}
213
+ onChange={handleInputChange}
214
+ onFocus={handleInputFocus}
215
+ onBlur={() => setTimeout(() => setShowSuggestions(false), 200)}
97
216
  placeholder="Search for products..."
98
217
  className="pl-10 pr-10 py-6 text-base rounded-lg border-gray-300 focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
218
+ autoComplete="off"
99
219
  />
100
220
  {searchInput && (
101
221
  <button
@@ -106,10 +226,70 @@ export default function SearchPage() {
106
226
  <X className="h-5 w-5" />
107
227
  </button>
108
228
  )}
229
+
230
+ {/* Search Suggestions Dropdown */}
231
+ <AnimatePresence>
232
+ {showSuggestions && (suggestions.length > 0 || recentSearches.length > 0) && (
233
+ <motion.div
234
+ initial={{ opacity: 0, y: -10 }}
235
+ animate={{ opacity: 1, y: 0 }}
236
+ exit={{ opacity: 0, y: -10 }}
237
+ transition={{ duration: 0.2 }}
238
+ className="absolute z-50 w-full mt-2 bg-white rounded-lg shadow-lg border border-gray-200 overflow-hidden"
239
+ >
240
+ {/* Real-time Suggestions */}
241
+ {suggestions.length > 0 && (
242
+ <div className="p-2">
243
+ <div className="px-3 py-2 text-xs font-semibold text-gray-500 uppercase tracking-wider flex items-center gap-2">
244
+ <TrendingUp className="h-3 w-3" />
245
+ Suggestions
246
+ </div>
247
+ {suggestions.map((suggestion, index) => (
248
+ <button
249
+ key={index}
250
+ type="button"
251
+ onClick={() => handleSuggestionClick(suggestion)}
252
+ className="w-full text-left px-3 py-2.5 hover:bg-gray-50 rounded-md transition-colors flex items-center gap-2 group"
253
+ >
254
+ <Search className="h-4 w-4 text-gray-400 group-hover:text-primary-500" />
255
+ <span className="text-sm text-gray-700 group-hover:text-gray-900">
256
+ {suggestion}
257
+ </span>
258
+ </button>
259
+ ))}
260
+ </div>
261
+ )}
262
+
263
+ {/* Recent Searches */}
264
+ {recentSearches.length > 0 && !searchInput.trim() && (
265
+ <div className="p-2 border-t border-gray-100">
266
+ <div className="px-3 py-2 text-xs font-semibold text-gray-500 uppercase tracking-wider flex items-center gap-2">
267
+ <Clock className="h-3 w-3" />
268
+ Recent Searches
269
+ </div>
270
+ {recentSearches.map((search, index) => (
271
+ <button
272
+ key={index}
273
+ type="button"
274
+ onClick={() => handleSuggestionClick(search)}
275
+ className="w-full text-left px-3 py-2.5 hover:bg-gray-50 rounded-md transition-colors flex items-center gap-2 group"
276
+ >
277
+ <Clock className="h-4 w-4 text-gray-400 group-hover:text-primary-500" />
278
+ <span className="text-sm text-gray-700 group-hover:text-gray-900">
279
+ {search}
280
+ </span>
281
+ </button>
282
+ ))}
283
+ </div>
284
+ )}
285
+ </motion.div>
286
+ )}
287
+ </AnimatePresence>
109
288
  </div>
110
289
  <button
111
290
  type="submit"
112
- className="mt-4 w-full bg-primary-600 hover:bg-primary-700 text-white font-medium py-2 px-4 rounded-lg transition-colors"
291
+ className="mt-4 w-full bg-primary-600 hover:bg-primary-700 text-white font-medium py-2 px-4 rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
292
+ disabled={!searchInput.trim()}
113
293
  >
114
294
  Search
115
295
  </button>
@@ -144,21 +324,79 @@ export default function SearchPage() {
144
324
  </div>
145
325
  ) : hasSearched ? (
146
326
  <div className="text-center py-12">
147
- <div className="text-gray-500 text-lg mb-4">
148
- No products found for &quot;{searchQuery}&quot;
327
+ <div className="mb-6">
328
+ <div className="w-16 h-16 bg-gray-100 rounded-full flex items-center justify-center mx-auto mb-4">
329
+ <Search className="h-8 w-8 text-gray-400" />
330
+ </div>
331
+ <h3 className="text-xl font-semibold text-gray-900 mb-2">
332
+ No products found
333
+ </h3>
334
+ <p className="text-gray-500 text-base mb-1">
335
+ We couldn&apos;t find any products matching &quot;{searchQuery}&quot;
336
+ </p>
337
+ <button
338
+ onClick={clearSearch}
339
+ className="px-6 py-2.5 mt-4 text-gray-700 rounded-lg hover:bg-gray-50 transition-colors font-medium border border-gray-300 bg-white cursor-pointer"
340
+ >
341
+ Clear Search
342
+ </button>
149
343
  </div>
150
- <p className="text-gray-500 mb-6">
151
- Try different keywords or check out our{" "}
152
- <Link href="/shop" className="text-primary-600 hover:underline ml-1 font-medium">
153
- featured products
344
+
345
+ <div className="flex flex-col sm:flex-row gap-3 justify-center items-center">
346
+
347
+ <Link
348
+ href={buildPath('/shop')}
349
+ className="px-6 py-2.5 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors font-medium"
350
+ >
351
+ Browse All Products
154
352
  </Link>
155
- </p>
353
+ </div>
354
+
355
+ {recentSearches.length > 0 && (
356
+ <div className="mt-8 pt-8 border-t border-gray-200">
357
+ <p className="text-sm font-medium text-gray-700 mb-3">Recent Searches:</p>
358
+ <div className="flex flex-wrap gap-2 justify-center">
359
+ {recentSearches.map((search, index) => (
360
+ <button
361
+ key={index}
362
+ onClick={() => handleSuggestionClick(search)}
363
+ className="px-3 py-1.5 bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-full text-sm transition-colors"
364
+ >
365
+ {search}
366
+ </button>
367
+ ))}
368
+ </div>
369
+ </div>
370
+ )}
156
371
  </div>
157
372
  ) : (
158
373
  <div className="text-center py-12">
159
- <p className="text-gray-500">
160
- Enter a search term to find products
374
+ <div className="w-16 h-16 bg-primary-50 rounded-full flex items-center justify-center mx-auto mb-4">
375
+ <Search className="h-8 w-8 text-primary-500" />
376
+ </div>
377
+ <h3 className="text-xl font-semibold text-gray-900 mb-2">
378
+ Start Searching
379
+ </h3>
380
+ <p className="text-gray-500 mb-6">
381
+ Enter a search term above to find products
161
382
  </p>
383
+ {recentSearches.length > 0 && (
384
+ <div className="max-w-md mx-auto">
385
+ <p className="text-sm font-medium text-gray-700 mb-3">Recent Searches:</p>
386
+ <div className="flex flex-wrap gap-2 justify-center">
387
+ {recentSearches.map((search, index) => (
388
+ <button
389
+ key={index}
390
+ onClick={() => handleSuggestionClick(search)}
391
+ className="px-3 py-1.5 bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-full text-sm transition-colors flex items-center gap-2"
392
+ >
393
+ <Clock className="h-3 w-3" />
394
+ {search}
395
+ </button>
396
+ ))}
397
+ </div>
398
+ </div>
399
+ )}
162
400
  </div>
163
401
  )}
164
402
  </div>
@@ -6,11 +6,10 @@ import {
6
6
  useMemo,
7
7
  useState,
8
8
  } from 'react';
9
- import { AnimatePresence, motion } from 'framer-motion';
9
+ import { motion } from 'framer-motion';
10
10
  import {
11
11
  ArrowUpDown,
12
12
  ChevronDown,
13
- ChevronUp,
14
13
  Clock,
15
14
  LayoutGrid,
16
15
  LayoutList,
@@ -74,12 +73,12 @@ export function ShopScreen({ initialFilters = {}, categoryName }: ShopScreenProp
74
73
 
75
74
  const { products, isLoading, pagination } = useProducts(filters, page, 20);
76
75
  const { categories, isLoading: isLoadingCategories } = useCategories();
77
-
78
76
  const handleSearch = (e: React.FormEvent) => {
79
77
  e.preventDefault();
80
- if (searchQuery.trim()) {
78
+ const sanitized = searchQuery.trim().replace(/\s+/g, ' ');
79
+ if (sanitized) {
81
80
  setIsSearching(true);
82
- router.push(buildPath(`/search?q=${encodeURIComponent(searchQuery.trim())}`));
81
+ router.push(buildPath(`/search?q=${encodeURIComponent(sanitized)}`));
83
82
  }
84
83
  };
85
84
 
@@ -90,10 +89,13 @@ export function ShopScreen({ initialFilters = {}, categoryName }: ShopScreenProp
90
89
 
91
90
  // Handle search when pressing Enter
92
91
  const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
93
- if (e.key === 'Enter' && searchQuery.trim()) {
92
+ if (e.key === 'Enter') {
94
93
  e.preventDefault();
95
- setIsSearching(true);
96
- router.push(buildPath(`/search?q=${encodeURIComponent(searchQuery.trim())}`));
94
+ const sanitized = searchQuery.trim().replace(/\s+/g, ' ');
95
+ if (sanitized) {
96
+ setIsSearching(true);
97
+ router.push(buildPath(`/search?q=${encodeURIComponent(sanitized)}`));
98
+ }
97
99
  }
98
100
  };
99
101
 
@@ -120,9 +122,10 @@ export function ShopScreen({ initialFilters = {}, categoryName }: ShopScreenProp
120
122
  if (filters.category) updates[filters.category] = true;
121
123
  if (filters.subCategory) {
122
124
  const parent = categories.find((c) =>
123
- c.categorySubCategories?.some((sc) => sc.id === filters.subCategory)
125
+ c.categorySubCategories?.some((sc) => (sc._id ?? sc.id) === filters.subCategory)
124
126
  );
125
- if (parent?.id) updates[parent.id] = true;
127
+ const parentId = parent?._id ?? parent?.id;
128
+ if (parentId) updates[parentId] = true;
126
129
  }
127
130
  if (Object.keys(updates).length) {
128
131
  setExpandedCategories((prev) => ({ ...prev, ...updates }));
@@ -156,7 +159,7 @@ export function ShopScreen({ initialFilters = {}, categoryName }: ShopScreenProp
156
159
  let inStockCount = 0;
157
160
 
158
161
  products.forEach((product) => {
159
- if (product.inventoryCount > 0) inStockCount += 1;
162
+ if (product.summary?.totalInventory > 0) inStockCount += 1;
160
163
  if (new Date(product.createdAt).getTime() >= monthAgo) newArrivals += 1;
161
164
  });
162
165
 
@@ -213,7 +216,7 @@ export function ShopScreen({ initialFilters = {}, categoryName }: ShopScreenProp
213
216
  if (isLoading) return products;
214
217
  let items = [...products];
215
218
  if (filters.tags?.length) {
216
- items = items.filter((p) => Array.isArray(p.tags) && p.tags.some((t) => filters.tags!.includes(t)));
219
+ items = items.filter((p) => Array.isArray(p.tags) && p.tags.some((t: string) => filters.tags!.includes(t)));
217
220
  }
218
221
  if (filters.newArrivals) {
219
222
  const thirtyDaysAgo = Date.now() - 30 * 24 * 60 * 60 * 1000;
@@ -234,9 +237,9 @@ export function ShopScreen({ initialFilters = {}, categoryName }: ShopScreenProp
234
237
 
235
238
  switch (sortOption) {
236
239
  case 'price-low-high':
237
- return items.sort((a, b) => a.finalPrice - b.finalPrice);
240
+ return items.sort((a, b) => (a.variants?.[0]?.finalPrice ?? 0) - (b.variants?.[0]?.finalPrice ?? 0));
238
241
  case 'price-high-low':
239
- return items.sort((a, b) => b.finalPrice - a.finalPrice);
242
+ return items.sort((a, b) => (b.variants?.[0]?.finalPrice ?? 0) - (a.variants?.[0]?.finalPrice ?? 0));
240
243
  case 'newest':
241
244
  return items.sort(
242
245
  (a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
@@ -262,7 +265,7 @@ export function ShopScreen({ initialFilters = {}, categoryName }: ShopScreenProp
262
265
  const quickSearches = useMemo(() => {
263
266
  const counts = new Map<string, number>();
264
267
  products.forEach((p) => {
265
- (p.tags || []).forEach((t) => counts.set(t, (counts.get(t) || 0) + 1));
268
+ (p.tags || []).forEach((t: string) => counts.set(t, (counts.get(t) || 0) + 1));
266
269
  });
267
270
  return Array.from(counts.entries())
268
271
  .sort((a, b) => b[1] - a[1])
@@ -539,7 +542,7 @@ export function ShopScreen({ initialFilters = {}, categoryName }: ShopScreenProp
539
542
  let subName: string | undefined;
540
543
  let parentName: string | undefined;
541
544
  categories.forEach((cat) => {
542
- const found = cat.categorySubCategories?.find((sc) => sc.id === subCategoryFilter);
545
+ const found = cat.categorySubCategories?.find((sc) => (sc._id ?? sc.id) === subCategoryFilter);
543
546
  if (found) {
544
547
  subName = found.name;
545
548
  parentName = cat.name;
@@ -551,7 +554,7 @@ export function ShopScreen({ initialFilters = {}, categoryName }: ShopScreenProp
551
554
  onRemove: handleRemoveSubCategory,
552
555
  });
553
556
  if (categoryFilter) {
554
- const catObj = categories.find((c) => c.id === categoryFilter);
557
+ const catObj = categories.find((c) => (c._id ?? c.id) === categoryFilter);
555
558
  chips.push({
556
559
  key: 'category',
557
560
  label: `Category: ${catObj?.name ?? parentName ?? categoryFilter}`,
@@ -559,7 +562,7 @@ export function ShopScreen({ initialFilters = {}, categoryName }: ShopScreenProp
559
562
  });
560
563
  }
561
564
  } else if (categoryFilter) {
562
- const category = categories.find((cat) => cat.id === categoryFilter);
565
+ const category = categories.find((cat) => (cat._id ?? cat.id) === categoryFilter);
563
566
  chips.push({
564
567
  key: 'category',
565
568
  label: `Category: ${category?.name ?? categoryFilter}`,
@@ -697,15 +700,16 @@ export function ShopScreen({ initialFilters = {}, categoryName }: ShopScreenProp
697
700
  {expandedFilterSections.category && (
698
701
  <div className="space-y-2">
699
702
  {sortedCategories.map((category) => {
700
- const isCategoryActive = categoryFilter === category.id;
701
- const isExpanded = !!expandedCategories[category.id as string];
703
+ const categoryId = category._id ?? category.id ?? '';
704
+ const isCategoryActive = categoryFilter === categoryId;
705
+ const isExpanded = !!expandedCategories[categoryId];
702
706
  const Icon = getCategoryIconForFilter(category.name ?? '');
703
707
  return (
704
708
  <button
705
- key={category.id}
709
+ key={categoryId}
706
710
  onClick={() => {
707
- if (!isExpanded) toggleCategoryExpand(category.id ?? '');
708
- handleCategoryChange(category.id ?? '');
711
+ if (!isExpanded) toggleCategoryExpand(categoryId);
712
+ handleCategoryChange(categoryId);
709
713
  }}
710
714
  className={`w-full text-left px-4 py-3 rounded-xl font-['Poppins',sans-serif] text-[13px] transition-all flex items-center gap-3 ${isCategoryActive
711
715
  ? 'bg-primary text-white shadow-lg'
@@ -955,15 +959,16 @@ export function ShopScreen({ initialFilters = {}, categoryName }: ShopScreenProp
955
959
 
956
960
  {displayCategories.map((category, index) => {
957
961
  const Icon = getCategoryIcon(category.name ?? '');
958
- const isSelected = categoryFilter === category.id;
962
+ const categoryId = category._id ?? category.id ?? '';
963
+ const isSelected = categoryFilter === categoryId;
959
964
 
960
965
  return (
961
966
  <motion.button
962
- key={category.id}
967
+ key={categoryId}
963
968
  initial={{ opacity: 0, y: 20 }}
964
969
  animate={{ opacity: 1, y: 0 }}
965
970
  transition={{ delay: index * 0.1 }}
966
- onClick={() => handleCategoryChange(category.id ?? '')}
971
+ onClick={() => handleCategoryChange(categoryId)}
967
972
  className={`group relative overflow-hidden rounded-[24px] p-6 min-h-[180px] min-w-[170px] transition-all duration-300 ${isSelected ? 'bg-linear-to-br from-primary to-secondary text-white shadow-xl scale-105'
968
973
  : 'bg-linear-to-br from-gray-50 to-white hover:shadow-lg border-2 border-gray-100 hover:border-primary'
969
974
  }`}
@@ -1106,14 +1111,11 @@ export function ShopScreen({ initialFilters = {}, categoryName }: ShopScreenProp
1106
1111
  ) : (
1107
1112
  <div className="space-y-4">
1108
1113
  {displayedProducts.map((product) => {
1109
- const discount =
1110
- product.priceBeforeDiscount && product.priceBeforeDiscount > product.finalPrice
1111
- ? Math.round(
1112
- ((product.priceBeforeDiscount - product.finalPrice) /
1113
- product.priceBeforeDiscount) *
1114
- 100
1115
- )
1116
- : 0;
1114
+ const firstVariant = product.variants?.[0];
1115
+ const displayFinalPrice = firstVariant?.finalPrice ?? 0;
1116
+ const displayRetailPrice = firstVariant?.retailPrice ?? 0;
1117
+ const displayIsDiscounted = firstVariant?.isDiscounted ?? false;
1118
+ const displayInventoryCount = firstVariant?.inventoryCount ?? 0;
1117
1119
 
1118
1120
  return (
1119
1121
  <motion.div
@@ -1124,7 +1126,7 @@ export function ShopScreen({ initialFilters = {}, categoryName }: ShopScreenProp
1124
1126
  >
1125
1127
  <div className="relative h-48 w-full overflow-hidden rounded-2xl bg-gray-100 md:h-40 md:w-40">
1126
1128
  <Image
1127
- src={product.productMedia[0]?.file || '/placeholder-product.jpg'}
1129
+ src={product.media?.[0]?.file || '/placeholder-product.jpg'}
1128
1130
  alt={product.name}
1129
1131
  fill
1130
1132
  className="object-cover transition duration-500 group-hover:scale-105"
@@ -1133,12 +1135,7 @@ export function ShopScreen({ initialFilters = {}, categoryName }: ShopScreenProp
1133
1135
 
1134
1136
  <div className="flex-1 space-y-3">
1135
1137
  <div className="flex flex-wrap items-center gap-2 text-xs font-semibold uppercase tracking-wide text-primary-600">
1136
- {product.parentCategories.length > 0 && (
1137
- <span className="rounded-full bg-primary-50 px-3 py-1 text-primary-700">
1138
- {product.parentCategories.map((category) => category?.name).join(', ')}
1139
- </span>
1140
- )}
1141
- {product.tags?.slice(0, 3).map((tag) => (
1138
+ {product.tags?.slice(0, 3).map((tag: string) => (
1142
1139
  <span
1143
1140
  key={tag}
1144
1141
  className="rounded-full bg-slate-100 px-3 py-1 text-gray-600"
@@ -1153,7 +1150,7 @@ export function ShopScreen({ initialFilters = {}, categoryName }: ShopScreenProp
1153
1150
  <div className="flex flex-wrap items-center gap-4 text-sm text-gray-500">
1154
1151
  <span className="inline-flex items-center gap-2 font-medium text-primary-600">
1155
1152
  <ShieldCheck className="h-4 w-4" />
1156
- {product.inventoryCount > 0 ? 'In stock & ready to ship' : 'Restocking soon'}
1153
+ {displayInventoryCount > 0 ? 'In stock & ready to ship' : 'Restocking soon'}
1157
1154
  </span>
1158
1155
  <span className="inline-flex items-center gap-2">
1159
1156
  <Clock className="h-4 w-4 text-primary-500" />
@@ -1165,11 +1162,11 @@ export function ShopScreen({ initialFilters = {}, categoryName }: ShopScreenProp
1165
1162
  <div className="flex w-full flex-col items-end gap-3 md:w-auto">
1166
1163
  <div className="text-right">
1167
1164
  <p className="text-3xl font-semibold text-gray-900">
1168
- {formatPrice(product.finalPrice)}
1165
+ {formatPrice(displayFinalPrice)}
1169
1166
  </p>
1170
- {product.isDiscounted && (
1167
+ {displayIsDiscounted && (
1171
1168
  <p className="text-sm text-gray-400 line-through">
1172
- {formatPrice(product.priceBeforeDiscount)}
1169
+ {formatPrice(displayRetailPrice)}
1173
1170
  </p>
1174
1171
  )}
1175
1172
  </div>