hey-pharmacist-ecommerce 1.0.5 → 1.0.7

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 (74) hide show
  1. package/README.md +157 -17
  2. package/dist/index.d.mts +3636 -316
  3. package/dist/index.d.ts +3636 -316
  4. package/dist/index.js +6802 -3866
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +6756 -3818
  7. package/dist/index.mjs.map +1 -1
  8. package/package.json +18 -15
  9. package/src/components/AddressFormModal.tsx +171 -0
  10. package/src/components/CartItem.tsx +17 -12
  11. package/src/components/FilterChips.tsx +195 -0
  12. package/src/components/Header.tsx +121 -71
  13. package/src/components/OrderCard.tsx +18 -25
  14. package/src/components/ProductCard.tsx +209 -72
  15. package/src/components/ui/Button.tsx +13 -5
  16. package/src/components/ui/Card.tsx +46 -0
  17. package/src/hooks/useAddresses.ts +83 -0
  18. package/src/hooks/useOrders.ts +37 -19
  19. package/src/hooks/useProducts.ts +55 -63
  20. package/src/hooks/useWishlistProducts.ts +75 -0
  21. package/src/index.ts +3 -19
  22. package/src/lib/Apis/api.ts +1 -0
  23. package/src/lib/Apis/apis/cart-api.ts +3 -3
  24. package/src/lib/Apis/apis/inventory-api.ts +0 -108
  25. package/src/lib/Apis/apis/stores-api.ts +70 -0
  26. package/src/lib/Apis/apis/wishlist-api.ts +447 -0
  27. package/src/lib/Apis/models/cart-item-populated.ts +0 -1
  28. package/src/lib/Apis/models/create-single-variant-product-dto.ts +3 -10
  29. package/src/lib/Apis/models/create-variant-dto.ts +26 -33
  30. package/src/lib/Apis/models/extended-product-dto.ts +20 -24
  31. package/src/lib/Apis/models/index.ts +2 -1
  32. package/src/lib/Apis/models/order-time-line-dto.ts +49 -0
  33. package/src/lib/Apis/models/order.ts +3 -8
  34. package/src/lib/Apis/models/populated-order.ts +3 -8
  35. package/src/lib/Apis/models/product-variant.ts +29 -0
  36. package/src/lib/Apis/models/update-product-variant-dto.ts +16 -23
  37. package/src/lib/Apis/models/wishlist.ts +51 -0
  38. package/src/lib/Apis/wrapper.ts +18 -7
  39. package/src/lib/api-adapter/index.ts +0 -12
  40. package/src/lib/types/index.ts +16 -61
  41. package/src/lib/utils/colors.ts +7 -4
  42. package/src/lib/utils/format.ts +1 -1
  43. package/src/lib/validations/address.ts +14 -0
  44. package/src/providers/AuthProvider.tsx +61 -31
  45. package/src/providers/CartProvider.tsx +18 -28
  46. package/src/providers/EcommerceProvider.tsx +7 -0
  47. package/src/providers/FavoritesProvider.tsx +86 -0
  48. package/src/providers/ThemeProvider.tsx +16 -1
  49. package/src/providers/WishlistProvider.tsx +174 -0
  50. package/src/screens/AddressesScreen.tsx +484 -0
  51. package/src/screens/CartScreen.tsx +120 -84
  52. package/src/screens/CategoriesScreen.tsx +120 -0
  53. package/src/screens/CheckoutScreen.tsx +919 -241
  54. package/src/screens/CurrentOrdersScreen.tsx +125 -61
  55. package/src/screens/HomeScreen.tsx +209 -0
  56. package/src/screens/LoginScreen.tsx +133 -88
  57. package/src/screens/NewAddressScreen.tsx +187 -0
  58. package/src/screens/OrdersScreen.tsx +162 -50
  59. package/src/screens/ProductDetailScreen.tsx +641 -190
  60. package/src/screens/ProfileScreen.tsx +192 -116
  61. package/src/screens/RegisterScreen.tsx +193 -144
  62. package/src/screens/SearchResultsScreen.tsx +165 -0
  63. package/src/screens/ShopScreen.tsx +1110 -146
  64. package/src/screens/WishlistScreen.tsx +428 -0
  65. package/src/lib/Apis/models/inventory-paginated-response.ts +0 -75
  66. package/src/lib/api/auth.ts +0 -81
  67. package/src/lib/api/cart.ts +0 -42
  68. package/src/lib/api/orders.ts +0 -53
  69. package/src/lib/api/products.ts +0 -51
  70. package/src/lib/api-adapter/auth-adapter.ts +0 -196
  71. package/src/lib/api-adapter/cart-adapter.ts +0 -193
  72. package/src/lib/api-adapter/mappers.ts +0 -152
  73. package/src/lib/api-adapter/orders-adapter.ts +0 -195
  74. package/src/lib/api-adapter/products-adapter.ts +0 -194
