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
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React, { useState } from 'react';
|
|
4
|
+
import { motion } from 'framer-motion';
|
|
5
|
+
import { useForm } from 'react-hook-form';
|
|
6
|
+
import { zodResolver } from '@hookform/resolvers/zod';
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
import Link from 'next/link';
|
|
9
|
+
import { ArrowLeft, Mail, Send } from 'lucide-react';
|
|
10
|
+
import { Input } from '@/components/ui/Input';
|
|
11
|
+
import { Button } from '@/components/ui/Button';
|
|
12
|
+
import { AuthApi } from '@/lib/Apis/apis/auth-api';
|
|
13
|
+
import { AXIOS_CONFIG } from '@/lib/Apis/sharedConfig';
|
|
14
|
+
import { useBasePath } from '@/providers/BasePathProvider';
|
|
15
|
+
|
|
16
|
+
const forgotPasswordSchema = z.object({
|
|
17
|
+
email: z.string().email('Enter a valid email address'),
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
type ForgotPasswordFormData = z.infer<typeof forgotPasswordSchema>;
|
|
21
|
+
|
|
22
|
+
export function ForgotPasswordScreen() {
|
|
23
|
+
const { buildPath } = useBasePath();
|
|
24
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
25
|
+
const [status, setStatus] = useState<{ type: 'success' | 'error'; message: string } | null>(
|
|
26
|
+
null
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
const {
|
|
30
|
+
register,
|
|
31
|
+
handleSubmit,
|
|
32
|
+
formState: { errors },
|
|
33
|
+
} = useForm<ForgotPasswordFormData>({
|
|
34
|
+
resolver: zodResolver(forgotPasswordSchema),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const onSubmit = async (data: ForgotPasswordFormData) => {
|
|
38
|
+
setIsSubmitting(true);
|
|
39
|
+
setStatus(null);
|
|
40
|
+
try {
|
|
41
|
+
const authApi = new AuthApi(AXIOS_CONFIG);
|
|
42
|
+
await authApi.sendForgetPasswordEmail({ email: data.email });
|
|
43
|
+
setStatus({
|
|
44
|
+
type: 'success',
|
|
45
|
+
message: 'Password reset link sent! Check your email inbox.',
|
|
46
|
+
});
|
|
47
|
+
} catch (error: any) {
|
|
48
|
+
setStatus({
|
|
49
|
+
type: 'error',
|
|
50
|
+
message: error?.response?.data?.message || 'Unable to send reset email. Please try again.',
|
|
51
|
+
});
|
|
52
|
+
} finally {
|
|
53
|
+
setIsSubmitting(false);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<div className="min-h-screen bg-linear-to-b from-[#F8FAFC] to-[#EBF4FB]">
|
|
59
|
+
<div className="grid min-h-screen overflow-hidden pb-12">
|
|
60
|
+
<motion.section
|
|
61
|
+
initial={{ opacity: 0, x: 24 }}
|
|
62
|
+
animate={{ opacity: 1, x: 0 }}
|
|
63
|
+
transition={{ duration: 0.4 }}
|
|
64
|
+
className="flex items-center justify-center px-6 py-12 lg:px-16"
|
|
65
|
+
>
|
|
66
|
+
<div className="w-full max-w-lg space-y-10 text-center">
|
|
67
|
+
<div className="space-y-2">
|
|
68
|
+
<Mail
|
|
69
|
+
strokeWidth={2}
|
|
70
|
+
className="h-16 w-16 mx-auto text-white rounded-full bg-secondary m-2 mb-4 px-4"
|
|
71
|
+
/>
|
|
72
|
+
<h2 className="text-4xl text-secondary">Forgot Password?</h2>
|
|
73
|
+
<p className="text-sm text-muted">
|
|
74
|
+
No worries! Enter your email and we'll send you reset instructions.
|
|
75
|
+
</p>
|
|
76
|
+
</div>
|
|
77
|
+
|
|
78
|
+
<form
|
|
79
|
+
onSubmit={handleSubmit(onSubmit)}
|
|
80
|
+
className="space-y-6 rounded-3xl border bg-white p-8"
|
|
81
|
+
style={{
|
|
82
|
+
boxShadow: '0px 4px 6px -4px #0000001A, 0px 10px 15px -3px #0000001A',
|
|
83
|
+
}}
|
|
84
|
+
>
|
|
85
|
+
{status && (
|
|
86
|
+
<div
|
|
87
|
+
className={`flex items-start gap-2 rounded-2xl border px-4 py-3 text-sm ${
|
|
88
|
+
status.type === 'success'
|
|
89
|
+
? 'border-green-200 bg-green-50 text-green-800'
|
|
90
|
+
: 'border-red-200 bg-red-50 text-red-700'
|
|
91
|
+
}`}
|
|
92
|
+
>
|
|
93
|
+
<span className="mt-[2px] text-base">{status.type === 'success' ? '✔' : '!'}</span>
|
|
94
|
+
<span>{status.message}</span>
|
|
95
|
+
</div>
|
|
96
|
+
)}
|
|
97
|
+
|
|
98
|
+
<div className="text-start text-secondary">
|
|
99
|
+
<h2 className="text-sm text-secondary mb-3">
|
|
100
|
+
Email Address <span className="text-primary-500">*</span>
|
|
101
|
+
</h2>
|
|
102
|
+
<Input
|
|
103
|
+
type="email"
|
|
104
|
+
placeholder="you@example.com"
|
|
105
|
+
{...register('email')}
|
|
106
|
+
error={errors.email?.message}
|
|
107
|
+
className="text-secondary"
|
|
108
|
+
/>
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
<button
|
|
112
|
+
type="submit"
|
|
113
|
+
disabled={isSubmitting}
|
|
114
|
+
className="w-full bg-secondary hover:opacity-80 text-white font-medium py-3 px-4 rounded-lg transition-colors disabled:opacity-70 disabled:cursor-not-allowed flex items-center justify-center gap-2"
|
|
115
|
+
>
|
|
116
|
+
{isSubmitting ? (
|
|
117
|
+
'Sending...'
|
|
118
|
+
) : (
|
|
119
|
+
<>
|
|
120
|
+
<Send className="h-4 w-4" />
|
|
121
|
+
Send Reset Link
|
|
122
|
+
</>
|
|
123
|
+
)}
|
|
124
|
+
</button>
|
|
125
|
+
|
|
126
|
+
<div className="pt-4 border-t border-slate-200">
|
|
127
|
+
<Link
|
|
128
|
+
href={buildPath('/login')}
|
|
129
|
+
className="flex items-center justify-center gap-2 text-sm font-medium text-primary transition hover:opacity-80"
|
|
130
|
+
>
|
|
131
|
+
<ArrowLeft className="h-4 w-4" />
|
|
132
|
+
Back to login
|
|
133
|
+
</Link>
|
|
134
|
+
</div>
|
|
135
|
+
</form>
|
|
136
|
+
|
|
137
|
+
<div className="mt-4">
|
|
138
|
+
<p className="text-muted">
|
|
139
|
+
Don't have an account?{' '}
|
|
140
|
+
<Link
|
|
141
|
+
href={buildPath('/register')}
|
|
142
|
+
className="font-medium text-primary transition hover:opacity-90"
|
|
143
|
+
>
|
|
144
|
+
Sign up
|
|
145
|
+
</Link>
|
|
146
|
+
</p>
|
|
147
|
+
</div>
|
|
148
|
+
</div>
|
|
149
|
+
</motion.section>
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
);
|
|
153
|
+
}
|
|
@@ -31,7 +31,6 @@ import { useAuth } from '@/providers/AuthProvider';
|
|
|
31
31
|
import { formatDate, formatPrice } from '@/lib/utils/format';
|
|
32
32
|
import { useNotification } from '@/providers/NotificationProvider';
|
|
33
33
|
import { useRouter } from 'next/navigation';
|
|
34
|
-
import { ProductVariantsApi } from '@/lib/Apis/apis/product-variants-api';
|
|
35
34
|
import { AXIOS_CONFIG } from '@/lib/Apis/wrapper';
|
|
36
35
|
import { useWishlist } from '@/providers/WishlistProvider';
|
|
37
36
|
import { ProductsApi, ProductVariant, ProductVariantInventoryStatusEnum } from '@/lib/Apis';
|
|
@@ -84,13 +83,13 @@ export function ProductDetailScreen({ productId }: ProductDetailScreenProps) {
|
|
|
84
83
|
|
|
85
84
|
if (!productData) return null;
|
|
86
85
|
|
|
87
|
-
if (productData.
|
|
86
|
+
if (productData.variants?.length && selectedVariant) {
|
|
88
87
|
return {
|
|
89
88
|
...productData,
|
|
90
89
|
price: selectedVariant.finalPrice,
|
|
91
90
|
inStock: selectedVariant.isActive,
|
|
92
91
|
sku: selectedVariant.sku || productData.sku,
|
|
93
|
-
variantId: selectedVariant.
|
|
92
|
+
variantId: selectedVariant._id,
|
|
94
93
|
variantName: selectedVariant.name
|
|
95
94
|
};
|
|
96
95
|
}
|
|
@@ -99,8 +98,8 @@ export function ProductDetailScreen({ productId }: ProductDetailScreenProps) {
|
|
|
99
98
|
}, [productData, selectedVariant, initialProductData]);
|
|
100
99
|
|
|
101
100
|
const getVariantImages = () => {
|
|
102
|
-
if (selectedVariant?.
|
|
103
|
-
return selectedVariant.
|
|
101
|
+
if (selectedVariant?.media?.length) {
|
|
102
|
+
return selectedVariant.media.map((media: any) => ({
|
|
104
103
|
src: media.file,
|
|
105
104
|
width: 800,
|
|
106
105
|
height: 1000,
|
|
@@ -111,8 +110,8 @@ export function ProductDetailScreen({ productId }: ProductDetailScreenProps) {
|
|
|
111
110
|
}
|
|
112
111
|
|
|
113
112
|
// Fallback to product media if no variant media
|
|
114
|
-
if (product?.
|
|
115
|
-
return product.
|
|
113
|
+
if (product?.media?.length) {
|
|
114
|
+
return product.media.map((media: any) => ({
|
|
116
115
|
src: media.file,
|
|
117
116
|
width: 800,
|
|
118
117
|
height: 1000,
|
|
@@ -122,14 +121,14 @@ export function ProductDetailScreen({ productId }: ProductDetailScreenProps) {
|
|
|
122
121
|
}));
|
|
123
122
|
}
|
|
124
123
|
|
|
125
|
-
if (product?.
|
|
126
|
-
return product.
|
|
127
|
-
src:
|
|
124
|
+
if (product?.media?.length) {
|
|
125
|
+
return product.media.map((media: any) => ({
|
|
126
|
+
src: media.file,
|
|
128
127
|
width: 800,
|
|
129
128
|
height: 1000,
|
|
130
129
|
alt: product?.name || 'Product image',
|
|
131
|
-
url:
|
|
132
|
-
file:
|
|
130
|
+
url: media.file,
|
|
131
|
+
file: media.file,
|
|
133
132
|
type: 'image' as const
|
|
134
133
|
}));
|
|
135
134
|
}
|
|
@@ -189,26 +188,19 @@ export function ProductDetailScreen({ productId }: ProductDetailScreenProps) {
|
|
|
189
188
|
}, [product?.productVariants]);
|
|
190
189
|
|
|
191
190
|
useEffect(() => {
|
|
192
|
-
if (!product?.
|
|
191
|
+
if (!product?._id) return;
|
|
193
192
|
|
|
194
|
-
let isMounted = true;
|
|
195
193
|
const fetchRelated = async () => {
|
|
196
194
|
try {
|
|
197
|
-
const response = await new ProductsApi(AXIOS_CONFIG).getRelatedProducts(product.
|
|
198
|
-
|
|
199
|
-
limit: 4
|
|
200
|
-
}
|
|
201
|
-
});
|
|
195
|
+
const response = await new ProductsApi(AXIOS_CONFIG).getRelatedProducts(product._id, 4);
|
|
196
|
+
console.log("response.data", response.data);
|
|
202
197
|
setRelatedProducts(response.data || []);
|
|
203
198
|
} catch (error: any) {
|
|
204
199
|
console.error('Failed to fetch related products', error);
|
|
205
200
|
}
|
|
206
201
|
};
|
|
207
202
|
fetchRelated();
|
|
208
|
-
|
|
209
|
-
isMounted = false;
|
|
210
|
-
};
|
|
211
|
-
}, [product?.id]);
|
|
203
|
+
}, [product?._id]);
|
|
212
204
|
|
|
213
205
|
|
|
214
206
|
const handleVariantSelect = async (variant: ProductVariant) => {
|
|
@@ -232,9 +224,9 @@ export function ProductDetailScreen({ productId }: ProductDetailScreenProps) {
|
|
|
232
224
|
setIsAddingToCart(true);
|
|
233
225
|
try {
|
|
234
226
|
await addToCart(
|
|
235
|
-
product.
|
|
227
|
+
product._id,
|
|
236
228
|
quantity,
|
|
237
|
-
selectedVariant.
|
|
229
|
+
selectedVariant._id
|
|
238
230
|
);
|
|
239
231
|
notification.success(
|
|
240
232
|
'Added to cart',
|
|
@@ -256,7 +248,7 @@ export function ProductDetailScreen({ productId }: ProductDetailScreenProps) {
|
|
|
256
248
|
// Initialize isFavorited based on whether the product is in the wishlist
|
|
257
249
|
useEffect(() => {
|
|
258
250
|
if (product) {
|
|
259
|
-
setIsFavorited(isInWishlist(product.
|
|
251
|
+
setIsFavorited(isInWishlist(product._id));
|
|
260
252
|
}
|
|
261
253
|
}, [product, isInWishlist]);
|
|
262
254
|
|
|
@@ -265,7 +257,7 @@ export function ProductDetailScreen({ productId }: ProductDetailScreenProps) {
|
|
|
265
257
|
|
|
266
258
|
try {
|
|
267
259
|
if (isFavorited) {
|
|
268
|
-
await removeFromWishlist(product.
|
|
260
|
+
await removeFromWishlist(product._id);
|
|
269
261
|
notification.info(
|
|
270
262
|
'Removed from saved items',
|
|
271
263
|
`${product.name} was removed from your saved items.`
|
|
@@ -362,7 +354,7 @@ export function ProductDetailScreen({ productId }: ProductDetailScreenProps) {
|
|
|
362
354
|
<section className="rounded-3xl ">
|
|
363
355
|
<div className="space-y-6">
|
|
364
356
|
<motion.div
|
|
365
|
-
key={selectedVariant?.
|
|
357
|
+
key={selectedVariant?._id || 'default'}
|
|
366
358
|
initial={{ opacity: 0.4, scale: 0.98 }}
|
|
367
359
|
animate={{ opacity: 1, scale: 1 }}
|
|
368
360
|
transition={{ duration: 0.35 }}
|
|
@@ -447,7 +439,7 @@ export function ProductDetailScreen({ productId }: ProductDetailScreenProps) {
|
|
|
447
439
|
{/* Category Path */}
|
|
448
440
|
<div className="mb-4">
|
|
449
441
|
<p className="font-['Poppins',sans-serif] text-[12px] text-primary uppercase tracking-wide font-medium mb-2">
|
|
450
|
-
{product.brand} • {product.
|
|
442
|
+
{product.brand} • {product.categoryIds?.[0]?.name || 'Uncategorized'}
|
|
451
443
|
</p>
|
|
452
444
|
<h1 className="text-3xl font-['Poppins',sans-serif] font-semibold text-secondary tracking-[-1.5px] mb-3">
|
|
453
445
|
{selectedVariant?.name || product.name}
|
|
@@ -469,38 +461,37 @@ export function ProductDetailScreen({ productId }: ProductDetailScreenProps) {
|
|
|
469
461
|
{product.rating} ({product.reviewCount ? product.reviewCount : 0} reviews)
|
|
470
462
|
</span>
|
|
471
463
|
</div>
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
</span>
|
|
482
|
-
<div className="px-3 py-1 rounded-full bg-[#E67E50]/10">
|
|
483
|
-
<span className="font-['Poppins',sans-serif] font-semibold text-[13px] text-[#E67E50]">
|
|
484
|
-
Save ${formatPrice(variantComparePrice - variantPrice)}
|
|
464
|
+
{selectedVariant && (
|
|
465
|
+
<div className="flex items-center gap-3 mb-6 pb-6 border-b-2 border-gray-100">
|
|
466
|
+
<span className="font-['Poppins',sans-serif] font-bold text-[40px] text-[#E67E50]">
|
|
467
|
+
${variantPrice.toFixed(2)}
|
|
468
|
+
</span>
|
|
469
|
+
{variantComparePrice && variantComparePrice > variantPrice && (
|
|
470
|
+
<>
|
|
471
|
+
<span className="font-['Poppins',sans-serif] text-[24px] text-muted line-through">
|
|
472
|
+
${variantComparePrice.toFixed(2)}
|
|
485
473
|
</span>
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
474
|
+
<div className="px-3 py-1 rounded-full bg-[#E67E50]/10">
|
|
475
|
+
<span className="font-['Poppins',sans-serif] font-semibold text-[13px] text-[#E67E50]">
|
|
476
|
+
Save ${formatPrice(variantComparePrice - variantPrice)}
|
|
477
|
+
</span>
|
|
478
|
+
</div>
|
|
479
|
+
</>
|
|
480
|
+
)}
|
|
481
|
+
</div>
|
|
482
|
+
)}
|
|
491
483
|
{/* Stock Status */}
|
|
492
484
|
{selectedVariant && (
|
|
493
485
|
<div className="mb-6">
|
|
494
486
|
<div className="flex items-center gap-2 mb-2">
|
|
495
|
-
{selectedVariant.inventoryStatus === ProductVariantInventoryStatusEnum.OUTOFSTOCK
|
|
496
|
-
selectedVariant.inventoryStatus === ProductVariantInventoryStatusEnum.LOWSTOCK ? (
|
|
487
|
+
{selectedVariant.inventoryStatus === ProductVariantInventoryStatusEnum.OUTOFSTOCK ? (
|
|
497
488
|
<>
|
|
498
489
|
<div className="size-3 rounded-full bg-red-500" />
|
|
499
490
|
<span className="font-['Poppins',sans-serif] text-[13px] text-red-600 font-medium">
|
|
500
491
|
Out of Stock
|
|
501
492
|
</span>
|
|
502
493
|
</>
|
|
503
|
-
) : selectedVariant.inventoryCount <= 10 ? (
|
|
494
|
+
) : selectedVariant.inventoryStatus === ProductVariantInventoryStatusEnum.LOWSTOCK || selectedVariant.inventoryCount <= 10 ? (
|
|
504
495
|
<>
|
|
505
496
|
<div className="size-3 rounded-full bg-primary animate-pulse" />
|
|
506
497
|
<span className="font-['Poppins',sans-serif] text-[13px] text-primary font-medium">
|
|
@@ -518,7 +509,7 @@ export function ProductDetailScreen({ productId }: ProductDetailScreenProps) {
|
|
|
518
509
|
|
|
519
510
|
</div>
|
|
520
511
|
<p className="font-['Poppins',sans-serif] text-[12px] text-muted">
|
|
521
|
-
SKU: {selectedVariant?.sku}
|
|
512
|
+
SKU: {selectedVariant?.sku || product?.sku}
|
|
522
513
|
</p>
|
|
523
514
|
</div>
|
|
524
515
|
)}
|
|
@@ -532,21 +523,21 @@ export function ProductDetailScreen({ productId }: ProductDetailScreenProps) {
|
|
|
532
523
|
)}
|
|
533
524
|
|
|
534
525
|
{/* Variant Selector with Images */}
|
|
535
|
-
{product?.
|
|
526
|
+
{product?.variants && product.variants.length > 0 && (
|
|
536
527
|
<div className="mb-6">
|
|
537
528
|
<h3 className="font-['Poppins',sans-serif] font-semibold text-[14px] text-secondary mb-3">
|
|
538
529
|
Select Variant
|
|
539
530
|
</h3>
|
|
540
531
|
<div className="flex flex-wrap gap-3">
|
|
541
|
-
{product.
|
|
542
|
-
const isSelected = selectedVariant?.
|
|
543
|
-
const variantImage = variant.
|
|
544
|
-
|| product.
|
|
532
|
+
{product.variants.map((variant: ProductVariant) => {
|
|
533
|
+
const isSelected = selectedVariant?._id === variant._id;
|
|
534
|
+
const variantImage = variant.media?.[0]?.file
|
|
535
|
+
|| product.media?.[0]?.file
|
|
545
536
|
|| product.images?.[0]
|
|
546
537
|
|| '/placeholder-product.jpg';
|
|
547
538
|
return (
|
|
548
539
|
<button
|
|
549
|
-
key={variant.
|
|
540
|
+
key={variant._id}
|
|
550
541
|
type="button"
|
|
551
542
|
onClick={() => handleVariantSelect(variant)}
|
|
552
543
|
className={`flex items-start gap-3 px-4 py-2.5 rounded-xl border-2 transition-all ${isSelected
|
|
@@ -623,7 +614,7 @@ export function ProductDetailScreen({ productId }: ProductDetailScreenProps) {
|
|
|
623
614
|
<button
|
|
624
615
|
className="flex-1 font-['Poppins',sans-serif] font-medium text-[14px] px-8 py-4 rounded-full transition-all duration-300 flex items-center justify-center gap-3 bg-[#E67E50] text-white hover:bg-[#d66f45] hover:shadow-lg disabled:opacity-50 disabled:cursor-not-allowed"
|
|
625
616
|
onClick={handleAddToCart}
|
|
626
|
-
disabled={!selectedVariant || selectedVariant.inventoryStatus === ProductVariantInventoryStatusEnum.OUTOFSTOCK}
|
|
617
|
+
disabled={!selectedVariant || selectedVariant.inventoryStatus === ProductVariantInventoryStatusEnum.OUTOFSTOCK || isAddingToCart}
|
|
627
618
|
>
|
|
628
619
|
{isAddingToCart ? (
|
|
629
620
|
<>
|
|
@@ -631,7 +622,7 @@ export function ProductDetailScreen({ productId }: ProductDetailScreenProps) {
|
|
|
631
622
|
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
|
632
623
|
<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>
|
|
633
624
|
</svg>
|
|
634
|
-
|
|
625
|
+
Adding...
|
|
635
626
|
</>
|
|
636
627
|
) : (
|
|
637
628
|
<>
|
|
@@ -787,7 +778,7 @@ export function ProductDetailScreen({ productId }: ProductDetailScreenProps) {
|
|
|
787
778
|
{relatedProducts.map(relatedProduct => (
|
|
788
779
|
|
|
789
780
|
<ProductCard
|
|
790
|
-
key={relatedProduct.
|
|
781
|
+
key={relatedProduct._id}
|
|
791
782
|
product={relatedProduct}
|
|
792
783
|
// viewMode="grid"
|
|
793
784
|
onClickProduct={(item) => {
|
|
@@ -25,8 +25,8 @@ import { useBasePath } from '@/providers/BasePathProvider';
|
|
|
25
25
|
|
|
26
26
|
const registerSchema = z
|
|
27
27
|
.object({
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
firstname: z.string().min(2, 'First name is required'),
|
|
29
|
+
lastname: z.string().min(2, 'Last name is required'),
|
|
30
30
|
email: z.string().email('Enter a valid email'),
|
|
31
31
|
phone: z.string().optional(),
|
|
32
32
|
password: z.string().min(6, 'Password must be at least 6 characters'),
|
|
@@ -108,44 +108,44 @@ export function RegisterScreen() {
|
|
|
108
108
|
onSubmit={handleSubmit(onSubmit)}
|
|
109
109
|
className="text-start space-y-6 rounded-3xl border border-slate-100 bg-white p-8 shadow-lg shadow-primary-50"
|
|
110
110
|
style={{
|
|
111
|
-
|
|
112
|
-
|
|
111
|
+
boxShadow: '0px 4px 6px -4px #0000001A, 0px 10px 15px -3px #0000001A',
|
|
112
|
+
}}
|
|
113
113
|
>
|
|
114
114
|
<div className="grid gap-4 md:grid-cols-2 text-start">
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
115
|
+
<div>
|
|
116
|
+
<h2 className="text-sm text-secondary mb-3">First name <span className='text-primary-500'>*</span></h2>
|
|
117
|
+
<Input
|
|
118
|
+
placeholder="Alex"
|
|
119
|
+
{...register('firstname')}
|
|
120
|
+
error={errors.firstname?.message}
|
|
121
|
+
/>
|
|
122
122
|
</div>
|
|
123
123
|
<div>
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
124
|
+
<h2 className="text-sm text-secondary mb-3">Last name <span className='text-primary-500'>*</span></h2>
|
|
125
|
+
<Input
|
|
126
|
+
placeholder="Morgan"
|
|
127
|
+
{...register('lastname')}
|
|
128
|
+
error={errors.lastname?.message}
|
|
129
|
+
/>
|
|
130
130
|
</div>
|
|
131
131
|
</div>
|
|
132
132
|
<div>
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
133
|
+
<h2 className="text-sm text-secondary mb-3">Email Address <span className='text-primary-500'>*</span></h2>
|
|
134
|
+
<Input
|
|
135
|
+
type="email"
|
|
136
|
+
placeholder="you@example.com"
|
|
137
|
+
{...register('email')}
|
|
138
|
+
error={errors.email?.message}
|
|
139
|
+
/>
|
|
140
140
|
</div>
|
|
141
141
|
<div>
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
142
|
+
<h2 className="text-sm text-secondary mb-3">Phone Number</h2>
|
|
143
|
+
<Input
|
|
144
|
+
type="tel"
|
|
145
|
+
placeholder="+1 (555) 123-4567"
|
|
146
|
+
{...register('phone')}
|
|
147
|
+
error={errors.phone?.message}
|
|
148
|
+
/>
|
|
149
149
|
</div>
|
|
150
150
|
<div className="relative">
|
|
151
151
|
|