hey-pharmacist-ecommerce 1.1.28 → 1.1.29
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 +344 -640
- package/dist/index.d.ts +344 -640
- package/dist/index.js +1807 -838
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1807 -840
- 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/useWishlistProducts.ts +4 -5
- package/src/index.ts +2 -0
- package/src/lib/Apis/api.ts +0 -1
- package/src/lib/Apis/apis/auth-api.ts +18 -29
- package/src/lib/Apis/apis/products-api.ts +845 -405
- 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-product-dto.ts +30 -23
- package/src/lib/Apis/models/create-sub-category-dto.ts +6 -0
- package/src/lib/Apis/models/create-variant-dto.ts +29 -29
- package/src/lib/Apis/models/index.ts +5 -7
- package/src/lib/Apis/models/paginated-products-dto.ts +6 -6
- package/src/lib/Apis/models/product-summary.ts +69 -0
- package/src/lib/Apis/models/product-variant.ts +34 -65
- 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/single-product-media.ts +0 -12
- package/src/lib/Apis/models/sub-category.ts +6 -12
- package/src/lib/Apis/models/update-product-dto.ts +30 -19
- 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} +51 -45
- package/src/lib/Apis/models/{shallow-parent-category-dto.ts → variant-id-inventory-body.ts} +5 -11
- 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 +40 -11
- 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 +202 -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
- package/src/lib/Apis/models/table-dto.ts +0 -34
|
@@ -83,3 +83,56 @@ export function clearAuthToken(): void {
|
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
/**
|
|
87
|
+
* Change user password
|
|
88
|
+
* Custom implementation to fix the auto-generated API which incorrectly sends passwords as headers
|
|
89
|
+
*/
|
|
90
|
+
export async function changePassword(oldPassword: string, newPassword: string): Promise<void> {
|
|
91
|
+
if (!currentConfig) {
|
|
92
|
+
throw new Error('API adapter not initialized.');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const token = getAuthToken();
|
|
96
|
+
if (!token) {
|
|
97
|
+
throw new Error('User is not authenticated');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const url = `${currentConfig.apiBaseUrl}/auth/change-password`;
|
|
101
|
+
|
|
102
|
+
console.log('Change Password Request:', {
|
|
103
|
+
url,
|
|
104
|
+
storeId: currentConfig.storeId,
|
|
105
|
+
hasToken: !!token,
|
|
106
|
+
tokenPrefix: token.substring(0, 20) + '...',
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const response = await fetch(url, {
|
|
110
|
+
method: 'POST',
|
|
111
|
+
headers: {
|
|
112
|
+
'Content-Type': 'application/json',
|
|
113
|
+
'X-Store-Key': currentConfig.storeId,
|
|
114
|
+
'Authorization': `Bearer ${token}`,
|
|
115
|
+
},
|
|
116
|
+
body: JSON.stringify({
|
|
117
|
+
oldPassword,
|
|
118
|
+
newPassword,
|
|
119
|
+
}),
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
console.log('Change Password Response:', {
|
|
123
|
+
status: response.status,
|
|
124
|
+
statusText: response.statusText,
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
if (!response.ok) {
|
|
128
|
+
const errorData = await response.json().catch(() => ({}));
|
|
129
|
+
console.error('Change Password Error:', errorData);
|
|
130
|
+
throw {
|
|
131
|
+
response: {
|
|
132
|
+
data: errorData,
|
|
133
|
+
status: response.status,
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
@@ -8,7 +8,7 @@ export const addressSchema = z.object({
|
|
|
8
8
|
state: z.string().min(2, 'State is required'),
|
|
9
9
|
zip: z.string().min(4, 'ZIP code is required'),
|
|
10
10
|
country: z.string().min(2, 'Country is required'),
|
|
11
|
-
phone: z.string().min(10, 'Phone number
|
|
11
|
+
phone: z.string().min(10, 'Phone number must be at least 10 characters').optional().or(z.literal('')),
|
|
12
12
|
});
|
|
13
13
|
|
|
14
14
|
export type AddressFormData = z.infer<typeof addressSchema>;
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { Product } from '@/lib/Apis';
|
|
4
4
|
import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react';
|
|
5
5
|
import { useNotification } from './NotificationProvider';
|
|
6
6
|
|
|
7
7
|
interface FavoritesContextType {
|
|
8
8
|
favorites: string[];
|
|
9
9
|
isFavorite: (productId: string) => boolean;
|
|
10
|
-
toggleFavorite: (product:
|
|
11
|
-
addToFavorites: (product:
|
|
10
|
+
toggleFavorite: (product: Product) => void;
|
|
11
|
+
addToFavorites: (product: Product) => void;
|
|
12
12
|
removeFromFavorites: (productId: string) => void;
|
|
13
13
|
}
|
|
14
14
|
|
|
@@ -53,7 +53,7 @@ export function FavoritesProvider({ children }: { children: ReactNode }) {
|
|
|
53
53
|
return favorites.includes(productId);
|
|
54
54
|
};
|
|
55
55
|
|
|
56
|
-
const addToFavorites = (product:
|
|
56
|
+
const addToFavorites = (product: Product) => {
|
|
57
57
|
if (!favorites.includes(product.id)) {
|
|
58
58
|
setFavorites(prev => [...prev, product.id]);
|
|
59
59
|
notification.success(
|
|
@@ -71,7 +71,7 @@ export function FavoritesProvider({ children }: { children: ReactNode }) {
|
|
|
71
71
|
);
|
|
72
72
|
};
|
|
73
73
|
|
|
74
|
-
const toggleFavorite = (product:
|
|
74
|
+
const toggleFavorite = (product: Product) => {
|
|
75
75
|
if (isFavorite(product.id)) {
|
|
76
76
|
removeFromFavorites(product.id);
|
|
77
77
|
} else {
|
|
@@ -5,12 +5,12 @@ import { WishlistApi } from '@/lib/Apis/apis/wishlist-api';
|
|
|
5
5
|
import { AXIOS_CONFIG } from '@/lib/Apis/wrapper';
|
|
6
6
|
import { useAuth } from './AuthProvider';
|
|
7
7
|
import { useMemo } from 'react';
|
|
8
|
-
import {
|
|
8
|
+
import { ProductsApi, Wishlist, Product } from '@/lib/Apis';
|
|
9
9
|
import { useNotification } from './NotificationProvider';
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
interface WishlistContextType extends Wishlist {
|
|
13
|
-
addToWishlist: (product:
|
|
13
|
+
addToWishlist: (product: Product) => Promise<void>;
|
|
14
14
|
removeFromWishlist: (productId: string) => Promise<void>;
|
|
15
15
|
isInWishlist: (productId: string) => boolean;
|
|
16
16
|
getWishlistCount: () => number;
|
|
@@ -64,7 +64,7 @@ export function WishlistProvider({ children }: { children: ReactNode }) {
|
|
|
64
64
|
error: error instanceof Error ? error.message : 'Failed to load wishlist',
|
|
65
65
|
}));
|
|
66
66
|
}
|
|
67
|
-
|
|
67
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
68
68
|
}, [isAuthenticated]);
|
|
69
69
|
|
|
70
70
|
|
|
@@ -72,7 +72,7 @@ export function WishlistProvider({ children }: { children: ReactNode }) {
|
|
|
72
72
|
fetchWishlist();
|
|
73
73
|
}, [fetchWishlist]);
|
|
74
74
|
|
|
75
|
-
const addToWishlist = async (product:
|
|
75
|
+
const addToWishlist = async (product: Product) => {
|
|
76
76
|
if (!isAuthenticated) {
|
|
77
77
|
notification.error(
|
|
78
78
|
'Sign-in required',
|
|
@@ -88,7 +88,7 @@ export function CartScreen() {
|
|
|
88
88
|
);
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
const subtotal = cart.total;
|
|
91
|
+
const subtotal = cart.cartBody.items.reduce((total, item) => total + item.productVariantData.finalPrice * item.quantity, 0);
|
|
92
92
|
const shipping = 0;
|
|
93
93
|
const tax = 0;
|
|
94
94
|
const total = subtotal + shipping + tax;
|
|
@@ -9,8 +9,7 @@ import { useRouter } from 'next/navigation';
|
|
|
9
9
|
import { Lock, ShieldCheck } from 'lucide-react';
|
|
10
10
|
import { Input } from '@/components/ui/Input';
|
|
11
11
|
import { Button } from '@/components/ui/Button';
|
|
12
|
-
import {
|
|
13
|
-
import { getApiConfiguration } from '@/lib/api-adapter/config';
|
|
12
|
+
import { changePassword } from '@/lib/api-adapter/config';
|
|
14
13
|
import { useAuth } from '@/providers/AuthProvider';
|
|
15
14
|
import { useBasePath } from '@/providers/BasePathProvider';
|
|
16
15
|
|
|
@@ -53,10 +52,7 @@ export function ChangePasswordScreen() {
|
|
|
53
52
|
setIsSubmitting(true);
|
|
54
53
|
setStatus(null);
|
|
55
54
|
try {
|
|
56
|
-
await
|
|
57
|
-
data.currentPassword,
|
|
58
|
-
data.newPassword
|
|
59
|
-
);
|
|
55
|
+
await changePassword(data.currentPassword, data.newPassword);
|
|
60
56
|
setStatus({ type: 'success', message: 'Password updated successfully' });
|
|
61
57
|
setTimeout(() => router.push(buildPath('/account')), 600);
|
|
62
58
|
} catch (error: any) {
|
|
@@ -116,13 +116,21 @@ export function CheckoutScreen() {
|
|
|
116
116
|
sameAsShipping: true,
|
|
117
117
|
shipping: {
|
|
118
118
|
name: user ? `${user.firstname} ${user.lastname}` : '',
|
|
119
|
-
phone: user?.phoneNumber ||
|
|
119
|
+
phone: user?.phoneNumber || '',
|
|
120
120
|
country: 'United States',
|
|
121
|
+
// street1: '',
|
|
122
|
+
// city: '',
|
|
123
|
+
// state: '',
|
|
124
|
+
// zip: '',
|
|
121
125
|
},
|
|
122
126
|
billing: {
|
|
123
127
|
name: user ? `${user.firstname} ${user.lastname}` : '',
|
|
124
|
-
phone: user?.phoneNumber ||
|
|
128
|
+
phone: user?.phoneNumber || '',
|
|
125
129
|
country: 'United States',
|
|
130
|
+
// street1: '',
|
|
131
|
+
// city: '',
|
|
132
|
+
// state: '',
|
|
133
|
+
// zip: '',
|
|
126
134
|
},
|
|
127
135
|
},
|
|
128
136
|
});
|
|
@@ -153,7 +161,7 @@ export function CheckoutScreen() {
|
|
|
153
161
|
setSelectedAddressId(defaultAddress.id);
|
|
154
162
|
// Update form with default address
|
|
155
163
|
setValue('shipping.name', defaultAddress.name);
|
|
156
|
-
setValue('shipping.phone', defaultAddress.phone ||
|
|
164
|
+
setValue('shipping.phone', defaultAddress.phone || '');
|
|
157
165
|
setValue('shipping.street1', defaultAddress.street1);
|
|
158
166
|
setValue('shipping.street2', defaultAddress.street2 || '');
|
|
159
167
|
setValue('shipping.city', defaultAddress.city);
|
|
@@ -342,7 +350,6 @@ export function CheckoutScreen() {
|
|
|
342
350
|
items,
|
|
343
351
|
paymentMethod,
|
|
344
352
|
orderStatus: 'Pending',
|
|
345
|
-
chargeTax: true,
|
|
346
353
|
orderRemindingDates: [],
|
|
347
354
|
shippingAddress: data.shipping,
|
|
348
355
|
billingAddress: sameAsShipping ? data.shipping : data.billing,
|
|
@@ -352,7 +359,7 @@ export function CheckoutScreen() {
|
|
|
352
359
|
const response = await api.createCheckout(
|
|
353
360
|
orderDTO,
|
|
354
361
|
isDelivery,
|
|
355
|
-
|
|
362
|
+
undefined, // dont pass userId
|
|
356
363
|
isDelivery ? (selectedShippingRateId || undefined) : undefined,
|
|
357
364
|
billingAddressId
|
|
358
365
|
);
|
|
@@ -425,7 +432,7 @@ export function CheckoutScreen() {
|
|
|
425
432
|
router.push(buildPath('/cart'));
|
|
426
433
|
return null;
|
|
427
434
|
}
|
|
428
|
-
const subtotal = cart.total;
|
|
435
|
+
const subtotal = cart.cartBody.items.reduce((total, item) => total + item.productVariantData.finalPrice * item.quantity, 0);
|
|
429
436
|
const tax = 0;
|
|
430
437
|
const total = subtotal + shippingPrice + tax;
|
|
431
438
|
|
|
@@ -500,7 +507,7 @@ export function CheckoutScreen() {
|
|
|
500
507
|
onChange={() => {
|
|
501
508
|
setSelectedAddressId(addr.id);
|
|
502
509
|
setValue('shipping.name', addr.name);
|
|
503
|
-
setValue('shipping.phone', addr.phone ||
|
|
510
|
+
setValue('shipping.phone', addr.phone || '');
|
|
504
511
|
setValue('shipping.street1', addr.street1);
|
|
505
512
|
setValue('shipping.street2', addr.street2 || '');
|
|
506
513
|
setValue('shipping.city', addr.city);
|
|
@@ -880,9 +887,9 @@ export function CheckoutScreen() {
|
|
|
880
887
|
<section className="mt-8 pt-6 border-t border-slate-100">
|
|
881
888
|
<div className="space-y-4 mb-6">
|
|
882
889
|
{cart?.cartBody?.items?.map((item: any) => (
|
|
883
|
-
<div key={
|
|
890
|
+
<div key={item.productVariantId || item.id} className="flex gap-3">
|
|
884
891
|
<div className="w-16 h-16 rounded-xl overflow-hidden bg-white shrink-0">
|
|
885
|
-
<Image src={item.productVariantData
|
|
892
|
+
<Image src={item.productVariantData?.media?.[0]?.file || '/placeholder-product.jpg'} alt={item.productVariantData.name} className="w-full h-full object-cover" height={200} width={200} />
|
|
886
893
|
</div>
|
|
887
894
|
<div className="flex-1 min-w-0">
|
|
888
895
|
<p className="font-['Poppins',sans-serif] font-medium text-[12px] text-[#2B4B7C] mb-1">
|
|
@@ -948,9 +955,31 @@ export function CheckoutScreen() {
|
|
|
948
955
|
</div>
|
|
949
956
|
</div>
|
|
950
957
|
)}
|
|
958
|
+
{Object.keys(errors).length > 0 && (
|
|
959
|
+
<div className="mt-4 p-4 rounded-xl bg-amber-50 border border-amber-200 text-amber-700 text-sm font-medium">
|
|
960
|
+
<div className="flex items-start gap-2">
|
|
961
|
+
<AlertCircle className="h-4 w-4 shrink-0 mt-0.5" />
|
|
962
|
+
<div>
|
|
963
|
+
<p className="font-semibold mb-1">Please fix the following errors:</p>
|
|
964
|
+
<ul className="list-disc list-inside space-y-1">
|
|
965
|
+
{errors.shipping?.name && <li>{errors.shipping.name.message}</li>}
|
|
966
|
+
{errors.shipping?.phone && <li>{errors.shipping.phone.message}</li>}
|
|
967
|
+
{errors.shipping?.street1 && <li>{errors.shipping.street1.message}</li>}
|
|
968
|
+
{errors.shipping?.city && <li>{errors.shipping.city.message}</li>}
|
|
969
|
+
{errors.shipping?.state && <li>{errors.shipping.state.message}</li>}
|
|
970
|
+
{errors.shipping?.zip && <li>{errors.shipping.zip.message}</li>}
|
|
971
|
+
{errors.shipping?.country && <li>{errors.shipping.country.message}</li>}
|
|
972
|
+
{!sameAsShipping && errors.billing?.name && <li>Billing: {errors.billing.name.message}</li>}
|
|
973
|
+
{!sameAsShipping && errors.billing?.street1 && <li>Billing: {errors.billing.street1.message}</li>}
|
|
974
|
+
</ul>
|
|
975
|
+
</div>
|
|
976
|
+
</div>
|
|
977
|
+
</div>
|
|
978
|
+
)}
|
|
951
979
|
<button
|
|
952
980
|
type="submit"
|
|
953
|
-
|
|
981
|
+
disabled={isSubmitting}
|
|
982
|
+
className="font-['Poppins',sans-serif] font-medium text-[14px] px-6 py-3 rounded-full text-white hover:bg-[#d66f45] hover:shadow-lg transition-all duration-300 mt-4 w-full bg-[#E67E50] hover:bg-[#2B4B7C] flex items-center justify-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
954
983
|
>
|
|
955
984
|
<CreditCard className="h-5 w-5" />
|
|
956
985
|
{isSubmitting ? 'Placing order...' : 'Place Secure Order'}
|
|
@@ -974,7 +1003,7 @@ export function CheckoutScreen() {
|
|
|
974
1003
|
refresh().then(() => {
|
|
975
1004
|
setSelectedAddressId(addr.id);
|
|
976
1005
|
setValue('shipping.name', addr.name);
|
|
977
|
-
setValue('shipping.phone', addr.phone ||
|
|
1006
|
+
setValue('shipping.phone', addr.phone || '');
|
|
978
1007
|
setValue('shipping.street1', addr.street1);
|
|
979
1008
|
setValue('shipping.street2', addr.street2 || '');
|
|
980
1009
|
setValue('shipping.city', addr.city);
|
|
@@ -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
|
+
}
|