hey-pharmacist-ecommerce 1.1.28 → 1.1.30

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 (91) hide show
  1. package/dist/index.d.mts +10552 -1370
  2. package/dist/index.d.ts +10552 -1370
  3. package/dist/index.js +4696 -1281
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +4640 -1283
  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/useStoreCapabilities.ts +87 -0
  19. package/src/hooks/useWishlistProducts.ts +4 -5
  20. package/src/index.ts +6 -0
  21. package/src/lib/Apis/api.ts +0 -1
  22. package/src/lib/Apis/apis/auth-api.ts +37 -36
  23. package/src/lib/Apis/apis/categories-api.ts +97 -0
  24. package/src/lib/Apis/apis/products-api.ts +942 -405
  25. package/src/lib/Apis/apis/shipping-api.ts +105 -0
  26. package/src/lib/Apis/apis/stores-api.ts +356 -0
  27. package/src/lib/Apis/apis/sub-categories-api.ts +97 -0
  28. package/src/lib/Apis/apis/users-api.ts +8 -8
  29. package/src/lib/Apis/models/address-created-request.ts +0 -12
  30. package/src/lib/Apis/models/address.ts +0 -12
  31. package/src/lib/Apis/models/api-key-info-dto.ts +49 -0
  32. package/src/lib/Apis/models/category-populated.ts +0 -12
  33. package/src/lib/Apis/models/category-sub-category-populated.ts +2 -2
  34. package/src/lib/Apis/models/category.ts +0 -18
  35. package/src/lib/Apis/models/{table-cell-dto.ts → change-password-dto.ts} +6 -6
  36. package/src/lib/Apis/models/create-address-dto.ts +0 -12
  37. package/src/lib/Apis/models/create-discount-dto.ts +0 -8
  38. package/src/lib/Apis/models/create-product-dto.ts +30 -23
  39. package/src/lib/Apis/models/create-store-address-dto.ts +0 -12
  40. package/src/lib/Apis/models/create-store-dto-settings.ts +51 -0
  41. package/src/lib/Apis/models/create-store-dto.ts +7 -0
  42. package/src/lib/Apis/models/create-sub-category-dto.ts +6 -0
  43. package/src/lib/Apis/models/create-variant-dto.ts +26 -32
  44. package/src/lib/Apis/models/discount.ts +0 -8
  45. package/src/lib/Apis/models/index.ts +16 -7
  46. package/src/lib/Apis/models/paginated-products-dto.ts +6 -6
  47. package/src/lib/Apis/models/populated-discount.ts +0 -8
  48. package/src/lib/Apis/models/product-summary.ts +69 -0
  49. package/src/lib/Apis/models/product-variant.ts +31 -68
  50. package/src/lib/Apis/models/product.ts +138 -0
  51. package/src/lib/Apis/models/products-insights-dto.ts +12 -0
  52. package/src/lib/Apis/models/reorder-categories-dto.ts +27 -0
  53. package/src/lib/Apis/models/reorder-products-dto.ts +49 -0
  54. package/src/lib/Apis/models/{table-dto.ts → reorder-products-success-response-dto.ts} +8 -9
  55. package/src/lib/Apis/models/reorder-subcategories-dto.ts +33 -0
  56. package/src/lib/Apis/models/{shallow-parent-category-dto.ts → reorder-success-response-dto.ts} +7 -7
  57. package/src/lib/Apis/models/shipment-with-order.ts +18 -0
  58. package/src/lib/Apis/models/shipment.ts +18 -0
  59. package/src/lib/Apis/models/single-product-media.ts +0 -12
  60. package/src/lib/Apis/models/store-api-keys-response-dto.ts +34 -0
  61. package/src/lib/Apis/models/store-capabilities-dto.ts +63 -0
  62. package/src/lib/Apis/models/store-entity.ts +7 -0
  63. package/src/lib/Apis/models/store.ts +7 -0
  64. package/src/lib/Apis/models/sub-category.ts +6 -12
  65. package/src/lib/Apis/models/update-address-dto.ts +0 -12
  66. package/src/lib/Apis/models/update-api-keys-dto.ts +39 -0
  67. package/src/lib/Apis/models/update-discount-dto.ts +0 -8
  68. package/src/lib/Apis/models/update-manual-shipment-status-dto.ts +47 -0
  69. package/src/lib/Apis/models/update-product-dto.ts +30 -19
  70. package/src/lib/Apis/models/update-store-dto.ts +7 -0
  71. package/src/lib/Apis/models/update-sub-category-dto.ts +6 -0
  72. package/src/lib/Apis/models/{update-product-variant-dto.ts → update-variant-dto.ts} +46 -46
  73. package/src/lib/Apis/models/variant-id-inventory-body.ts +27 -0
  74. package/src/lib/api-adapter/config.ts +53 -0
  75. package/src/lib/validations/address.ts +1 -1
  76. package/src/providers/FavoritesProvider.tsx +5 -5
  77. package/src/providers/WishlistProvider.tsx +4 -4
  78. package/src/screens/CartScreen.tsx +1 -1
  79. package/src/screens/ChangePasswordScreen.tsx +2 -6
  80. package/src/screens/CheckoutScreen.tsx +402 -288
  81. package/src/screens/ForgotPasswordScreen.tsx +153 -0
  82. package/src/screens/ProductDetailScreen.tsx +51 -60
  83. package/src/screens/RegisterScreen.tsx +31 -31
  84. package/src/screens/ResetPasswordScreen.tsx +208 -0
  85. package/src/screens/SearchResultsScreen.tsx +264 -26
  86. package/src/screens/ShopScreen.tsx +42 -45
  87. package/src/screens/WishlistScreen.tsx +35 -31
  88. package/src/lib/Apis/apis/product-variants-api.ts +0 -552
  89. package/src/lib/Apis/models/create-single-variant-product-dto.ts +0 -154
  90. package/src/lib/Apis/models/extended-product-dto.ts +0 -206
  91. package/src/lib/Apis/models/frequently-bought-product-dto.ts +0 -71
