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.
- package/dist/index.d.mts +10552 -1370
- package/dist/index.d.ts +10552 -1370
- package/dist/index.js +4696 -1281
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +4640 -1283
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/components/AccountOrdersTab.tsx +1 -1
- package/src/components/AccountSettingsTab.tsx +88 -6
- package/src/components/CartItem.tsx +1 -1
- package/src/components/Header.tsx +8 -2
- package/src/components/OrderCard.tsx +4 -4
- package/src/components/ProductCard.tsx +59 -42
- package/src/components/QuickViewModal.tsx +13 -13
- package/src/hooks/useAddresses.ts +4 -1
- package/src/hooks/usePaymentMethods.ts +26 -31
- package/src/hooks/useProducts.ts +63 -64
- package/src/hooks/useStoreCapabilities.ts +87 -0
- package/src/hooks/useWishlistProducts.ts +4 -5
- package/src/index.ts +6 -0
- package/src/lib/Apis/api.ts +0 -1
- package/src/lib/Apis/apis/auth-api.ts +37 -36
- package/src/lib/Apis/apis/categories-api.ts +97 -0
- package/src/lib/Apis/apis/products-api.ts +942 -405
- package/src/lib/Apis/apis/shipping-api.ts +105 -0
- package/src/lib/Apis/apis/stores-api.ts +356 -0
- package/src/lib/Apis/apis/sub-categories-api.ts +97 -0
- package/src/lib/Apis/apis/users-api.ts +8 -8
- package/src/lib/Apis/models/address-created-request.ts +0 -12
- package/src/lib/Apis/models/address.ts +0 -12
- package/src/lib/Apis/models/api-key-info-dto.ts +49 -0
- package/src/lib/Apis/models/category-populated.ts +0 -12
- package/src/lib/Apis/models/category-sub-category-populated.ts +2 -2
- package/src/lib/Apis/models/category.ts +0 -18
- package/src/lib/Apis/models/{table-cell-dto.ts → change-password-dto.ts} +6 -6
- package/src/lib/Apis/models/create-address-dto.ts +0 -12
- package/src/lib/Apis/models/create-discount-dto.ts +0 -8
- package/src/lib/Apis/models/create-product-dto.ts +30 -23
- package/src/lib/Apis/models/create-store-address-dto.ts +0 -12
- package/src/lib/Apis/models/create-store-dto-settings.ts +51 -0
- package/src/lib/Apis/models/create-store-dto.ts +7 -0
- package/src/lib/Apis/models/create-sub-category-dto.ts +6 -0
- package/src/lib/Apis/models/create-variant-dto.ts +26 -32
- package/src/lib/Apis/models/discount.ts +0 -8
- package/src/lib/Apis/models/index.ts +16 -7
- package/src/lib/Apis/models/paginated-products-dto.ts +6 -6
- package/src/lib/Apis/models/populated-discount.ts +0 -8
- package/src/lib/Apis/models/product-summary.ts +69 -0
- package/src/lib/Apis/models/product-variant.ts +31 -68
- package/src/lib/Apis/models/product.ts +138 -0
- package/src/lib/Apis/models/products-insights-dto.ts +12 -0
- package/src/lib/Apis/models/reorder-categories-dto.ts +27 -0
- package/src/lib/Apis/models/reorder-products-dto.ts +49 -0
- package/src/lib/Apis/models/{table-dto.ts → reorder-products-success-response-dto.ts} +8 -9
- package/src/lib/Apis/models/reorder-subcategories-dto.ts +33 -0
- package/src/lib/Apis/models/{shallow-parent-category-dto.ts → reorder-success-response-dto.ts} +7 -7
- package/src/lib/Apis/models/shipment-with-order.ts +18 -0
- package/src/lib/Apis/models/shipment.ts +18 -0
- package/src/lib/Apis/models/single-product-media.ts +0 -12
- package/src/lib/Apis/models/store-api-keys-response-dto.ts +34 -0
- package/src/lib/Apis/models/store-capabilities-dto.ts +63 -0
- package/src/lib/Apis/models/store-entity.ts +7 -0
- package/src/lib/Apis/models/store.ts +7 -0
- package/src/lib/Apis/models/sub-category.ts +6 -12
- package/src/lib/Apis/models/update-address-dto.ts +0 -12
- package/src/lib/Apis/models/update-api-keys-dto.ts +39 -0
- package/src/lib/Apis/models/update-discount-dto.ts +0 -8
- package/src/lib/Apis/models/update-manual-shipment-status-dto.ts +47 -0
- package/src/lib/Apis/models/update-product-dto.ts +30 -19
- package/src/lib/Apis/models/update-store-dto.ts +7 -0
- package/src/lib/Apis/models/update-sub-category-dto.ts +6 -0
- package/src/lib/Apis/models/{update-product-variant-dto.ts → update-variant-dto.ts} +46 -46
- package/src/lib/Apis/models/variant-id-inventory-body.ts +27 -0
- package/src/lib/api-adapter/config.ts +53 -0
- package/src/lib/validations/address.ts +1 -1
- package/src/providers/FavoritesProvider.tsx +5 -5
- package/src/providers/WishlistProvider.tsx +4 -4
- package/src/screens/CartScreen.tsx +1 -1
- package/src/screens/ChangePasswordScreen.tsx +2 -6
- package/src/screens/CheckoutScreen.tsx +402 -288
- package/src/screens/ForgotPasswordScreen.tsx +153 -0
- package/src/screens/ProductDetailScreen.tsx +51 -60
- package/src/screens/RegisterScreen.tsx +31 -31
- package/src/screens/ResetPasswordScreen.tsx +208 -0
- package/src/screens/SearchResultsScreen.tsx +264 -26
- package/src/screens/ShopScreen.tsx +42 -45
- package/src/screens/WishlistScreen.tsx +35 -31
- package/src/lib/Apis/apis/product-variants-api.ts +0 -552
- package/src/lib/Apis/models/create-single-variant-product-dto.ts +0 -154
- package/src/lib/Apis/models/extended-product-dto.ts +0 -206
- package/src/lib/Apis/models/frequently-bought-product-dto.ts +0 -71
|
@@ -6,11 +6,10 @@ import {
|
|
|
6
6
|
useMemo,
|
|
7
7
|
useState,
|
|
8
8
|
} from 'react';
|
|
9
|
-
import {
|
|
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
|
-
|
|
78
|
+
const sanitized = searchQuery.trim().replace(/\s+/g, ' ');
|
|
79
|
+
if (sanitized) {
|
|
81
80
|
setIsSearching(true);
|
|
82
|
-
router.push(buildPath(`/search?q=${encodeURIComponent(
|
|
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'
|
|
92
|
+
if (e.key === 'Enter') {
|
|
94
93
|
e.preventDefault();
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
701
|
-
const
|
|
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={
|
|
709
|
+
key={categoryId}
|
|
706
710
|
onClick={() => {
|
|
707
|
-
if (!isExpanded) toggleCategoryExpand(
|
|
708
|
-
handleCategoryChange(
|
|
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
|
|
962
|
+
const categoryId = category._id ?? category.id ?? '';
|
|
963
|
+
const isSelected = categoryFilter === categoryId;
|
|
959
964
|
|
|
960
965
|
return (
|
|
961
966
|
<motion.button
|
|
962
|
-
key={
|
|
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(
|
|
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
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
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.
|
|
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.
|
|
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
|
-
{
|
|
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(
|
|
1165
|
+
{formatPrice(displayFinalPrice)}
|
|
1169
1166
|
</p>
|
|
1170
|
-
{
|
|
1167
|
+
{displayIsDiscounted && (
|
|
1171
1168
|
<p className="text-sm text-gray-400 line-through">
|
|
1172
|
-
{formatPrice(
|
|
1169
|
+
{formatPrice(displayRetailPrice)}
|
|
1173
1170
|
</p>
|
|
1174
1171
|
)}
|
|
1175
1172
|
</div>
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
ShoppingBag,
|
|
13
13
|
Sparkles,
|
|
14
14
|
Trash2,
|
|
15
|
+
Trash
|
|
15
16
|
} from 'lucide-react';
|
|
16
17
|
import { useRouter } from 'next/navigation';
|
|
17
18
|
import { ProductCard } from '@/components/ProductCard';
|
|
@@ -21,7 +22,7 @@ import { useAuth } from '@/providers/AuthProvider';
|
|
|
21
22
|
import { useWishlistProducts } from '@/hooks/useWishlistProducts';
|
|
22
23
|
import Image from 'next/image';
|
|
23
24
|
import { formatPrice } from '@/lib/utils/format';
|
|
24
|
-
import {
|
|
25
|
+
import { Product } from '@/lib/Apis';
|
|
25
26
|
import { useBasePath } from '@/providers/BasePathProvider';
|
|
26
27
|
import { useNotification } from '@/providers/NotificationProvider';
|
|
27
28
|
|
|
@@ -95,23 +96,26 @@ export default function WishlistScreen() {
|
|
|
95
96
|
};
|
|
96
97
|
|
|
97
98
|
const totalValue = useMemo(
|
|
98
|
-
() => wishlistProducts.reduce((sum, product) => sum + (product.
|
|
99
|
+
() => wishlistProducts.reduce((sum, product) => sum + (product.summary?.minPrice ?? 0), 0),
|
|
99
100
|
[wishlistProducts]
|
|
100
101
|
);
|
|
101
102
|
|
|
102
103
|
const totalSavings = useMemo(
|
|
103
104
|
() =>
|
|
104
105
|
wishlistProducts.reduce((sum, product) => {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
106
|
+
// Calculate savings if product has discount
|
|
107
|
+
if (product.summary?.hasDiscount) {
|
|
108
|
+
const maxPrice = product.summary?.maxPrice ?? 0;
|
|
109
|
+
const minPrice = product.summary?.minPrice ?? 0;
|
|
110
|
+
return sum + (maxPrice - minPrice);
|
|
111
|
+
}
|
|
112
|
+
return sum;
|
|
109
113
|
}, 0),
|
|
110
114
|
[wishlistProducts]
|
|
111
115
|
);
|
|
112
116
|
|
|
113
117
|
const inStockCount = useMemo(
|
|
114
|
-
() => wishlistProducts.filter((product) => (product.
|
|
118
|
+
() => wishlistProducts.filter((product) => (product.summary?.totalInventory ?? 0) > 0).length,
|
|
115
119
|
[wishlistProducts]
|
|
116
120
|
);
|
|
117
121
|
|
|
@@ -119,25 +123,25 @@ export default function WishlistScreen() {
|
|
|
119
123
|
let list = [...wishlistProducts];
|
|
120
124
|
|
|
121
125
|
if (onlyInStock) {
|
|
122
|
-
list = list.filter((product) => (product.
|
|
126
|
+
list = list.filter((product) => (product.summary?.totalInventory ?? 0) > 0);
|
|
123
127
|
}
|
|
124
128
|
|
|
125
129
|
switch (sortOption) {
|
|
126
130
|
case 'price-low':
|
|
127
|
-
list.sort((a, b) => (a.
|
|
131
|
+
list.sort((a, b) => (a.summary?.minPrice ?? 0) - (b.summary?.minPrice ?? 0));
|
|
128
132
|
break;
|
|
129
133
|
case 'price-high':
|
|
130
|
-
list.sort((a, b) => (b.
|
|
134
|
+
list.sort((a, b) => (b.summary?.maxPrice ?? 0) - (a.summary?.maxPrice ?? 0));
|
|
131
135
|
break;
|
|
132
136
|
case 'name':
|
|
133
137
|
list.sort((a, b) => (a.name || '').localeCompare(b.name || ''));
|
|
134
138
|
break;
|
|
135
139
|
case 'availability':
|
|
136
|
-
list.sort((a, b) => (b.
|
|
140
|
+
list.sort((a, b) => (b.summary?.totalInventory ?? 0) - (a.summary?.totalInventory ?? 0));
|
|
137
141
|
break;
|
|
138
142
|
case 'featured':
|
|
139
143
|
default:
|
|
140
|
-
list.sort((a, b) => (b.totalSold ?? 0) - (a.totalSold ?? 0));
|
|
144
|
+
list.sort((a, b) => (b.summary?.totalSold ?? 0) - (a.summary?.totalSold ?? 0));
|
|
141
145
|
break;
|
|
142
146
|
}
|
|
143
147
|
|
|
@@ -318,9 +322,9 @@ export default function WishlistScreen() {
|
|
|
318
322
|
transition={{ duration: 0.2 }}
|
|
319
323
|
>
|
|
320
324
|
<ProductCard
|
|
321
|
-
product={product as
|
|
322
|
-
onClickProduct={(p) => router.push(buildPath(`/products/${p.
|
|
323
|
-
onFavorite={() => handleRemoveFromWishlist(product.id)}
|
|
325
|
+
product={product as Product}
|
|
326
|
+
onClickProduct={(p) => router.push(buildPath(`/products/${p._id}`))}
|
|
327
|
+
onFavorite={() => handleRemoveFromWishlist(product._id || product.id || '')}
|
|
324
328
|
isFavorited
|
|
325
329
|
/>
|
|
326
330
|
</motion.div>
|
|
@@ -343,7 +347,7 @@ export default function WishlistScreen() {
|
|
|
343
347
|
<div className="relative h-28 w-full overflow-hidden rounded-2xl bg-white sm:w-40">
|
|
344
348
|
<Image
|
|
345
349
|
fill
|
|
346
|
-
src={product.
|
|
350
|
+
src={product.media?.[0]?.file || '/placeholder-product.jpg'}
|
|
347
351
|
alt={product.name || 'Wishlist item'}
|
|
348
352
|
className="h-full w-full object-cover"
|
|
349
353
|
/>
|
|
@@ -355,47 +359,47 @@ export default function WishlistScreen() {
|
|
|
355
359
|
{product.name}
|
|
356
360
|
</h3>
|
|
357
361
|
<p className="text-sm text-muted">
|
|
358
|
-
|
|
362
|
+
General wellness
|
|
359
363
|
</p>
|
|
360
364
|
</div>
|
|
361
365
|
<div className="text-right">
|
|
362
366
|
<p className="text-lg font-bold text-primary">
|
|
363
|
-
{formatPrice(product.
|
|
367
|
+
{formatPrice(product.summary?.minPrice ?? 0)}
|
|
364
368
|
</p>
|
|
365
|
-
{product.
|
|
369
|
+
{product.summary?.hasDiscount && (
|
|
366
370
|
<p className="text-xs text-emerald-500">
|
|
367
|
-
You save {formatPrice(Math.max((product.
|
|
371
|
+
You save {formatPrice(Math.max((product.summary?.maxPrice ?? 0) - (product.summary?.minPrice ?? 0), 0))}
|
|
368
372
|
</p>
|
|
369
373
|
)}
|
|
370
374
|
</div>
|
|
371
375
|
</div>
|
|
372
376
|
<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.
|
|
377
|
+
<span className={`inline-flex items-center gap-1 rounded-full px-2.5 py-1 font-medium ${(product.summary?.totalInventory ?? 0) > 0 ? 'bg-emerald-100 text-emerald-700' : 'bg-rose-100 text-rose-700'}`}>
|
|
374
378
|
<Package className="h-3.5 w-3.5" />
|
|
375
|
-
{product.
|
|
379
|
+
{(product.summary?.totalInventory ?? 0) > 0 ? 'In stock' : 'Backordered'}
|
|
376
380
|
</span>
|
|
377
|
-
{product.totalSold > 0 && (
|
|
381
|
+
{(product.summary?.totalSold ?? 0) > 0 && (
|
|
378
382
|
<span className="inline-flex items-center gap-1 rounded-full bg-slate-200 px-2.5 py-1 font-medium text-slate-700">
|
|
379
383
|
<Sparkles className="h-3.5 w-3.5" />
|
|
380
|
-
{product.totalSold}+ purchased
|
|
384
|
+
{product.summary?.totalSold}+ purchased
|
|
381
385
|
</span>
|
|
382
386
|
)}
|
|
383
387
|
</div>
|
|
384
388
|
<div className="flex flex-wrap gap-2">
|
|
385
389
|
<Button
|
|
386
390
|
size="sm"
|
|
387
|
-
onClick={() => router.push(buildPath(`/products/${product.
|
|
391
|
+
onClick={() => router.push(buildPath(`/products/${product._id}`))}
|
|
392
|
+
className='bg-primary/90 text-white hover:bg-primary/70'
|
|
388
393
|
>
|
|
389
394
|
View details
|
|
390
395
|
</Button>
|
|
391
|
-
<
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
onClick={() => handleRemoveFromWishlist(product.id)}
|
|
395
|
-
className="text-secondary"
|
|
396
|
+
<button
|
|
397
|
+
onClick={() => handleRemoveFromWishlist(product._id || product.id || '')}
|
|
398
|
+
className='text-red-500 hover:text-red-600 hover:bg-red-50 transition-colors cursor-pointer border border-red-500 rounded-full px-4 py-1 text-sm flex-row'
|
|
396
399
|
>
|
|
400
|
+
{/* <Trash className="h-4 w-4" /> */}
|
|
397
401
|
Remove
|
|
398
|
-
</
|
|
402
|
+
</button>
|
|
399
403
|
</div>
|
|
400
404
|
</div>
|
|
401
405
|
</motion.div>
|