hey-pharmacist-ecommerce 1.1.13 → 1.1.14
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.
- package/dist/index.d.mts +2 -4
- package/dist/index.d.ts +2 -4
- package/dist/index.js +1039 -857
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1039 -856
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/components/AccountAddressesTab.tsx +209 -0
- package/src/components/AccountOrdersTab.tsx +151 -0
- package/src/components/AccountOverviewTab.tsx +209 -0
- package/src/components/AccountPaymentTab.tsx +116 -0
- package/src/components/AccountSavedItemsTab.tsx +76 -0
- package/src/components/AccountSettingsTab.tsx +116 -0
- package/src/components/AddressFormModal.tsx +23 -10
- package/src/components/CartItem.tsx +60 -56
- package/src/components/Header.tsx +69 -16
- package/src/components/Notification.tsx +148 -0
- package/src/components/ProductCard.tsx +215 -178
- package/src/components/QuickViewModal.tsx +314 -0
- package/src/components/TabNavigation.tsx +48 -0
- package/src/components/ui/Button.tsx +1 -1
- package/src/components/ui/ConfirmModal.tsx +84 -0
- package/src/hooks/usePaymentMethods.ts +58 -0
- package/src/index.ts +0 -1
- package/src/providers/CartProvider.tsx +22 -6
- package/src/providers/EcommerceProvider.tsx +8 -7
- package/src/providers/FavoritesProvider.tsx +10 -3
- package/src/providers/NotificationProvider.tsx +79 -0
- package/src/providers/WishlistProvider.tsx +34 -9
- package/src/screens/AddressesScreen.tsx +72 -61
- package/src/screens/CartScreen.tsx +48 -32
- package/src/screens/ChangePasswordScreen.tsx +155 -0
- package/src/screens/CheckoutScreen.tsx +162 -125
- package/src/screens/EditProfileScreen.tsx +165 -0
- package/src/screens/LoginScreen.tsx +59 -72
- package/src/screens/NewAddressScreen.tsx +16 -10
- package/src/screens/ProductDetailScreen.tsx +334 -234
- package/src/screens/ProfileScreen.tsx +190 -200
- package/src/screens/RegisterScreen.tsx +51 -70
- package/src/screens/SearchResultsScreen.tsx +2 -1
- package/src/screens/ShopScreen.tsx +260 -384
- package/src/screens/WishlistScreen.tsx +226 -224
- package/src/styles/globals.css +9 -0
- package/src/screens/CategoriesScreen.tsx +0 -122
- package/src/screens/HomeScreen.tsx +0 -211
|
@@ -14,7 +14,6 @@ import {
|
|
|
14
14
|
Trash2,
|
|
15
15
|
} from 'lucide-react';
|
|
16
16
|
import { useRouter } from 'next/navigation';
|
|
17
|
-
import { toast } from 'sonner';
|
|
18
17
|
import { ProductCard } from '@/components/ProductCard';
|
|
19
18
|
import { Button } from '@/components/ui/Button';
|
|
20
19
|
import { useWishlist } from '@/providers/WishlistProvider';
|
|
@@ -24,6 +23,7 @@ import Image from 'next/image';
|
|
|
24
23
|
import { formatPrice } from '@/lib/utils/format';
|
|
25
24
|
import { ExtendedProductDTO } from '@/lib/Apis';
|
|
26
25
|
import { useBasePath } from '@/providers/BasePathProvider';
|
|
26
|
+
import { useNotification } from '@/providers/NotificationProvider';
|
|
27
27
|
|
|
28
28
|
type SortOption = 'featured' | 'price-low' | 'price-high' | 'name' | 'availability';
|
|
29
29
|
|
|
@@ -46,7 +46,7 @@ interface InsightCardProps {
|
|
|
46
46
|
|
|
47
47
|
export default function WishlistScreen() {
|
|
48
48
|
const router = useRouter();
|
|
49
|
-
|
|
49
|
+
const { buildPath } = useBasePath();
|
|
50
50
|
const { isAuthenticated } = useAuth() || {};
|
|
51
51
|
const {
|
|
52
52
|
products: wishlistItems,
|
|
@@ -55,6 +55,7 @@ export default function WishlistScreen() {
|
|
|
55
55
|
clearWishlist,
|
|
56
56
|
refreshWishlist,
|
|
57
57
|
} = useWishlist();
|
|
58
|
+
const notification = useNotification();
|
|
58
59
|
|
|
59
60
|
const wishlistCount = getWishlistCount?.() ?? 0;
|
|
60
61
|
const { products: wishlistProducts, isLoading, error } = useWishlistProducts(
|
|
@@ -67,9 +68,12 @@ export default function WishlistScreen() {
|
|
|
67
68
|
|
|
68
69
|
useEffect(() => {
|
|
69
70
|
if (error) {
|
|
70
|
-
|
|
71
|
+
notification.error(
|
|
72
|
+
'Could not load wishlist',
|
|
73
|
+
'We had trouble loading your saved products. Please refresh the page or try again shortly.'
|
|
74
|
+
);
|
|
71
75
|
}
|
|
72
|
-
}, [error]);
|
|
76
|
+
}, [error, notification]);
|
|
73
77
|
|
|
74
78
|
const handleRemoveFromWishlist = async (productId: string) => {
|
|
75
79
|
try {
|
|
@@ -156,19 +160,19 @@ export default function WishlistScreen() {
|
|
|
156
160
|
<div className="max-w-lg text-center space-y-6">
|
|
157
161
|
<div className="flex justify-center">
|
|
158
162
|
<div className="rounded-full bg-gray-100 p-6">
|
|
159
|
-
<Heart className="h-12 w-12 text-
|
|
163
|
+
<Heart className="h-12 w-12 text-secondary" />
|
|
160
164
|
</div>
|
|
161
165
|
</div>
|
|
162
166
|
<div className="space-y-2">
|
|
163
|
-
<h2 className="text-2xl font-bold text-
|
|
164
|
-
<p className="text-
|
|
167
|
+
<h2 className="text-2xl font-bold text-secondary">Sign in to see your favourites</h2>
|
|
168
|
+
<p className="text-muted">
|
|
165
169
|
Create your curated shelf of products and we'll keep them ready whenever you return.
|
|
166
170
|
</p>
|
|
167
171
|
</div>
|
|
168
172
|
<button
|
|
169
173
|
type="button"
|
|
170
174
|
onClick={() => router.push(buildPath('/login'))}
|
|
171
|
-
className="rounded-
|
|
175
|
+
className="rounded-xl border-2 border-primary bg-primary text-white px-6 py-3 text-sm font-medium transition-colors hover:opacity-80"
|
|
172
176
|
>
|
|
173
177
|
Sign In
|
|
174
178
|
</button>
|
|
@@ -179,8 +183,8 @@ export default function WishlistScreen() {
|
|
|
179
183
|
{isAuthenticated && (
|
|
180
184
|
<>
|
|
181
185
|
<div className="mb-6">
|
|
182
|
-
<h1 className="text-2xl font-bold text-
|
|
183
|
-
<p className="text-sm text-
|
|
186
|
+
<h1 className="text-2xl font-bold text-secondary">Wishlist</h1>
|
|
187
|
+
<p className="text-sm text-muted mt-1">
|
|
184
188
|
{wishlistCount} {wishlistCount === 1 ? 'item' : 'items'} saved
|
|
185
189
|
{wishlistCount > 0 && ` • Total value: ${formatPrice(totalValue)}`}
|
|
186
190
|
</p>
|
|
@@ -189,243 +193,241 @@ export default function WishlistScreen() {
|
|
|
189
193
|
<div className="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between mb-6">
|
|
190
194
|
<div className="space-y-1">
|
|
191
195
|
{onlyInStock && (
|
|
192
|
-
<p className="text-sm text-
|
|
196
|
+
<p className="text-sm text-muted">
|
|
193
197
|
Showing items ready to ship
|
|
194
198
|
</p>
|
|
195
199
|
)}
|
|
196
200
|
</div>
|
|
197
201
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
202
|
+
<div className="flex flex-wrap items-center gap-3">
|
|
203
|
+
<label className="inline-flex cursor-pointer items-center gap-2 rounded-full border border-slate-200 bg-slate-50 px-3 py-1.5 text-sm font-medium text-slate-600 transition hover:border-primary hover:text-secondary">
|
|
204
|
+
<input
|
|
205
|
+
type="checkbox"
|
|
206
|
+
checked={onlyInStock}
|
|
207
|
+
onChange={(event) => setOnlyInStock(event.target.checked)}
|
|
208
|
+
className="h-4 w-4 rounded border-slate-300 text-secondary focus:ring-secondary"
|
|
209
|
+
/>
|
|
210
|
+
Only show in-stock
|
|
211
|
+
</label>
|
|
208
212
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
213
|
+
<div className="flex items-center gap-2 rounded-full border border-slate-200 bg-slate-50 px-3 py-1.5 text-sm text-slate-600">
|
|
214
|
+
<span>Sort</span>
|
|
215
|
+
<select
|
|
216
|
+
value={sortOption}
|
|
217
|
+
onChange={(event) => setSortOption(event.target.value as SortOption)}
|
|
218
|
+
className="bg-transparent text-sm font-medium text-slate-700 outline-none"
|
|
219
|
+
>
|
|
220
|
+
{SORT_OPTIONS.map((option) => (
|
|
221
|
+
<option key={option.value} value={option.value}>
|
|
222
|
+
{option.label}
|
|
223
|
+
</option>
|
|
224
|
+
))}
|
|
225
|
+
</select>
|
|
226
|
+
</div>
|
|
223
227
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
228
|
+
<div className="flex overflow-hidden rounded-full border border-slate-200 bg-gray-100 p-1">
|
|
229
|
+
<button
|
|
230
|
+
type="button"
|
|
231
|
+
onClick={() => setViewMode('grid')}
|
|
232
|
+
className={`flex items-center gap-1 px-3 py-1.5 text-sm font-medium transition rounded-full ${viewMode === 'grid'
|
|
233
|
+
? 'bg-white text-primary shadow-md'
|
|
234
|
+
: 'text-slate-600 hover:bg-white'
|
|
235
|
+
}`}
|
|
236
|
+
>
|
|
237
|
+
<Grid className="h-4 w-4" />
|
|
238
|
+
</button>
|
|
239
|
+
<button
|
|
240
|
+
type="button"
|
|
241
|
+
onClick={() => setViewMode('list')}
|
|
242
|
+
className={`flex items-center gap-1 px-3 py-1.5 text-sm font-medium transition rounded-full ${viewMode === 'list'
|
|
243
|
+
? 'bg-white text-primary shadow-md'
|
|
244
|
+
: 'text-slate-600 hover:bg-white'
|
|
245
|
+
}`}
|
|
246
|
+
>
|
|
247
|
+
<List className="h-4 w-4" />
|
|
248
|
+
</button>
|
|
249
|
+
</div>
|
|
250
|
+
|
|
251
|
+
{wishlistCount > 0 && (
|
|
252
|
+
<Button
|
|
253
|
+
variant="ghost"
|
|
254
|
+
className="text-sm font-semibold text-slate-500 hover:text-red-500"
|
|
255
|
+
onClick={handleClearWishlist}
|
|
256
|
+
>
|
|
257
|
+
<Trash2 className="h-4 w-4" />
|
|
258
|
+
Clear all
|
|
259
|
+
</Button>
|
|
260
|
+
)}
|
|
261
|
+
</div>
|
|
262
|
+
</div>
|
|
263
|
+
|
|
264
|
+
{isLoading && (
|
|
265
|
+
<div className="grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-3">
|
|
266
|
+
{Array.from({ length: Math.min(wishlistCount || 3, 6) }).map((_, index) => (
|
|
267
|
+
<div
|
|
268
|
+
key={index}
|
|
269
|
+
className="h-72 animate-pulse rounded-2xl border border-slate-200 bg-slate-100"
|
|
270
|
+
/>
|
|
271
|
+
))}
|
|
272
|
+
</div>
|
|
273
|
+
)}
|
|
274
|
+
|
|
275
|
+
{!isLoading && wishlistCount === 0 && (
|
|
276
|
+
<div className="flex min-h-[30vh] items-center justify-center">
|
|
277
|
+
<div className="text-center space-y-6 max-w-md">
|
|
278
|
+
<div className="flex justify-center">
|
|
279
|
+
<div className="rounded-full bg-gray-100 p-6">
|
|
280
|
+
<Heart className="h-12 w-12 text-secondary" />
|
|
281
|
+
</div>
|
|
282
|
+
</div>
|
|
283
|
+
<div className="space-y-2">
|
|
284
|
+
<h2 className="text-2xl font-bold text-secondary">Your wishlist is empty</h2>
|
|
285
|
+
<p className="text-muted">
|
|
286
|
+
Start adding products to your wishlist to see them here.
|
|
287
|
+
</p>
|
|
288
|
+
</div>
|
|
289
|
+
<div className="flex flex-wrap justify-center gap-3">
|
|
236
290
|
<button
|
|
237
291
|
type="button"
|
|
238
|
-
onClick={() =>
|
|
239
|
-
className=
|
|
240
|
-
? 'bg-primary-600 text-white shadow-lg shadow-primary-500/30'
|
|
241
|
-
: 'text-slate-600 hover:bg-white'
|
|
242
|
-
}`}
|
|
292
|
+
onClick={() => router.push(buildPath('/shop'))}
|
|
293
|
+
className="rounded-xl border-2 border-primary bg-primary text-white px-6 py-3 text-sm font-medium transition-colors flex items-center justify-center gap-2 hover:opacity-80"
|
|
243
294
|
>
|
|
244
|
-
|
|
245
|
-
List
|
|
295
|
+
Discover products
|
|
246
296
|
</button>
|
|
247
297
|
</div>
|
|
248
|
-
|
|
249
|
-
{wishlistCount > 0 && (
|
|
250
|
-
<Button
|
|
251
|
-
variant="ghost"
|
|
252
|
-
className="text-sm font-semibold text-slate-500 hover:text-red-500"
|
|
253
|
-
onClick={handleClearWishlist}
|
|
254
|
-
>
|
|
255
|
-
<Trash2 className="h-4 w-4" />
|
|
256
|
-
Clear all
|
|
257
|
-
</Button>
|
|
258
|
-
)}
|
|
259
298
|
</div>
|
|
260
299
|
</div>
|
|
300
|
+
)}
|
|
261
301
|
|
|
262
|
-
{isLoading && (
|
|
263
|
-
<div className="grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-3">
|
|
264
|
-
{Array.from({ length: Math.min(wishlistCount || 3, 6) }).map((_, index) => (
|
|
265
|
-
<div
|
|
266
|
-
key={index}
|
|
267
|
-
className="h-72 animate-pulse rounded-2xl border border-slate-200 bg-slate-100"
|
|
268
|
-
/>
|
|
269
|
-
))}
|
|
270
|
-
</div>
|
|
271
|
-
)}
|
|
272
|
-
|
|
273
|
-
{!isLoading && wishlistCount === 0 && (
|
|
274
|
-
<div className="flex min-h-[30vh] items-center justify-center">
|
|
275
|
-
<div className="text-center space-y-6 max-w-md">
|
|
276
|
-
<div className="flex justify-center">
|
|
277
|
-
<div className="rounded-full bg-gray-100 p-6">
|
|
278
|
-
<Heart className="h-12 w-12 text-gray-400" />
|
|
279
|
-
</div>
|
|
280
|
-
</div>
|
|
281
|
-
<div className="space-y-2">
|
|
282
|
-
<h2 className="text-2xl font-bold text-slate-900">Your wishlist is empty</h2>
|
|
283
|
-
<p className="text-gray-500">
|
|
284
|
-
Start adding products to your wishlist to see them here.
|
|
285
|
-
</p>
|
|
286
|
-
</div>
|
|
287
|
-
<div className="flex flex-wrap justify-center gap-3">
|
|
288
|
-
<button
|
|
289
|
-
type="button"
|
|
290
|
-
onClick={() => router.push(buildPath('/shop'))}
|
|
291
|
-
className="rounded-full border-2 border-primary-500 bg-primary-500 hover:bg-primary-600 text-white px-6 py-3 text-sm font-medium transition-colors flex items-center justify-center gap-2"
|
|
292
|
-
>
|
|
293
|
-
Discover products
|
|
294
|
-
</button>
|
|
295
|
-
</div>
|
|
296
|
-
</div>
|
|
297
|
-
</div>
|
|
298
|
-
)}
|
|
299
|
-
|
|
300
302
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
/>
|
|
324
|
-
</motion.div>
|
|
325
|
-
))}
|
|
326
|
-
</AnimatePresence>
|
|
327
|
-
</motion.div>
|
|
328
|
-
) : (
|
|
329
|
-
<motion.div layout className="space-y-4">
|
|
330
|
-
<AnimatePresence>
|
|
331
|
-
{processedProducts.map((product) => (
|
|
332
|
-
<motion.div
|
|
333
|
-
key={product.id}
|
|
334
|
-
layout
|
|
335
|
-
initial={{ opacity: 0, y: 20 }}
|
|
336
|
-
animate={{ opacity: 1, y: 0 }}
|
|
337
|
-
exit={{ opacity: 0, y: -20 }}
|
|
338
|
-
transition={{ duration: 0.2 }}
|
|
339
|
-
className="flex flex-col gap-4 rounded-2xl border border-slate-100 bg-slate-50 p-4 shadow-sm shadow-primary-50 sm:flex-row sm:items-center"
|
|
340
|
-
>
|
|
341
|
-
<div className="relative h-28 w-full overflow-hidden rounded-2xl bg-white sm:w-40">
|
|
342
|
-
<Image
|
|
343
|
-
fill
|
|
344
|
-
src={product.productMedia?.[0]?.file || '/placeholder-product.jpg'}
|
|
345
|
-
alt={product.name || 'Wishlist item'}
|
|
346
|
-
className="h-full w-full object-cover"
|
|
303
|
+
{!isLoading && processedProducts.length > 0 && (
|
|
304
|
+
<>
|
|
305
|
+
{viewMode === 'grid' ? (
|
|
306
|
+
<motion.div
|
|
307
|
+
layout
|
|
308
|
+
className="grid grid-cols-1 gap-5 sm:grid-cols-2 xl:grid-cols-3"
|
|
309
|
+
>
|
|
310
|
+
<AnimatePresence>
|
|
311
|
+
{processedProducts.map((product) => (
|
|
312
|
+
<motion.div
|
|
313
|
+
key={product.id}
|
|
314
|
+
layout
|
|
315
|
+
initial={{ opacity: 0, y: 20 }}
|
|
316
|
+
animate={{ opacity: 1, y: 0 }}
|
|
317
|
+
exit={{ opacity: 0, y: -20 }}
|
|
318
|
+
transition={{ duration: 0.2 }}
|
|
319
|
+
>
|
|
320
|
+
<ProductCard
|
|
321
|
+
product={product as ExtendedProductDTO}
|
|
322
|
+
onClickProduct={(p) => router.push(buildPath(`/products/${p.id}`))}
|
|
323
|
+
onFavorite={() => handleRemoveFromWishlist(product.id)}
|
|
324
|
+
isFavorited
|
|
347
325
|
/>
|
|
348
|
-
</div>
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
326
|
+
</motion.div>
|
|
327
|
+
))}
|
|
328
|
+
</AnimatePresence>
|
|
329
|
+
</motion.div>
|
|
330
|
+
) : (
|
|
331
|
+
<motion.div layout className="space-y-4">
|
|
332
|
+
<AnimatePresence>
|
|
333
|
+
{processedProducts.map((product) => (
|
|
334
|
+
<motion.div
|
|
335
|
+
key={product.id}
|
|
336
|
+
layout
|
|
337
|
+
initial={{ opacity: 0, y: 20 }}
|
|
338
|
+
animate={{ opacity: 1, y: 0 }}
|
|
339
|
+
exit={{ opacity: 0, y: -20 }}
|
|
340
|
+
transition={{ duration: 0.2 }}
|
|
341
|
+
className="flex flex-col gap-4 rounded-2xl border border-slate-100 bg-slate-50 p-4 shadow-sm shadow-primary-50 sm:flex-row sm:items-center"
|
|
342
|
+
>
|
|
343
|
+
<div className="relative h-28 w-full overflow-hidden rounded-2xl bg-white sm:w-40">
|
|
344
|
+
<Image
|
|
345
|
+
fill
|
|
346
|
+
src={product.productMedia?.[0]?.file || '/placeholder-product.jpg'}
|
|
347
|
+
alt={product.name || 'Wishlist item'}
|
|
348
|
+
className="h-full w-full object-cover"
|
|
349
|
+
/>
|
|
350
|
+
</div>
|
|
351
|
+
<div className="flex flex-1 flex-col gap-2">
|
|
352
|
+
<div className="flex flex-wrap items-center justify-between gap-3">
|
|
353
|
+
<div>
|
|
354
|
+
<h3 className="text-lg font-semibold text-secondary">
|
|
355
|
+
{product.name}
|
|
356
|
+
</h3>
|
|
357
|
+
<p className="text-sm text-muted">
|
|
358
|
+
{product.parentCategories?.map((category) => category?.name).join(', ') || 'General wellness'}
|
|
366
359
|
</p>
|
|
367
|
-
|
|
360
|
+
</div>
|
|
361
|
+
<div className="text-right">
|
|
362
|
+
<p className="text-lg font-bold text-primary">
|
|
363
|
+
{formatPrice(product.finalPrice ?? 0)}
|
|
364
|
+
</p>
|
|
365
|
+
{product.isDiscounted && (
|
|
366
|
+
<p className="text-xs text-emerald-500">
|
|
367
|
+
You save {formatPrice(Math.max((product.priceBeforeDiscount ?? 0) - (product.finalPrice ?? 0), 0))}
|
|
368
|
+
</p>
|
|
369
|
+
)}
|
|
370
|
+
</div>
|
|
368
371
|
</div>
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
{product.inventoryCount > 0 ? 'In stock' : 'Backordered'}
|
|
374
|
-
</span>
|
|
375
|
-
{product.totalSold > 0 && (
|
|
376
|
-
<span className="inline-flex items-center gap-1 rounded-full bg-slate-200 px-2.5 py-1 font-medium text-slate-700">
|
|
377
|
-
<Sparkles className="h-3.5 w-3.5" />
|
|
378
|
-
{product.totalSold}+ purchased
|
|
372
|
+
<div className="flex flex-wrap items-center gap-3 text-xs text-slate-500">
|
|
373
|
+
<span className={`inline-flex items-center gap-1 rounded-full px-2.5 py-1 font-medium ${product.inventoryCount > 0 ? 'bg-emerald-100 text-emerald-700' : 'bg-rose-100 text-rose-700'}`}>
|
|
374
|
+
<Package className="h-3.5 w-3.5" />
|
|
375
|
+
{product.inventoryCount > 0 ? 'In stock' : 'Backordered'}
|
|
379
376
|
</span>
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
>
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
377
|
+
{product.totalSold > 0 && (
|
|
378
|
+
<span className="inline-flex items-center gap-1 rounded-full bg-slate-200 px-2.5 py-1 font-medium text-slate-700">
|
|
379
|
+
<Sparkles className="h-3.5 w-3.5" />
|
|
380
|
+
{product.totalSold}+ purchased
|
|
381
|
+
</span>
|
|
382
|
+
)}
|
|
383
|
+
</div>
|
|
384
|
+
<div className="flex flex-wrap gap-2">
|
|
385
|
+
<Button
|
|
386
|
+
size="sm"
|
|
387
|
+
onClick={() => router.push(buildPath(`/products/${product.id}`))}
|
|
388
|
+
>
|
|
389
|
+
View details
|
|
390
|
+
</Button>
|
|
391
|
+
<Button
|
|
392
|
+
size="sm"
|
|
393
|
+
variant="outline"
|
|
394
|
+
onClick={() => handleRemoveFromWishlist(product.id)}
|
|
395
|
+
className="text-secondary"
|
|
396
|
+
>
|
|
397
|
+
Remove
|
|
398
|
+
</Button>
|
|
399
|
+
</div>
|
|
397
400
|
</div>
|
|
398
|
-
</div>
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
</
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
)}
|
|
401
|
+
</motion.div>
|
|
402
|
+
))}
|
|
403
|
+
</AnimatePresence>
|
|
404
|
+
</motion.div>
|
|
405
|
+
)}
|
|
406
|
+
</>
|
|
407
|
+
)}
|
|
406
408
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
409
|
+
{isAuthenticated && emptyAfterFiltering && (
|
|
410
|
+
<div className="flex flex-col items-center justify-center rounded-lg border border-dashed border-gray-200 bg-gray-50 p-12 text-center">
|
|
411
|
+
<div className="flex h-16 w-16 items-center justify-center rounded-full bg-gray-200 text-gray-500">
|
|
412
|
+
<Package className="h-8 w-8" />
|
|
413
|
+
</div>
|
|
414
|
+
<h3 className="mt-6 text-xl font-semibold text-secondary">
|
|
415
|
+
Nothing matches those filters
|
|
416
|
+
</h3>
|
|
417
|
+
<p className="mt-2 max-w-md text-sm text-muted">
|
|
418
|
+
Try showing out-of-stock items or adjust your sort order to revisit everything you've saved.
|
|
419
|
+
</p>
|
|
420
|
+
<button
|
|
421
|
+
type="button"
|
|
422
|
+
className="mt-6 rounded-xl border-2 border-primary bg-white px-6 py-3 text-sm font-medium text-secondary hover:opacity-80 transition-colors"
|
|
423
|
+
onClick={() => setOnlyInStock(false)}
|
|
424
|
+
>
|
|
425
|
+
Show all saved products
|
|
426
|
+
</button>
|
|
427
|
+
</div>
|
|
428
|
+
)}
|
|
429
|
+
</>
|
|
426
430
|
)}
|
|
427
|
-
</>
|
|
428
|
-
)}
|
|
429
431
|
</motion.div>
|
|
430
432
|
</div>
|
|
431
433
|
</div>
|
package/src/styles/globals.css
CHANGED
|
@@ -3,6 +3,15 @@
|
|
|
3
3
|
@tailwind utilities;
|
|
4
4
|
|
|
5
5
|
@layer base {
|
|
6
|
+
:root {
|
|
7
|
+
--color-primary: #5B9BD5;
|
|
8
|
+
--color-primary-dark: #4a8ac4;
|
|
9
|
+
--color-secondary: #2B4B7C;
|
|
10
|
+
--color-accent: #E67E50;
|
|
11
|
+
--color-accent-dark: #d66f45;
|
|
12
|
+
--color-text-muted: #676c80;
|
|
13
|
+
}
|
|
14
|
+
|
|
6
15
|
* {
|
|
7
16
|
@apply border-gray-200;
|
|
8
17
|
}
|