@@ -0,0 +1,208 @@
1
+ 'use client';
2
+
3
+ import React, { useState, useEffect } from 'react';
4
+ import { motion } from 'framer-motion';
5
+ import { useForm } from 'react-hook-form';
6
+ import { zodResolver } from '@hookform/resolvers/zod';
7
+ import { z } from 'zod';
8
+ import { useRouter, useParams } from 'next/navigation';
9
+ import Link from 'next/link';
10
+ import { Eye, EyeOff, Lock, ShieldCheck } from 'lucide-react';
11
+ import { Input } from '@/components/ui/Input';
12
+ import { Button } from '@/components/ui/Button';
13
+ import { AuthApi } from '@/lib/Apis/apis/auth-api';
14
+ import { AXIOS_CONFIG } from '@/lib/Apis/sharedConfig';
15
+ import { useBasePath } from '@/providers/BasePathProvider';
16
+
17
+ const resetPasswordSchema = z
18
+ .object({
19
+ newPassword: z.string().min(8, 'Password must be at least 8 characters'),
20
+ confirmPassword: z.string().min(8, 'Confirm your new password'),
21
+ })
22
+ .refine((data) => data.newPassword === data.confirmPassword, {
23
+ path: ['confirmPassword'],
24
+ message: 'Passwords do not match',
25
+ });
26
+
27
+ type ResetPasswordFormData = z.infer<typeof resetPasswordSchema>;
28
+
29
+ export function ResetPasswordScreen() {
30
+ const router = useRouter();
31
+ const { buildPath } = useBasePath();
32
+ const params = useParams();
33
+ const token = params?.token as string | undefined;
34
+ const storeId = params?.storeId as string | undefined;
35
+ const [showPassword, setShowPassword] = useState(false);
36
+ const [showConfirmPassword, setShowConfirmPassword] = useState(false);
37
+ const [isSubmitting, setIsSubmitting] = useState(false);
38
+ const [status, setStatus] = useState<{ type: 'success' | 'error'; message: string } | null>(
39
+ null
40
+ );
41
+
42
+ const {
43
+ register,
44
+ handleSubmit,
45
+ formState: { errors },
46
+ } = useForm<ResetPasswordFormData>({
47
+ resolver: zodResolver(resetPasswordSchema),
48
+ });
49
+
50
+ useEffect(() => {
51
+ if (!token) {
52
+ setStatus({
53
+ type: 'error',
54
+ message: 'Invalid or missing reset token. Please request a new password reset link.',
55
+ });
56
+ }
57
+ }, [token]);
58
+
59
+ const onSubmit = async (data: ResetPasswordFormData) => {
60
+ if (!token) {
61
+ setStatus({
62
+ type: 'error',
63
+ message: 'Reset token is missing. Please use the link from your email.',
64
+ });
65
+ return;
66
+ }
67
+
68
+ setIsSubmitting(true);
69
+ setStatus(null);
70
+ try {
71
+ const authApi = new AuthApi(AXIOS_CONFIG);
72
+ if (!storeId) {
73
+ setStatus({
74
+ type: 'error',
75
+ message: 'Store ID is missing. Please use the link from your email.',
76
+ });
77
+ return;
78
+ }
79
+ await authApi.resetPassword(data.newPassword, token, storeId);
80
+ setStatus({
81
+ type: 'success',
82
+ message: 'Password reset successfully! Redirecting to login...',
83
+ });
84
+ setTimeout(() => router.push(buildPath('/login')), 2000);
85
+ } catch (error: any) {
86
+ setStatus({
87
+ type: 'error',
88
+ message:
89
+ error?.response?.data?.message ||
90
+ 'Unable to reset password. The link may have expired.',
91
+ });
92
+ } finally {
93
+ setIsSubmitting(false);
94
+ }
95
+ };
96
+
97
+ return (
98
+ <div className="min-h-screen bg-linear-to-b from-[#F8FAFC] to-[#EBF4FB]">
99
+ <div className="grid min-h-screen overflow-hidden pb-12">
100
+ <motion.section
101
+ initial={{ opacity: 0, x: 24 }}
102
+ animate={{ opacity: 1, x: 0 }}
103
+ transition={{ duration: 0.4 }}
104
+ className="flex items-center justify-center px-6 py-12 lg:px-16"
105
+ >
106
+ <div className="w-full max-w-lg space-y-10 text-center">
107
+ <div className="space-y-2">
108
+ <Lock
109
+ strokeWidth={2}
110
+ className="h-16 w-16 mx-auto text-white rounded-full bg-secondary m-2 mb-4 px-4"
111
+ />
112
+ <h2 className="text-4xl text-secondary">Reset Password</h2>
113
+ <p className="text-sm text-muted">
114
+ Enter your new password below to reset your account password.
115
+ </p>
116
+ </div>
117
+
118
+ <form
119
+ onSubmit={handleSubmit(onSubmit)}
120
+ className="space-y-6 rounded-3xl border bg-white p-8"
121
+ style={{
122
+ boxShadow: '0px 4px 6px -4px #0000001A, 0px 10px 15px -3px #0000001A',
123
+ }}
124
+ >
125
+ {status && (
126
+ <div
127
+ className={`flex items-start gap-2 rounded-2xl border px-4 py-3 text-sm ${status.type === 'success'
128
+ ? 'border-green-200 bg-green-50 text-green-800'
129
+ : 'border-red-200 bg-red-50 text-red-700'
130
+ }`}
131
+ >
132
+ <span className="mt-[2px] text-base">{status.type === 'success' ? '✔' : '!'}</span>
133
+ <span>{status.message}</span>
134
+ </div>
135
+ )}
136
+
137
+ <div className="relative text-start text-secondary">
138
+ <h2 className="text-sm text-secondary mb-3">
139
+ New Password <span className="text-primary-500">*</span>
140
+ </h2>
141
+ <Input
142
+ type={showPassword ? 'text' : 'password'}
143
+ placeholder="Enter new password (min. 8 characters)"
144
+ {...register('newPassword')}
145
+ error={errors.newPassword?.message}
146
+ className="text-secondary"
147
+ />
148
+ <button
149
+ type="button"
150
+ onClick={() => setShowPassword((prev) => !prev)}
151
+ className="absolute right-3 top-[42px] text-slate-400 transition hover:text-slate-600"
152
+ >
153
+ {showPassword ? <EyeOff className="h-5 w-5" /> : <Eye className="h-5 w-5" />}
154
+ </button>
155
+ </div>
156
+
157
+ <div className="relative text-start text-secondary">
158
+ <h2 className="text-sm text-secondary mb-3">
159
+ Confirm Password <span className="text-primary-500">*</span>
160
+ </h2>
161
+ <Input
162
+ type={showConfirmPassword ? 'text' : 'password'}
163
+ placeholder="Re-enter new password"
164
+ {...register('confirmPassword')}
165
+ error={errors.confirmPassword?.message}
166
+ className="text-secondary"
167
+ />
168
+ <button
169
+ type="button"
170
+ onClick={() => setShowConfirmPassword((prev) => !prev)}
171
+ className="absolute right-3 top-[42px] text-slate-400 transition hover:text-slate-600"
172
+ >
173
+ {showConfirmPassword ? (
174
+ <EyeOff className="h-5 w-5" />
175
+ ) : (
176
+ <Eye className="h-5 w-5" />
177
+ )}
178
+ </button>
179
+ </div>
180
+
181
+ <button
182
+ type="submit"
183
+ disabled={isSubmitting || !token}
184
+ className="w-full bg-secondary hover:opacity-80 text-white font-medium py-3 px-4 rounded-lg transition-colors disabled:opacity-70 disabled:cursor-not-allowed"
185
+ >
186
+ {isSubmitting ? 'Resetting Password...' : 'Reset Password'}
187
+ </button>
188
+
189
+ <div className="flex items-center gap-2 rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3 text-sm text-slate-600">
190
+ <ShieldCheck className="h-4 w-4 text-primary-600" />
191
+ Use a strong password that you haven't used elsewhere.
192
+ </div>
193
+
194
+ <div className="pt-4 border-t border-slate-200">
195
+ <Link
196
+ href={buildPath('/login')}
197
+ className="text-sm font-medium text-primary transition hover:opacity-80"
198
+ >
199
+ Back to login
200
+ </Link>
201
+ </div>
202
+ </form>
203
+ </div>
204
+ </motion.section>
205
+ </div>
206
+ </div>
207
+ );
208
+ }
@@ -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>