hey-pharmacist-ecommerce 1.0.5 → 1.0.6

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 +107 -1
  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 +17 -14
  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
@@ -1,122 +1,259 @@
1
1
  'use client';
2
2
 
3
- import React, { useState } from 'react';
4
- import { motion } from 'framer-motion';
5
- import { ShoppingCart, Heart } from 'lucide-react';
6
- import { Product } from '@/lib/types';
3
+ import React, { useState, useEffect, useMemo, useCallback } from 'react';
4
+ import { motion, AnimatePresence } from 'framer-motion';
5
+ import { Heart } from 'lucide-react';
6
+ import { ExtendedProductDTO, Product } from '@/lib/Apis';
7
7
  import { formatPrice } from '@/lib/utils/format';
8
- import { useCart } from '@/providers/CartProvider';
8
+ import { useWishlist } from '@/providers/WishlistProvider';
9
9
  import Image from 'next/image';
10
+ import { toast } from 'sonner';
11
+ import { useRouter } from 'next/navigation';
12
+
10
13
 
11
14
  interface ProductCardProps {
12
- product: Product;
13
- onClickProduct?: (product: Product) => void;
15
+ product: ExtendedProductDTO;
16
+ onClickProduct?: (product: ExtendedProductDTO) => void;
17
+ onFavorite?: (product: ExtendedProductDTO) => void;
18
+ isFavorited?: boolean;
19
+ showFavoriteButton?: boolean;
14
20
  }
15
21
 
