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.
- package/README.md +157 -17
- package/dist/index.d.mts +3636 -316
- package/dist/index.d.ts +3636 -316
- package/dist/index.js +6802 -3866
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +6756 -3818
- package/dist/index.mjs.map +1 -1
- package/package.json +18 -15
- package/src/components/AddressFormModal.tsx +171 -0
- package/src/components/CartItem.tsx +17 -12
- package/src/components/FilterChips.tsx +195 -0
- package/src/components/Header.tsx +121 -71
- package/src/components/OrderCard.tsx +18 -25
- package/src/components/ProductCard.tsx +209 -72
- package/src/components/ui/Button.tsx +13 -5
- package/src/components/ui/Card.tsx +46 -0
- package/src/hooks/useAddresses.ts +83 -0
- package/src/hooks/useOrders.ts +37 -19
- package/src/hooks/useProducts.ts +55 -63
- package/src/hooks/useWishlistProducts.ts +75 -0
- package/src/index.ts +3 -19
- package/src/lib/Apis/api.ts +1 -0
- package/src/lib/Apis/apis/cart-api.ts +3 -3
- package/src/lib/Apis/apis/inventory-api.ts +0 -108
- package/src/lib/Apis/apis/stores-api.ts +70 -0
- package/src/lib/Apis/apis/wishlist-api.ts +447 -0
- package/src/lib/Apis/models/cart-item-populated.ts +0 -1
- package/src/lib/Apis/models/create-single-variant-product-dto.ts +3 -10
- package/src/lib/Apis/models/create-variant-dto.ts +26 -33
- package/src/lib/Apis/models/extended-product-dto.ts +20 -24
- package/src/lib/Apis/models/index.ts +2 -1
- package/src/lib/Apis/models/order-time-line-dto.ts +49 -0
- package/src/lib/Apis/models/order.ts +3 -8
- package/src/lib/Apis/models/populated-order.ts +3 -8
- package/src/lib/Apis/models/product-variant.ts +29 -0
- package/src/lib/Apis/models/update-product-variant-dto.ts +16 -23
- package/src/lib/Apis/models/wishlist.ts +51 -0
- package/src/lib/Apis/wrapper.ts +18 -7
- package/src/lib/api-adapter/index.ts +0 -12
- package/src/lib/types/index.ts +16 -61
- package/src/lib/utils/colors.ts +7 -4
- package/src/lib/utils/format.ts +1 -1
- package/src/lib/validations/address.ts +14 -0
- package/src/providers/AuthProvider.tsx +61 -31
- package/src/providers/CartProvider.tsx +18 -28
- package/src/providers/EcommerceProvider.tsx +7 -0
- package/src/providers/FavoritesProvider.tsx +86 -0
- package/src/providers/ThemeProvider.tsx +16 -1
- package/src/providers/WishlistProvider.tsx +174 -0
- package/src/screens/AddressesScreen.tsx +484 -0
- package/src/screens/CartScreen.tsx +120 -84
- package/src/screens/CategoriesScreen.tsx +120 -0
- package/src/screens/CheckoutScreen.tsx +919 -241
- package/src/screens/CurrentOrdersScreen.tsx +125 -61
- package/src/screens/HomeScreen.tsx +209 -0
- package/src/screens/LoginScreen.tsx +133 -88
- package/src/screens/NewAddressScreen.tsx +187 -0
- package/src/screens/OrdersScreen.tsx +162 -50
- package/src/screens/ProductDetailScreen.tsx +641 -190
- package/src/screens/ProfileScreen.tsx +192 -116
- package/src/screens/RegisterScreen.tsx +193 -144
- package/src/screens/SearchResultsScreen.tsx +165 -0
- package/src/screens/ShopScreen.tsx +1110 -146
- package/src/screens/WishlistScreen.tsx +428 -0
- package/src/lib/Apis/models/inventory-paginated-response.ts +0 -75
- package/src/lib/api/auth.ts +0 -81
- package/src/lib/api/cart.ts +0 -42
- package/src/lib/api/orders.ts +0 -53
- package/src/lib/api/products.ts +0 -51
- package/src/lib/api-adapter/auth-adapter.ts +0 -196
- package/src/lib/api-adapter/cart-adapter.ts +0 -193
- package/src/lib/api-adapter/mappers.ts +0 -152
- package/src/lib/api-adapter/orders-adapter.ts +0 -195
- package/src/lib/api-adapter/products-adapter.ts +0 -194
|
@@ -6,18 +6,24 @@ import { ShoppingCart, User, Menu, X, Search, Heart } from 'lucide-react';
|
|
|
6
6
|
import { useAuth } from '@/providers/AuthProvider';
|
|
7
7
|
import { useCart } from '@/providers/CartProvider';
|
|
8
8
|
import { useTheme } from '@/providers/ThemeProvider';
|
|
9
|
+
import { useWishlist } from '@/providers/WishlistProvider';
|
|
9
10
|
import Link from 'next/link';
|
|
10
11
|
import Image from 'next/image';
|
|
11
12
|
|
|
12
13
|
export function Header() {
|
|
13
14
|
const { config } = useTheme();
|
|
14
15
|
const { user, isAuthenticated } = useAuth();
|
|
15
|
-
const { cart } = useCart();
|
|
16
|
+
const { cart } = useCart() || { cart: { itemCount: 0 } };
|
|
17
|
+
const { getWishlistCount } = useWishlist();
|
|
18
|
+
const wishlistCount = getWishlistCount?.() || 0;
|
|
16
19
|
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
|
20
|
+
const [isSearchOpen, setIsSearchOpen] = useState(false);
|
|
21
|
+
const [searchQuery, setSearchQuery] = useState('');
|
|
17
22
|
|
|
18
23
|
const navLinks = [
|
|
19
24
|
{ href: '/shop', label: 'Shop' },
|
|
20
25
|
{ href: '/categories', label: 'Categories' },
|
|
26
|
+
{ href: '/orders', label: 'Orders' },
|
|
21
27
|
{ href: '/about', label: 'About' },
|
|
22
28
|
{ href: '/contact', label: 'Contact' },
|
|
23
29
|
];
|
|
@@ -58,49 +64,94 @@ export function Header() {
|
|
|
58
64
|
{/* Actions */}
|
|
59
65
|
<div className="flex items-center gap-4">
|
|
60
66
|
{/* Search */}
|
|
61
|
-
<
|
|
62
|
-
<
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
{/* Favorites */}
|
|
66
|
-
<button className="p-2 hover:bg-gray-100 rounded-lg transition-colors relative">
|
|
67
|
-
<Heart className="w-5 h-5 text-gray-700" />
|
|
68
|
-
</button>
|
|
69
|
-
|
|
70
|
-
{/* Cart */}
|
|
71
|
-
<Link
|
|
72
|
-
href="/cart"
|
|
73
|
-
className="p-2 hover:bg-gray-100 rounded-lg transition-colors relative"
|
|
74
|
-
>
|
|
75
|
-
<ShoppingCart className="w-5 h-5 text-gray-700" />
|
|
76
|
-
{cart && cart.itemCount > 0 && (
|
|
77
|
-
<span className="absolute -top-1 -right-1 w-5 h-5 bg-primary-600 text-white text-xs font-bold rounded-full flex items-center justify-center">
|
|
78
|
-
{cart.itemCount}
|
|
79
|
-
</span>
|
|
80
|
-
)}
|
|
81
|
-
</Link>
|
|
82
|
-
|
|
83
|
-
{/* User Menu */}
|
|
84
|
-
{isAuthenticated ? (
|
|
85
|
-
<Link
|
|
86
|
-
href="/account"
|
|
67
|
+
<div className="relative">
|
|
68
|
+
<button
|
|
69
|
+
onClick={() => setIsSearchOpen(!isSearchOpen)}
|
|
87
70
|
className="p-2 hover:bg-gray-100 rounded-lg transition-colors"
|
|
71
|
+
aria-label="Search"
|
|
88
72
|
>
|
|
89
|
-
<
|
|
73
|
+
<Search className="w-5 h-5 text-gray-700" />
|
|
74
|
+
</button>
|
|
75
|
+
|
|
76
|
+
<AnimatePresence>
|
|
77
|
+
{isSearchOpen && (
|
|
78
|
+
<motion.div
|
|
79
|
+
initial={{ opacity: 0, width: 0 }}
|
|
80
|
+
animate={{ opacity: 1, width: 'auto' }}
|
|
81
|
+
exit={{ opacity: 0, width: 0 }}
|
|
82
|
+
className="absolute right-0 top-full mt-2 bg-white rounded-lg shadow-lg overflow-hidden"
|
|
83
|
+
>
|
|
84
|
+
<div className="flex items-center p-2 w-64">
|
|
85
|
+
<Search className="w-5 h-5 text-gray-400 mr-2" />
|
|
86
|
+
<input
|
|
87
|
+
type="text"
|
|
88
|
+
value={searchQuery}
|
|
89
|
+
onChange={(e) => setSearchQuery(e.target.value)}
|
|
90
|
+
onKeyDown={(e) => {
|
|
91
|
+
if (e.key === 'Enter' && searchQuery.trim()) {
|
|
92
|
+
window.location.href = `/search?q=${encodeURIComponent(searchQuery.trim())}`;
|
|
93
|
+
}
|
|
94
|
+
}}
|
|
95
|
+
placeholder="Search products..."
|
|
96
|
+
className="w-full outline-none text-gray-700"
|
|
97
|
+
autoFocus
|
|
98
|
+
/>
|
|
99
|
+
{searchQuery && (
|
|
100
|
+
<button
|
|
101
|
+
onClick={() => setSearchQuery('')}
|
|
102
|
+
className="text-gray-400 hover:text-gray-600"
|
|
103
|
+
>
|
|
104
|
+
<X className="w-4 h-4" />
|
|
105
|
+
</button>
|
|
106
|
+
)}
|
|
107
|
+
</div>
|
|
108
|
+
</motion.div>
|
|
109
|
+
)}
|
|
110
|
+
</AnimatePresence>
|
|
111
|
+
</div>
|
|
112
|
+
|
|
113
|
+
{/* Wishlist and Cart */}
|
|
114
|
+
<div className="flex items-center gap-4">
|
|
115
|
+
<Link href="/wishlist" className="relative p-2 text-gray-700 hover:text-red-500 transition-colors">
|
|
116
|
+
<Heart className="w-6 h-6" />
|
|
117
|
+
{wishlistCount > 0 && (
|
|
118
|
+
<span className="absolute -top-1 -right-1 bg-red-500 text-white text-xs font-bold rounded-full w-5 h-5 flex items-center justify-center">
|
|
119
|
+
{wishlistCount}
|
|
120
|
+
</span>
|
|
121
|
+
)}
|
|
90
122
|
</Link>
|
|
91
|
-
|
|
92
|
-
<Link
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
123
|
+
|
|
124
|
+
<Link href="/cart" className="relative p-2 text-gray-700 hover:text-primary-600 transition-colors">
|
|
125
|
+
<ShoppingCart className="w-6 h-6" />
|
|
126
|
+
{cart?.cartBody?.items?.length && cart.cartBody?.items?.length > 0 ? (
|
|
127
|
+
<span className="absolute -top-1 -right-1 bg-red-500 text-white text-xs font-bold rounded-full w-5 h-5 flex items-center justify-center">
|
|
128
|
+
{cart.cartBody?.items?.length}
|
|
129
|
+
</span>
|
|
130
|
+
) : null}
|
|
97
131
|
</Link>
|
|
98
|
-
|
|
132
|
+
|
|
133
|
+
{/* User Menu */}
|
|
134
|
+
{isAuthenticated ? (
|
|
135
|
+
<Link
|
|
136
|
+
href="/account"
|
|
137
|
+
className="p-2 hover:bg-gray-100 rounded-lg transition-colors"
|
|
138
|
+
>
|
|
139
|
+
<User className="w-6 h-6 text-gray-700" />
|
|
140
|
+
</Link>
|
|
141
|
+
) : (
|
|
142
|
+
<Link
|
|
143
|
+
href="/login"
|
|
144
|
+
className="hidden sm:block px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors font-medium"
|
|
145
|
+
>
|
|
146
|
+
Sign In
|
|
147
|
+
</Link>
|
|
148
|
+
)}
|
|
149
|
+
</div>
|
|
99
150
|
|
|
100
151
|
{/* Mobile Menu Button */}
|
|
101
152
|
<button
|
|
102
|
-
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
|
|
103
153
|
className="lg:hidden p-2 hover:bg-gray-100 rounded-lg transition-colors"
|
|
154
|
+
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
|
|
104
155
|
>
|
|
105
156
|
{isMobileMenuOpen ? (
|
|
106
157
|
<X className="w-6 h-6" />
|
|
@@ -109,43 +160,42 @@ export function Header() {
|
|
|
109
160
|
)}
|
|
110
161
|
</button>
|
|
111
162
|
</div>
|
|
112
|
-
</div>
|
|
113
163
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
164
|
+
{/* Mobile Menu */}
|
|
165
|
+
<AnimatePresence>
|
|
166
|
+
{isMobileMenuOpen && (
|
|
167
|
+
<motion.div
|
|
168
|
+
initial={{ opacity: 0, height: 0 }}
|
|
169
|
+
animate={{ opacity: 1, height: 'auto' }}
|
|
170
|
+
exit={{ opacity: 0, height: 0 }}
|
|
171
|
+
className="lg:hidden overflow-hidden border-t border-gray-200"
|
|
172
|
+
>
|
|
173
|
+
<nav className="flex flex-col gap-1 py-2">
|
|
174
|
+
{navLinks.map((link) => (
|
|
175
|
+
<Link
|
|
176
|
+
key={link.href}
|
|
177
|
+
href={link.href}
|
|
178
|
+
className="px-4 py-3 text-gray-700 hover:bg-gray-50 rounded-lg font-medium"
|
|
179
|
+
onClick={() => setIsMobileMenuOpen(false)}
|
|
180
|
+
>
|
|
181
|
+
{link.label}
|
|
182
|
+
</Link>
|
|
183
|
+
))}
|
|
184
|
+
{!isAuthenticated && (
|
|
185
|
+
<Link
|
|
186
|
+
href="/login"
|
|
187
|
+
onClick={() => setIsMobileMenuOpen(false)}
|
|
188
|
+
className="px-4 py-3 text-primary-600 hover:bg-gray-50 rounded-lg font-medium"
|
|
189
|
+
>
|
|
190
|
+
Sign In
|
|
191
|
+
</Link>
|
|
192
|
+
)}
|
|
193
|
+
</nav>
|
|
194
|
+
</motion.div>
|
|
195
|
+
)}
|
|
196
|
+
</AnimatePresence>
|
|
197
|
+
</div>
|
|
147
198
|
</div>
|
|
148
199
|
</header>
|
|
149
200
|
);
|
|
150
201
|
}
|
|
151
|
-
|
|
@@ -3,25 +3,18 @@
|
|
|
3
3
|
import React from 'react';
|
|
4
4
|
import { motion } from 'framer-motion';
|
|
5
5
|
import { Package, Calendar, CreditCard, ExternalLink } from 'lucide-react';
|
|
6
|
-
import {
|
|
6
|
+
import { PaymentPaymentMethodEnum, PaymentPaymentStatusEnum, PopulatedOrder } from '@/lib/Apis';
|
|
7
7
|
import { formatPrice, formatDate } from '@/lib/utils/format';
|
|
8
8
|
import { Badge } from './ui/Badge';
|
|
9
9
|
import Link from 'next/link';
|
|
10
|
+
import Image from 'next/image';
|
|
10
11
|
|
|
11
12
|
interface OrderCardProps {
|
|
12
|
-
order:
|
|
13
|
+
order: PopulatedOrder;
|
|
13
14
|
}
|
|
14
15
|
|
|
15
|
-
const statusConfig: Record<OrderStatus, { variant: 'success' | 'warning' | 'primary' | 'danger' | 'gray', label: string }> = {
|
|
16
|
-
[OrderStatus.PENDING]: { variant: 'warning', label: 'Pending' },
|
|
17
|
-
[OrderStatus.PROCESSING]: { variant: 'primary', label: 'Processing' },
|
|
18
|
-
[OrderStatus.SHIPPED]: { variant: 'primary', label: 'Shipped' },
|
|
19
|
-
[OrderStatus.DELIVERED]: { variant: 'success', label: 'Delivered' },
|
|
20
|
-
[OrderStatus.CANCELLED]: { variant: 'danger', label: 'Cancelled' },
|
|
21
|
-
};
|
|
22
|
-
|
|
23
16
|
export function OrderCard({ order }: OrderCardProps) {
|
|
24
|
-
const config =
|
|
17
|
+
const config = order.orderStatus;
|
|
25
18
|
|
|
26
19
|
return (
|
|
27
20
|
<motion.div
|
|
@@ -35,29 +28,29 @@ export function OrderCard({ order }: OrderCardProps) {
|
|
|
35
28
|
<div>
|
|
36
29
|
<h3 className="text-lg font-bold text-gray-900 flex items-center gap-2">
|
|
37
30
|
<Package className="w-5 h-5 text-primary-600" />
|
|
38
|
-
Order #{order
|
|
31
|
+
Order #{order?._id?.slice(0, 6) || ''}
|
|
39
32
|
</h3>
|
|
40
33
|
<p className="text-sm text-gray-500 mt-1 flex items-center gap-2">
|
|
41
34
|
<Calendar className="w-4 h-4" />
|
|
42
|
-
{formatDate(order.createdAt, 'long')}
|
|
35
|
+
{formatDate(order.createdAt || new Date(), 'long')}
|
|
43
36
|
</p>
|
|
44
37
|
</div>
|
|
45
|
-
<Badge variant={config
|
|
38
|
+
<Badge variant={config as 'success' | 'warning' | 'primary' | 'danger' | 'gray'}>{config}</Badge>
|
|
46
39
|
</div>
|
|
47
40
|
|
|
48
41
|
{/* Items Preview */}
|
|
49
42
|
<div className="space-y-2 mb-4">
|
|
50
|
-
{order.items
|
|
51
|
-
<div key={item.
|
|
52
|
-
<
|
|
43
|
+
{order.items?.slice(0, 2).map((item) => (
|
|
44
|
+
<div key={item.productVariantId} className="flex items-center gap-3 text-sm">
|
|
45
|
+
<Image src={item?.productVariantData?.productMedia?.[0]?.file || '/placeholder-product.jpg'} alt={item?.productVariantData?.name || 'Product image'} width={48} height={48} className="w-12 h-12 rounded-lg bg-gray-100 flex-shrink-0" />
|
|
53
46
|
<div className="flex-1 min-w-0">
|
|
54
|
-
<p className="font-medium text-gray-900 truncate">{item.
|
|
47
|
+
<p className="font-medium text-gray-900 truncate">{item.productVariantData.name}</p>
|
|
55
48
|
<p className="text-gray-500">Qty: {item.quantity}</p>
|
|
56
49
|
</div>
|
|
57
|
-
<p className="font-semibold text-gray-900">{formatPrice(item.
|
|
50
|
+
<p className="font-semibold text-gray-900">{formatPrice(item.productVariantData.finalPrice)}</p>
|
|
58
51
|
</div>
|
|
59
52
|
))}
|
|
60
|
-
{order.items.length > 2 && (
|
|
53
|
+
{order.items?.length && order.items?.length > 2 && (
|
|
61
54
|
<p className="text-sm text-gray-500 pl-15">
|
|
62
55
|
+{order.items.length - 2} more item{order.items.length - 2 > 1 ? 's' : ''}
|
|
63
56
|
</p>
|
|
@@ -68,13 +61,13 @@ export function OrderCard({ order }: OrderCardProps) {
|
|
|
68
61
|
<div className="flex justify-between items-center pt-4 border-t border-gray-200">
|
|
69
62
|
<div>
|
|
70
63
|
<p className="text-sm text-gray-500">Total Amount</p>
|
|
71
|
-
<p className="text-2xl font-bold text-gray-900">{formatPrice(order.
|
|
64
|
+
<p className="text-2xl font-bold text-gray-900">{formatPrice(order.grandTotal || 0)}</p>
|
|
72
65
|
</div>
|
|
73
66
|
|
|
74
67
|
<div className="flex gap-2">
|
|
75
|
-
{order.
|
|
68
|
+
{order.payment.paymentStatus !== PaymentPaymentStatusEnum.Paid && order.payment.paymentMethod === PaymentPaymentMethodEnum.Card && (
|
|
76
69
|
<a
|
|
77
|
-
href={order
|
|
70
|
+
href={order?.payment?.paymentIntent?.hostedInvoiceUrl || ''}
|
|
78
71
|
target="_blank"
|
|
79
72
|
rel="noopener noreferrer"
|
|
80
73
|
className="inline-flex items-center gap-2 px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors"
|
|
@@ -83,13 +76,13 @@ export function OrderCard({ order }: OrderCardProps) {
|
|
|
83
76
|
Pay Now
|
|
84
77
|
</a>
|
|
85
78
|
)}
|
|
86
|
-
<Link
|
|
79
|
+
{/* <Link
|
|
87
80
|
href={`/orders/${order.id}`}
|
|
88
81
|
className="inline-flex items-center gap-2 px-4 py-2 border-2 border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition-colors"
|
|
89
82
|
>
|
|
90
83
|
View Details
|
|
91
84
|
<ExternalLink className="w-4 h-4" />
|
|
92
|
-
</Link>
|
|
85
|
+
</Link> */}
|
|
93
86
|
</div>
|
|
94
87
|
</div>
|
|
95
88
|
</motion.div>
|