@@ -5,28 +5,45 @@ import { motion } from 'framer-motion';
5
5
  import { useForm } from 'react-hook-form';
6
6
  import { zodResolver } from '@hookform/resolvers/zod';
7
7
  import { z } from 'zod';
8
- import { UserPlus, Eye, EyeOff } from 'lucide-react';
9
- import { Button } from '@/components/ui/Button';
8
+ import Link from 'next/link';
9
+ import { useRouter } from 'next/navigation';
10
+ import {
11
+ CheckCircle2,
12
+ Eye,
13
+ EyeOff,
14
+ Heart,
15
+ HeartPulse,
16
+ Shield,
17
+ UserPlus,
18
+ } from 'lucide-react';
10
19
  import { Input } from '@/components/ui/Input';
20
+ import { Button } from '@/components/ui/Button';
11
21
  import { useAuth } from '@/providers/AuthProvider';
12
- import { useRouter } from 'next/navigation';
13
22
  import { toast } from 'sonner';
14
- import Link from 'next/link';
15
-
16
- const registerSchema = z.object({
17
- firstName: z.string().min(2, 'First name is required'),
18
- lastName: z.string().min(2, 'Last name is required'),
19
- email: z.string().email('Invalid email address'),
20
- phone: z.string().optional(),
21
- password: z.string().min(6, 'Password must be at least 6 characters'),
22
- confirmPassword: z.string(),
23
- }).refine((data) => data.password === data.confirmPassword, {
24
- message: "Passwords don't match",
25
- path: ['confirmPassword'],
26
- });
23
+ import { CreateUserDtoCustomerTypeEnum, CreateUserDtoRoleEnum } from '@/lib/Apis/models';
24
+
25
+ const registerSchema = z
26
+ .object({
27
+ firstName: z.string().min(2, 'First name is required'),
28
+ lastName: z.string().min(2, 'Last name is required'),
29
+ email: z.string().email('Enter a valid email'),
30
+ phone: z.string().optional(),
31
+ password: z.string().min(6, 'Password must be at least 6 characters'),
32
+ confirmPassword: z.string(),
33
+ })
34
+ .refine((data) => data.password === data.confirmPassword, {
35
+ message: "Passwords don't match",
36
+ path: ['confirmPassword'],
37
+ });
27
38
 
28
39
  type RegisterFormData = z.infer<typeof registerSchema>;
29
40
 