16
- export function ProductCard({ product, onClickProduct }: ProductCardProps) {
17
- const { addToCart } = useCart();
18
- const [isAdding, setIsAdding] = useState(false);
19
- const [isFavorited, setIsFavorited] = useState(false);
22
+ export function ProductCard({
23
+ product,
24
+ onClickProduct,
25
+ onFavorite,
26
+ isFavorited = false,
27
+ showFavoriteButton = true,
28
+ className
29
+ }: ProductCardProps & { className?: string }) {
30
+ const router = useRouter();
31
+ const [isFavorite, setIsFavorite] = useState(isFavorited);
32
+ const { addToWishlist, removeFromWishlist, isInWishlist } = useWishlist();
33
+ const [isHovered, setIsHovered] = useState(false);
34
+ const [isImageLoaded, setIsImageLoaded] = useState(false);
35
+
36
+ // Handle image load state
37
+ const handleImageLoad = useCallback(() => {
38
+ setIsImageLoaded(true);
39
+ }, []);
40
+
41
+ // Handle card click
42
+ const handleCardClick = useCallback((e: React.MouseEvent) => {
43
+ if (onClickProduct) {
44
+ e.preventDefault();
45
+ onClickProduct(product);
46
+ }
47
+ }, [onClickProduct, product]);
48
+
20
49
 
21
- const handleAddToCart = async (e: React.MouseEvent) => {
50
+ const handleFavorite = async (e: React.MouseEvent) => {
22
51
  e.stopPropagation();
23
- setIsAdding(true);
24
52
  try {
25
- await addToCart(product.id);
26
- } finally {
27
- setIsAdding(false);
53
+ if (isInWishlist(product?._id || '')) {
54
+ await removeFromWishlist(product?._id || '');
55
+ setIsFavorite(false);
56
+ toast.success('Removed from wishlist');
57
+ } else {
58
+ await addToWishlist(product as unknown as Product);
59
+ setIsFavorite(true);
60
+ toast.success('Added to wishlist');
61
+ }
62
+ onFavorite?.(product);
63
+ } catch (error) {
64
+ console.error('Error updating wishlist:', error);
65
+ toast.error('Failed to update wishlist');
28
66
  }
29
67
  };
30
68
 
31
- const handleFavorite = (e: React.MouseEvent) => {
32
- e.stopPropagation();
33
- setIsFavorited(!isFavorited);
69
+ // Update favorite state when props or wishlist changes
70
+ useEffect(() => {
71
+ setIsFavorite(isInWishlist(product?._id || '') || isFavorited);
72
+ // eslint-disable-next-line react-hooks/exhaustive-deps
73
+ }, [isFavorited, isInWishlist, product?._id]);
74
+
75
+ // Handle keyboard navigation
76
+ const handleKeyDown = (e: React.KeyboardEvent) => {
77
+ if (e.key === 'Enter' || e.key === ' ') {
78
+ e.preventDefault();
79
+ handleCardClick(e as any);
80
+ }
34
81
  };
35
82
 
36
- const discount = product.compareAtPrice
37
- ? Math.round(((product.compareAtPrice - product.price) / product.compareAtPrice) * 100)
38
- : 0;
83
+
84
+ const imageSource = useMemo(() => {
85
+ return {
86
+ src: product.productMedia?.[0]?.file || '/placeholder-product.jpg',
87
+ alt: product.name || 'Product image'
88
+ };
89
+ }, [product.productMedia, product.name]);
90
+
91
+
39
92
 
40
93
  return (
41
- <motion.div
42
- whileHover={{ y: -8 }}
43
- className="group bg-white rounded-2xl overflow-hidden shadow-sm hover:shadow-2xl transition-all duration-300 cursor-pointer"
44
- onClick={() => onClickProduct?.(product)}
94
+ <motion.article
95
+ className=
96
+ "relative group bg-white rounded-xl overflow-hidden shadow-sm hover:shadow-md transition-all duration-300 border border-gray-100 hover:border-gray-200 flex h-[420px] flex-col"
97
+
98
+ whileHover={{ y: -4 }}
99
+ onMouseEnter={() => setIsHovered(true)}
100
+ onMouseLeave={() => setIsHovered(false)}
101
+ aria-labelledby={`product-${product.id}-title`}
102
+ role="article"
103
+ tabIndex={0}
104
+ onClick={handleCardClick}
105
+ onKeyDown={handleKeyDown}
45
106
  >
46
107
  {/* Image Container */}
47
- <div className="relative aspect-square overflow-hidden bg-gray-100">
48
- <Image
49
- src={product.images[0] || '/placeholder-product.jpg'}
50
- alt={product.name}
51
- fill
52
- className="object-cover group-hover:scale-110 transition-transform duration-500"
53
- />
54
-
108
+ <div className="relative h-48 w-full overflow-hidden bg-gray-50">
109
+ <AnimatePresence>
110
+ {!isImageLoaded && (
111
+ <motion.div
112
+ className="absolute inset-0 bg-gray-200 animate-pulse"
113
+ initial={{ opacity: 1 }}
114
+ exit={{ opacity: 0 }}
115
+ transition={{ duration: 0.2 }}
116
+ />
117
+ )}
118
+ </AnimatePresence>
119
+
120
+ {product.productMedia?.[0]?.file && (
121
+ <Image
122
+ src={product.productMedia?.[0]?.file || '/placeholder-product.jpg'}
123
+ alt={product.name || 'Product image'}
124
+ fill
125
+ className={`h-full w-full object-cover object-center transition-opacity duration-300 ${product.inventoryCount === 0 ? 'opacity-60' : ''
126
+ } ${isImageLoaded ? 'opacity-100' : 'opacity-0'}`}
127
+ sizes="(max-width: 640px) 100vw, (max-width: 768px) 50vw, 33vw"
128
+ priority={false}
129
+ onLoad={handleImageLoad}
130
+ />
131
+ )}
132
+
55
133
  {/* Badges */}
56
- <div className="absolute top-4 left-4 flex flex-col gap-2">
57
- {discount > 0 && (
58
- <span className="bg-red-500 text-white px-3 py-1 rounded-full text-sm font-bold">
59
- -{discount}%
60
- </span>
134
+ <div className="absolute top-3 left-3 flex flex-col gap-2 z-10">
135
+ {product.isDiscounted && (
136
+ <motion.span
137
+ initial={{ scale: 0.9, opacity: 0 }}
138
+ animate={{ scale: 1, opacity: 1 }}
139
+ className="inline-flex items-center justify-center px-2.5 py-1 rounded-full text-xs font-bold text-white bg-green-600 shadow-md"
140
+ >
141
+ -{product.discountAmount}%
142
+ </motion.span>
61
143
  )}
62
- {!product.inStock && (
63
- <span className="bg-gray-900 text-white px-3 py-1 rounded-full text-sm font-bold">
144
+
145
+ {product.inventoryCount === 0 && (
146
+ <motion.span
147
+ initial={{ scale: 0.9, opacity: 0 }}
148
+ animate={{ scale: 1, opacity: 1 }}
149
+ className="inline-flex items-center justify-center px-2.5 py-1 rounded-full text-xs font-bold text-white bg-red-600"
150
+ >
64
151
  Out of Stock
65
- </span>
152
+ </motion.span>
66
153
  )}
67
154
  </div>
68
155
 
69
156
  {/* Favorite Button */}
70
- <button
71
- onClick={handleFavorite}
72
- className="absolute top-4 right-4 p-2 bg-white/90 backdrop-blur-sm rounded-full shadow-lg opacity-0 group-hover:opacity-100 transition-all duration-300 hover:scale-110"
73
- >
74
- <Heart
75
- className={`w-5 h-5 ${isFavorited ? 'fill-red-500 text-red-500' : 'text-gray-700'}`}
76
- />
77
- </button>
78
-
79
- {/* Quick Add Button - Shows on hover */}
80
- {product.inStock && (
157
+ {showFavoriteButton && (
81
158
  <motion.button
82
- onClick={handleAddToCart}
83
- disabled={isAdding}
84
- className="absolute bottom-4 left-4 right-4 bg-primary-600 text-white py-3 rounded-xl font-medium opacity-0 group-hover:opacity-100 transition-all duration-300 hover:bg-primary-700 flex items-center justify-center gap-2 shadow-lg"
159
+ type="button"
160
+ onClick={handleFavorite}
161
+ className={
162
+ `absolute top-2 right-2 p-2 rounded-full z-10 transition-colors bg-white/90 backdrop-blur-sm shadow-md hover:shadow-lg ${isFavorite ? 'text-red-500' : 'text-primary-600 hover:text-red-500'} ${isHovered || isFavorite ? 'opacity-100' : 'opacity-0 group-hover:opacity-100'}`
163
+ }
164
+ whileHover={{ scale: 1.1 }}
85
165
  whileTap={{ scale: 0.95 }}
166
+ aria-label={isFavorite ? 'Remove from wishlist' : 'Add to wishlist'}
86
167
  >
87
- <ShoppingCart className="w-5 h-5" />
88
- {isAdding ? 'Adding...' : 'Quick Add'}
168
+ <Heart className={`w-5 h-5 ${isFavorite ? 'fill-current' : ''}`} />
89
169
  </motion.button>
90
170
  )}
171
+
172
+ </div>
173
+ <div className="absolute top-4 left-4 flex flex-col gap-2">
174
+ {product.inventoryCount === 0 && (
175
+ <span className="px-3 py-1 rounded-full text-sm font-bold bg-red-100 text-red-800">
176
+ Out of Stock
177
+ </span>
178
+ )}
91
179
  </div>
92
180
 
93
- {/* Content */}
181
+ {/* Favorite Button */}
182
+ {showFavoriteButton && (
183
+ <button
184
+ type="button"
185
+ onClick={handleFavorite}
186
+ className={`absolute top-2 right-2 p-2 rounded-full transition-colors ${isFavorite ? 'text-red-500' : 'text-gray-400 hover:text-red-500'
187
+ }`}
188
+ aria-label={isFavorite ? 'Remove from wishlist' : 'Add to wishlist'}
189
+ >
190
+ <Heart className={`w-5 h-5 ${isFavorite ? 'fill-current' : ''}`} />
191
+ </button>
192
+ )}
193
+
94
194
  <div className="p-4">
95
195
  {/* Category */}
96
- {product.category && (
196
+ {product.parentCategories && product.parentCategories?.length > 0 && (
97
197
  <p className="text-xs text-gray-500 uppercase tracking-wider mb-2">
98
- {product.category}
198
+ {product.parentCategories?.map((category) => category?.name).join(', ') || 'No categories'}
99
199
  </p>
100
200
  )}
101
201
 
102
- {/* Product Name */}
103
- <h3 className="text-lg font-semibold text-gray-900 mb-2 line-clamp-2 group-hover:text-primary-600 transition-colors">
104
- {product.name}
105
- </h3>
202
+ {/* Product Name and Variant */}
203
+ <div className="mb-2">
204
+ <h3 className="text-lg font-semibold text-gray-900 line-clamp-1 group-hover:text-primary-600 transition-colors">
205
+ {product.name}
206
+ </h3>
207
+ {product?.sku && (
208
+ <p className="text-xs text-gray-400 mt-1">SKU: {product.sku}</p>
209
+ )}
210
+ </div>
106
211
 
107
212
  {/* Price */}
108
213
  <div className="flex items-baseline gap-2">
109
- <span className="text-2xl font-bold text-gray-900">
110
- {formatPrice(product.price)}
111
- </span>
112
- {product.compareAtPrice && (
214
+ <div className="flex flex-col">
215
+ <span className="text-2xl font-bold text-gray-900">
216
+ {formatPrice(product.finalPrice)}
217
+ </span>
218
+ {product.inventoryCount > 0 && (
219
+ <span className="text-xs text-gray-500">
220
+ {product.inventoryCount > 0 ? "In Stock" : "Out of Stock"}
221
+ </span>
222
+ )}
223
+ </div>
224
+ {product.priceBeforeDiscount && product.priceBeforeDiscount > product.finalPrice && (
113
225
  <span className="text-sm text-gray-500 line-through">
114
- {formatPrice(product.compareAtPrice)}
226
+ {formatPrice(product.priceBeforeDiscount)}
115
227
  </span>
116
228
  )}
117
229
  </div>
118
230
  </div>
119
- </motion.div>
231
+ <div className="mt-auto p-4 pt-0">
232
+ <button
233
+ type="button"
234
+ onClick={(e) => {
235
+ e.stopPropagation();
236
+ router.push(`/products/${product._id}`);
237
+ }}
238
+ className={`w-full flex items-center justify-center rounded-md px-3 py-2 text-sm font-medium bg-primary-600 hover:bg-primary-700 text-white`}
239
+ >
240
+ View Product
241
+ </button>
242
+
243
+ {showFavoriteButton && (
244
+ <button
245
+ type="button"
246
+ onClick={handleFavorite}
247
+ className="mt-2 w-full flex items-center justify-center rounded-md border border-gray-300 bg-white px-3 py-2 text-sm font-medium text-primary-600 hover:bg-gray-50"
248
+ aria-label={isFavorite ? 'Remove from wishlist' : 'Add to wishlist'}
249
+ >
250
+ <Heart
251
+ className={`mr-2 h-4 w-4 ${isFavorite ? 'fill-red-500 text-red-500' : 'text-primary-600'}`}
252
+ />
253
+ {isFavorite ? 'Saved' : 'Save for later'}
254
+ </button>
255
+ )}
256
+ </div>
257
+ </motion.article>
120
258
  );
121
259
  }
122
-
@@ -1,5 +1,11 @@
1
+ 'use client';
2
+
1
3
  import React from 'react';
2
- import { motion } from 'framer-motion';
4
+ import dynamic from 'next/dynamic';
5
+
6
+ const MotionDiv = dynamic(() => import('framer-motion').then((mod) => mod.motion.div), {
7
+ ssr: false,
8
+ });
3
9
 
4
10
  interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
5
11
  variant?: 'primary' | 'secondary' | 'outline' | 'ghost';
@@ -17,7 +23,7 @@ export function Button({
17
23
  children,
18
24
  ...props
19
25
  }: ButtonProps) {
20
- const baseStyles = 'font-medium rounded-lg transition-all duration-200 inline-flex items-center justify-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed';
26
+ const baseStyles = 'font-medium rounded-lg transition-all duration-200 inline-flex items-center justify-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-primary-500';
21
27
 
22
28
  const variants = {
23
29
  primary: 'bg-primary-600 text-white hover:bg-primary-700 shadow-lg shadow-primary-500/30 hover:shadow-xl hover:shadow-primary-500/40',
@@ -33,7 +39,7 @@ export function Button({
33
39
  };
34
40
 
35
41
  return (
36
- <motion.div
42
+ <MotionDiv
37
43
  whileHover={{ scale: 1.02 }}
38
44
  whileTap={{ scale: 0.98 }}
39
45
  className="inline-block"
@@ -41,11 +47,13 @@ export function Button({
41
47
  <button
42
48
  className={`${baseStyles} ${variants[variant]} ${sizes[size]} ${className}`}
43
49
  disabled={disabled || isLoading}
50
+ aria-disabled={disabled || isLoading}
51
+ aria-busy={isLoading}
44
52
  {...props}
45
53
  >
46
54
  {isLoading ? (
47
55
  <>
48
- <svg className="animate-spin h-5 w-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
56
+ <svg className="animate-spin h-5 w-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" aria-hidden="true">
49
57
  <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
50
58
  <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
51
59
  </svg>
@@ -55,7 +63,7 @@ export function Button({
55
63
  children
56
64
  )}
57
65
  </button>
58
- </motion.div>
66
+ </MotionDiv>
59
67
  );
60
68
  }
61
69
 
@@ -0,0 +1,46 @@
1
+ import React from 'react';
2
+
3
+ // Base card component
4
+ interface CardProps extends React.HTMLAttributes<HTMLDivElement> {
5
+ children: React.ReactNode;
6
+ }
7
+
8
+ export function Card({ className = '', ...props }: CardProps) {
9
+ return (
10
+ <div
11
+ className={`rounded-lg border bg-white shadow-sm ${className}`}
12
+ {...props}
13
+ />
14
+ );
15
+ }
16
+
17
+ // Card Header component
18
+ interface CardHeaderProps extends React.HTMLAttributes<HTMLDivElement> {}
19
+
20
+ export function CardHeader({ className = '', ...props }: CardHeaderProps) {
21
+ return (
22
+ <div
23
+ className={`flex flex-col space-y-1.5 p-6 ${className}`}
24
+ {...props}
25
+ />
26
+ );
27
+ }
28
+
29
+ // Card Title component
30
+ interface CardTitleProps extends React.HTMLAttributes<HTMLHeadingElement> {}
31
+
32
+ export function CardTitle({ className = '', ...props }: CardTitleProps) {
33
+ return (
34
+ <h3
35
+ className={`text-2xl font-semibold leading-none tracking-tight ${className}`}
36
+ {...props}
37
+ />
38
+ );
39
+ }
40
+
41
+ // Card Content component
42
+ interface CardContentProps extends React.HTMLAttributes<HTMLDivElement> {}
43
+
44
+ export function CardContent({ className = '', ...props }: CardContentProps) {
45
+ return <div className={`p-6 pt-0 ${className}`} {...props} />;
46
+ }
@@ -0,0 +1,83 @@
1
+ import { useCallback, useEffect, useMemo, useState } from 'react';
2
+ import { Address, AddressesApi, CreateAddressDto, UpdateAddressDto } from '@/lib/Apis';
3
+ import { AXIOS_CONFIG } from '@/lib/Apis/wrapper';
4
+
5
+ interface UseAddressesReturn {
6
+ addresses: Address[];
7
+ defaultAddress: Address | null;
8
+ isLoading: boolean;
9
+ error: Error | null;
10
+ refresh: () => Promise<void>;
11
+ addAddress: (payload: CreateAddressDto) => Promise<Address>;
12
+ updateAddress: (id: string, payload: UpdateAddressDto) => Promise<Address>;
13
+ removeAddress: (id: string) => Promise<void>;
14
+ markAsDefault: (id: string) => Promise<Address>;
15
+ }
16
+
17
+ export function useAddresses(): UseAddressesReturn {
18
+ const [addresses, setAddresses] = useState<Address[]>([]);
19
+ const [isLoading, setIsLoading] = useState<boolean>(true);
20
+ const [error, setError] = useState<Error | null>(null);
21
+
22
+ const refresh = useCallback(async () => {
23
+ setIsLoading(true);
24
+ setError(null);
25
+ const response = await new AddressesApi(AXIOS_CONFIG).getMyAddresses();
26
+ setAddresses(response.data || []);
27
+ setIsLoading(false);
28
+ }, []);
29
+
30
+ useEffect(() => {
31
+ refresh();
32
+ }, [refresh]);
33
+
34
+ const sortedAddresses = useMemo(() => {
35
+ return [...addresses].sort((a, b) => {
36
+ if (a.isDefault === b.isDefault) {
37
+ return (b.updatedAt.toISOString() || '').localeCompare(a.updatedAt.toISOString() || '');
38
+ }
39
+ return a.isDefault ? -1 : 1;
40
+ });
41
+ }, [addresses]);
42
+
43
+ const defaultAddress = useMemo(
44
+ () => sortedAddresses.find((address) => address.isDefault) || null,
45
+ [sortedAddresses]
46
+ );
47
+
48
+ const addAddress = useCallback(async (payload: CreateAddressDto) => {
49
+ const response = await new AddressesApi(AXIOS_CONFIG).createAddressForUser(payload);
50
+ setAddresses((prev) => [...prev, response.data]);
51
+ return response.data;
52
+ }, []);
53
+
54
+ const updateAddress = useCallback(async (id: string, payload: UpdateAddressDto) => {
55
+ const response = await new AddressesApi(AXIOS_CONFIG).updateUserAddress(payload, id);
56
+ setAddresses((prev) => prev.map((address) => address.id === id ? response.data : address));
57
+ return response.data;
58
+ }, []);
59
+
60
+ const removeAddress = useCallback(async (id: string) => {
61
+ await new AddressesApi(AXIOS_CONFIG).deleteUserAddress(id);
62
+ setAddresses((prev) => prev.filter((address) => address.id !== id));
63
+ return
64
+ }, []);
65
+
66
+ const markAsDefault = useCallback(async (id: string) => {
67
+ const response = await new AddressesApi(AXIOS_CONFIG).updateDefaultAddress(id);
68
+ setAddresses((prev) => prev.map((address) => address.id === id ? response.data : address));
69
+ return response.data;
70
+ }, []);
71
+
72
+ return {
73
+ addresses: sortedAddresses,
74
+ defaultAddress,
75
+ isLoading,
76
+ error,
77
+ refresh,
78
+ addAddress,
79
+ updateAddress,
80
+ removeAddress,
81
+ markAsDefault,
82
+ };
83
+ }
@@ -1,9 +1,16 @@
1
1
  import { useState, useEffect, useCallback } from 'react';
2
- import { Order } from '@/lib/types';
3
- import { ordersApi } from '@/lib/api/orders';
2
+ import { OrdersApi } from '@/lib/Apis';
3
+ import { getApiConfiguration } from '@/lib/api-adapter';
4
+ import { PopulatedOrder } from '@/lib/Apis/models';
5
+ import { useAuth } from '@/providers/AuthProvider';
4
6
 
5
- export function useOrders(page: number = 1, limit: number = 10) {
6
- const [orders, setOrders] = useState<Order[]>([]);
7
+ export function useOrders(
8
+ page: number = 1,
9
+ limit: number = 10,
10
+ orderStatus?: string,
11
+ paymentStatus?: string
12
+ ) {
13
+ const [orders, setOrders] = useState<PopulatedOrder[]>([]);
7
14
  const [isLoading, setIsLoading] = useState(true);
8
15
  const [error, setError] = useState<Error | null>(null);
9
16
  const [pagination, setPagination] = useState({
@@ -12,20 +19,36 @@ export function useOrders(page: number = 1, limit: number = 10) {
12
19
  total: 0,
13
20
  totalPages: 0,
14
21
  });
22
+ const { user } = useAuth();
23
+ const resolvedUserId = (user as any)?._id || (user as any)?.id;
15
24
 
16
25
  const fetchOrders = useCallback(async () => {
17
26
  setIsLoading(true);
18
27
  setError(null);
19
28
  try {
20
- const response = await ordersApi.getOrders(page, limit);
21
- setOrders(response.data);
22
- setPagination(response.pagination);
29
+ const response = await new OrdersApi(getApiConfiguration()).getAllOrders(
30
+ paymentStatus && paymentStatus !== 'All' ? paymentStatus : undefined,
31
+ orderStatus && orderStatus !== 'All' ? orderStatus : undefined,
32
+ page,
33
+ limit,
34
+ undefined,
35
+ undefined,
36
+ undefined,
37
+ resolvedUserId
38
+ );
39
+ setOrders(response.data.data || []);
40
+ setPagination({
41
+ page: response.data.page || page,
42
+ limit: response.data.limit || limit,
43
+ total: response.data.total ?? 0,
44
+ totalPages: response.data.totalPages || 1,
45
+ });
23
46
  } catch (err) {
24
47
  setError(err as Error);
25
48
  } finally {
26
49
  setIsLoading(false);
27
50
  }
28
- }, [page, limit]);
51
+ }, [page, limit, resolvedUserId, orderStatus, paymentStatus]);
29
52
 
30
53
  useEffect(() => {
31
54
  fetchOrders();
@@ -41,7 +64,7 @@ export function useOrders(page: number = 1, limit: number = 10) {
41
64
  }
42
65
 
43
66
  export function useOrder(id: string) {
44
- const [order, setOrder] = useState<Order | null>(null);
67
+ const [order, setOrder] = useState<PopulatedOrder | null>(null);
45
68
  const [isLoading, setIsLoading] = useState(true);
46
69
  const [error, setError] = useState<Error | null>(null);
47
70
 
@@ -49,10 +72,8 @@ export function useOrder(id: string) {
49
72
  setIsLoading(true);
50
73
  setError(null);
51
74
  try {
52
- const response = await ordersApi.getOrder(id);
53
- if (response.success) {
54
- setOrder(response.data);
55
- }
75
+ const response = await new OrdersApi(getApiConfiguration()).getSingleOrder(id);
76
+ setOrder(response.data);
56
77
  } catch (err) {
57
78
  setError(err as Error);
58
79
  } finally {
@@ -70,7 +91,7 @@ export function useOrder(id: string) {
70
91
  }
71
92
 
72
93
  export function useCurrentOrders() {
73
- const [orders, setOrders] = useState<Order[]>([]);
94
+ const [orders, setOrders] = useState<PopulatedOrder[]>([]);
74
95
  const [isLoading, setIsLoading] = useState(true);
75
96
  const [error, setError] = useState<Error | null>(null);
76
97
 
@@ -78,10 +99,8 @@ export function useCurrentOrders() {
78
99
  setIsLoading(true);
79
100
  setError(null);
80
101
  try {
81
- const response = await ordersApi.getCurrentOrders();
82
- if (response.success) {
83
- setOrders(response.data);
84
- }
102
+ const response = await new OrdersApi(getApiConfiguration()).getUserOrders();
103
+ setOrders(response.data || []);
85
104
  } catch (err) {
86
105
  setError(err as Error);
87
106
  } finally {
@@ -95,4 +114,3 @@ export function useCurrentOrders() {
95
114
 
96
115
  return { orders, isLoading, error, refetch: fetchCurrentOrders };
97
116
  }
98
-