41
+ const BENEFITS = [
42
+ 'Personalized care plans from licensed pharmacists',
43
+ 'Express delivery with cold-chain handling',
44
+ 'Automatic refills and refill reminders',
45
+ ];
46
+
30
47
  export function RegisterScreen() {
31
48
  const router = useRouter();
32
49
  const { register: registerUser } = useAuth();
@@ -46,7 +63,12 @@ export function RegisterScreen() {
46
63
  setIsSubmitting(true);
47
64
  try {
48
65
  const { confirmPassword, ...registerData } = data;
49
- await registerUser(registerData);
66
+ await registerUser({
67
+ ...registerData,
68
+ addresses: [],
69
+ customerType: CreateUserDtoCustomerTypeEnum.Regular,
70
+ role: CreateUserDtoRoleEnum.User,
71
+ });
50
72
  toast.success('Account created successfully!');
51
73
  router.push('/');
52
74
  } catch (error: any) {
@@ -57,144 +79,171 @@ export function RegisterScreen() {
57
79
  };
58
80
 
59
81
  return (
60
- <div className="min-h-screen bg-gradient-to-br from-primary-600 via-primary-700 to-secondary-600 flex items-center justify-center p-4">
61
- <motion.div
62
- initial={{ opacity: 0, y: 20 }}
63
- animate={{ opacity: 1, y: 0 }}
64
- className="w-full max-w-md"
65
- >
66
- <div className="bg-white rounded-3xl shadow-2xl p-8">
67
- {/* Header */}
68
- <div className="text-center mb-8">
69
- <div className="w-16 h-16 bg-primary-100 rounded-full flex items-center justify-center mx-auto mb-4">
70
- <UserPlus className="w-8 h-8 text-primary-600" />
71
- </div>
72
- <h1 className="text-3xl font-bold text-gray-900 mb-2">Create Account</h1>
73
- <p className="text-gray-600">Join us and start shopping</p>
82
+ <div className="min-h-screen bg-slate-50">
83
+ <div className="grid min-h-screen overflow-hidden bg-white lg:grid-cols-[0.95fr_1.05fr]">
84
+ <motion.section
85
+ initial={{ opacity: 0, x: -24 }}
86
+ animate={{ opacity: 1, x: 0 }}
87
+ transition={{ duration: 0.4 }}
88
+ className="flex flex-col justify-between bg-gradient-to-br from-[rgb(var(--header-from))] via-[rgb(var(--header-via))] to-[rgb(var(--header-to))] px-10 py-14 text-white"
89
+ >
90
+ <div className="space-y-6">
91
+ <span className="inline-flex items-center gap-2 rounded-full bg-white/15 px-3 py-1 text-sm font-semibold uppercase tracking-[0.35em] text-white/70 backdrop-blur">
92
+ <HeartPulse className="h-4 w-4" />
93
+ Join Hey Pharmacist
94
+ </span>
95
+ <h1 className="text-4xl font-bold leading-tight lg:text-5xl">
96
+ Create your wellness account
97
+ </h1>
98
+ <p className="max-w-xl text-white/80">
99
+ Unlock concierge-level pharmacy support, curated product recommendations, and smarter
100
+ refills designed for busy families.
101
+ </p>
74
102
  </div>
75
103
 
76
- {/* Form */}
77
- <form onSubmit={handleSubmit(onSubmit)} className="space-y-5">
78
- <div className="grid grid-cols-2 gap-4">
79
- <Input
80
- label="First Name"
81
- placeholder="John"
82
- {...register('firstName')}
83
- error={errors.firstName?.message}
84
- />
85
- <Input
86
- label="Last Name"
87
- placeholder="Doe"
88
- {...register('lastName')}
89
- error={errors.lastName?.message}
90
- />
91
- </div>
92
-
93
- <Input
94
- type="email"
95
- label="Email Address"
96
- placeholder="you@example.com"
97
- {...register('email')}
98
- error={errors.email?.message}
99
- />
104
+ <div className="space-y-4 rounded-3xl bg-white/10 p-6 backdrop-blur">
105
+ {BENEFITS.map((benefit) => (
106
+ <div key={benefit} className="flex items-center gap-3">
107
+ <CheckCircle2 className="h-5 w-5 text-white" />
108
+ <p className="text-sm text-white/85">{benefit}</p>
109
+ </div>
110
+ ))}
111
+ </div>
100
112
 
101
- <Input
102
- type="tel"
103
- label="Phone Number (Optional)"
104
- placeholder="+1 (555) 000-0000"
105
- {...register('phone')}
106
- error={errors.phone?.message}
107
- />
113
+ <div className="flex items-center gap-6 text-sm text-white/80">
114
+ <span>Already part of the community?</span>
115
+ <Link
116
+ href="/login"
117
+ className="inline-flex items-center gap-2 rounded-full bg-white/15 px-4 py-2 font-semibold transition hover:bg-white/25"
118
+ >
119
+ Sign in
120
+ </Link>
121
+ </div>
122
+ </motion.section>
123
+
124
+ <motion.section
125
+ initial={{ opacity: 0, x: 24 }}
126
+ animate={{ opacity: 1, x: 0 }}
127
+ transition={{ duration: 0.4 }}
128
+ className="flex items-center justify-center px-6 py-12 lg:px-16"
129
+ >
130
+ <div className="w-full max-w-lg space-y-10">
131
+ <div className="space-y-3">
132
+ <h2 className="text-3xl font-bold text-slate-900">Create an account</h2>
133
+ <p className="text-sm text-slate-500">
134
+ Start your personalized care journey with pharmacist-backed guidance.
135
+ </p>
136
+ </div>
108
137
 
109
- <div className="relative">
138
+ <form
139
+ onSubmit={handleSubmit(onSubmit)}
140
+ className="space-y-6 rounded-3xl border border-slate-100 bg-white p-8 shadow-lg shadow-primary-50"
141
+ >
142
+ <div className="grid gap-4 md:grid-cols-2">
143
+ <Input
144
+ label="First name"
145
+ placeholder="Alex"
146
+ {...register('firstName')}
147
+ error={errors.firstName?.message}
148
+ />
149
+ <Input
150
+ label="Last name"
151
+ placeholder="Morgan"
152
+ {...register('lastName')}
153
+ error={errors.lastName?.message}
154
+ />
155
+ </div>
110
156
  <Input
111
- type={showPassword ? 'text' : 'password'}
112
- label="Password"
113
- placeholder="••••••••"
114
- {...register('password')}
115
- error={errors.password?.message}
157
+ type="email"
158
+ label="Email address"
159
+ placeholder="you@example.com"
160
+ {...register('email')}
161
+ error={errors.email?.message}
116
162
  />
117
- <button
118
- type="button"
119
- onClick={() => setShowPassword(!showPassword)}
120
- className="absolute right-3 top-[42px] text-gray-500 hover:text-gray-700"
121
- >
122
- {showPassword ? (
123
- <EyeOff className="w-5 h-5" />
124
- ) : (
125
- <Eye className="w-5 h-5" />
126
- )}
127
- </button>
128
- </div>
129
-
130
- <div className="relative">
131
163
  <Input
132
- type={showConfirmPassword ? 'text' : 'password'}
133
- label="Confirm Password"
134
- placeholder="••••••••"
135
- {...register('confirmPassword')}
136
- error={errors.confirmPassword?.message}
164
+ type="tel"
165
+ label="Phone number (optional)"
166
+ placeholder="+1 (555) 123-4567"
167
+ {...register('phone')}
168
+ error={errors.phone?.message}
137
169
  />
138
- <button
139
- type="button"
140
- onClick={() => setShowConfirmPassword(!showConfirmPassword)}
141
- className="absolute right-3 top-[42px] text-gray-500 hover:text-gray-700"
170
+ <div className="relative">
171
+ <Input
172
+ type={showPassword ? 'text' : 'password'}
173
+ label="Password"
174
+ placeholder="••••••••"
175
+ {...register('password')}
176
+ error={errors.password?.message}
177
+ />
178
+ <button
179
+ type="button"
180
+ onClick={() => setShowPassword((prev) => !prev)}
181
+ className="absolute right-3 top-[42px] text-slate-400 transition hover:text-slate-600"
182
+ >
183
+ {showPassword ? <EyeOff className="h-5 w-5" /> : <Eye className="h-5 w-5" />}
184
+ </button>
185
+ </div>
186
+ <div className="relative">
187
+ <Input
188
+ type={showConfirmPassword ? 'text' : 'password'}
189
+ label="Confirm password"
190
+ placeholder="••••••••"
191
+ {...register('confirmPassword')}
192
+ error={errors.confirmPassword?.message}
193
+ />
194
+ <button
195
+ type="button"
196
+ onClick={() => setShowConfirmPassword((prev) => !prev)}
197
+ className="absolute right-3 top-[42px] text-slate-400 transition hover:text-slate-600"
198
+ >
199
+ {showConfirmPassword ? <EyeOff className="h-5 w-5" /> : <Eye className="h-5 w-5" />}
200
+ </button>
201
+ </div>
202
+
203
+ <div className="flex items-start gap-3 rounded-2xl bg-slate-50 p-4 text-sm text-slate-600">
204
+ <Shield className="mt-0.5 h-5 w-5 text-primary-500" />
205
+ <span>
206
+ By creating an account, you agree to our{' '}
207
+ <Link href="/terms" className="font-semibold text-primary-600 hover:text-primary-700">
208
+ Terms of Service
209
+ </Link>{' '}
210
+ and{' '}
211
+ <Link
212
+ href="/privacy"
213
+ className="font-semibold text-primary-600 hover:text-primary-700"
214
+ >
215
+ Privacy Policy
216
+ </Link>
217
+ . We protect your information with bank-level encryption.
218
+ </span>
219
+ </div>
220
+
221
+ <Button
222
+ type="submit"
223
+ size="lg"
224
+ isLoading={isSubmitting}
225
+ className="w-full"
142
226
  >
143
- {showConfirmPassword ? (
144
- <EyeOff className="w-5 h-5" />
145
- ) : (
146
- <Eye className="w-5 h-5" />
147
- )}
148
- </button>
149
- </div>
150
-
151
- <div className="flex items-start gap-2">
152
- <input
153
- type="checkbox"
154
- required
155
- className="w-4 h-4 text-primary-600 rounded mt-1"
156
- />
157
- <label className="text-sm text-gray-700">
158
- I agree to the{' '}
159
- <Link href="/terms" className="text-primary-600 hover:text-primary-700">
160
- Terms of Service
161
- </Link>{' '}
162
- and{' '}
163
- <Link href="/privacy" className="text-primary-600 hover:text-primary-700">
164
- Privacy Policy
165
- </Link>
166
- </label>
167
- </div>
168
-
169
- <Button
170
- type="submit"
171
- size="lg"
172
- isLoading={isSubmitting}
173
- className="w-full"
174
- >
175
- Create Account
176
- </Button>
177
- </form>
178
-
179
- {/* Divider */}
180
- <div className="relative my-8">
181
- <div className="absolute inset-0 flex items-center">
182
- <div className="w-full border-t border-gray-200"></div>
183
- </div>
184
- <div className="relative flex justify-center text-sm">
185
- <span className="px-4 bg-white text-gray-500">Already have an account?</span>
227
+ <UserPlus className="h-5 w-5" />
228
+ Create my account
229
+ </Button>
230
+ </form>
231
+
232
+ <div className="rounded-3xl border border-slate-100 bg-slate-50 p-6 text-sm text-slate-600">
233
+ <div className="flex items-start gap-3">
234
+ <Heart className="mt-0.5 h-5 w-5 text-primary-500" />
235
+ <div>
236
+ <p className="font-semibold text-slate-800">Members love us</p>
237
+ <p>
238
+ 92% of patients report improved adherence after receiving pharmacist touchpoints
239
+ through their Hey Pharmacist account.
240
+ </p>
241
+ </div>
242
+ </div>
186
243
  </div>
187
244
  </div>
188
-
189
- {/* Login Link */}
190
- <Link href="/login">
191
- <Button variant="outline" size="lg" className="w-full">
192
- Sign In
193
- </Button>
194
- </Link>
195
- </div>
196
- </motion.div>
245
+ </motion.section>
246
+ </div>
197
247
  </div>
198
248
  );
199
249
  }
200
-
@@ -0,0 +1,165 @@
1
+ 'use client';
2
+
3
+ import { useEffect, useState } from 'react';
4
+ import { useSearchParams } from 'next/navigation';
5
+ import { ProductsApi } from '@/lib/Apis';
6
+ import { ExtendedProductDTO } from '@/lib/Apis';
7
+ import { ProductCard } from '@/components/ProductCard';
8
+ import { Skeleton } from '@/components/ui/Skeleton';
9
+ import { Input } from '@/components/ui/Input';
10
+ import { Search, X } from 'lucide-react';
11
+ import Link from 'next/link';
12
+ import { AXIOS_CONFIG } from '@/lib/Apis/wrapper';
13
+ import { useWishlist } from '@/providers/WishlistProvider';
14
+ import { useRouter } from 'next/navigation';
15
+
16
+ export default function SearchPage() {
17
+ const router = useRouter();
18
+ const searchParams = useSearchParams();
19
+ const searchQuery = searchParams.get('q') || '';
20
+ const [products, setProducts] = useState<ExtendedProductDTO[]>([]);
21
+ const [isLoading, setIsLoading] = useState(true);
22
+ const [searchInput, setSearchInput] = useState(searchQuery);
23
+ const [hasSearched, setHasSearched] = useState(false);
24
+ const { isInWishlist } = useWishlist();
25
+
26
+ useEffect(() => {
27
+ const fetchSearchResults = async () => {
28
+ if (!searchQuery.trim()) {
29
+ setProducts([]);
30
+ setIsLoading(false);
31
+ return;
32
+ }
33
+
34
+ try {
35
+ setIsLoading(true);
36
+ console.log(searchQuery);
37
+ const api = new ProductsApi(AXIOS_CONFIG);
38
+ const response = await api.getAllProductsForStore(
39
+ searchQuery,
40
+ undefined, // productType
41
+ undefined, // categoryId
42
+ undefined, // maxPrice
43
+ undefined, // minPrice
44
+ undefined, // brandFilter
45
+ 'in-stock', // availability
46
+ 'relevance', // sort
47
+ true, // includeNoVariantProducts
48
+ true, // isActive
49
+ 20, // limit
50
+ 1 // page
51
+ );
52
+
53
+ if (response.data?.data) {
54
+ const transformedProducts = response.data.data.map((item: ExtendedProductDTO) => ({
55
+ ...item,
56
+ id: item._id || '',
57
+ }));
58
+
59
+ setProducts(transformedProducts);
60
+ } else {
61
+ setProducts([]);
62
+ }
63
+ setHasSearched(true);
64
+ } catch (error) {
65
+ console.error('Error fetching search results:', error);
66
+ setProducts([]);
67
+ } finally {
68
+ setIsLoading(false);
69
+ }
70
+ };
71
+
72
+ fetchSearchResults();
73
+ }, [searchQuery]);
74
+
75
+ const handleSearch = (e: React.FormEvent) => {
76
+ e.preventDefault();
77
+ if (searchInput.trim()) {
78
+ window.location.href = `/search?q=${encodeURIComponent(searchInput.trim())}`;
79
+ }
80
+ };
81
+
82
+ const clearSearch = () => {
83
+ setSearchInput('');
84
+ };
85
+
86
+ return (
87
+ <div className="container mx-auto px-4 py-8">
88
+ <div className="max-w-2xl mx-auto mb-8">
89
+ <form onSubmit={handleSearch} className="relative">
90
+ <div className="relative">
91
+ <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-5 w-5 text-gray-400" />
92
+ <Input
93
+ type="text"
94
+ value={searchInput}
95
+ onChange={(e) => setSearchInput(e.target.value)}
96
+ placeholder="Search for products..."
97
+ 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"
98
+ />
99
+ {searchInput && (
100
+ <button
101
+ type="button"
102
+ onClick={clearSearch}
103
+ className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-gray-600"
104
+ >
105
+ <X className="h-5 w-5" />
106
+ </button>
107
+ )}
108
+ </div>
109
+ <button
110
+ type="submit"
111
+ className="mt-4 w-full bg-primary-600 hover:bg-primary-700 text-white font-medium py-2 px-4 rounded-lg transition-colors"
112
+ >
113
+ Search
114
+ </button>
115
+ </form>
116
+ </div>
117
+
118
+ <div>
119
+ <h1 className="text-2xl font-bold text-gray-900 mb-6">
120
+ {searchQuery ? `Search results for "${searchQuery}"` : 'Search Products'}
121
+ </h1>
122
+
123
+ {isLoading ? (
124
+ <div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
125
+ {[...Array(8)].map((_, i) => (
126
+ <div key={i} className="space-y-3">
127
+ <Skeleton className="h-48 w-full rounded-lg" />
128
+ <Skeleton className="h-4 w-3/4" />
129
+ <Skeleton className="h-4 w-1/2" />
130
+ </div>
131
+ ))}
132
+ </div>
133
+ ) : products.length > 0 ? (
134
+ <div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
135
+ {products.map((product) => (
136
+ <ProductCard
137
+ product={product}
138
+ isFavorited={isInWishlist(product.id)}
139
+ onClickProduct={(p) => router.push(`/products/${p.id}`)}
140
+ />
141
+ ))}
142
+ </div>
143
+ ) : hasSearched ? (
144
+ <div className="text-center py-12">
145
+ <div className="text-gray-500 text-lg mb-4">
146
+ No products found for "{searchQuery}"
147
+ </div>
148
+ <p className="text-gray-500 mb-6">
149
+ Try different keywords or check out our{" "}
150
+ <Link href="/shop" className="text-primary-600 hover:underline ml-1 font-medium">
151
+ featured products
152
+ </Link>
153
+ </p>
154
+ </div>
155
+ ) : (
156
+ <div className="text-center py-12">
157
+ <p className="text-gray-500">
158
+ Enter a search term to find products
159
+ </p>
160
+ </div>
161
+ )}
162
+ </div>
163
+ </div>
164
+ );
165
+ }