789 lines
23 KiB
JavaScript
789 lines
23 KiB
JavaScript
import {
|
|
Box,
|
|
Card,
|
|
Typography,
|
|
Button,
|
|
// Link,
|
|
Container,
|
|
Dialog,
|
|
DialogContent,
|
|
Zoom,
|
|
Fade,
|
|
Stepper,
|
|
Step,
|
|
StepLabel,
|
|
IconButton,
|
|
CircularProgress,
|
|
} from '@mui/material'
|
|
import CheckCircleIcon from '@mui/icons-material/CheckCircle'
|
|
import ArrowBackIcon from '@mui/icons-material/ArrowBack'
|
|
import LockResetIcon from '@mui/icons-material/LockReset'
|
|
import PhoneAndroidIcon from '@mui/icons-material/PhoneAndroid'
|
|
import VpnKeyIcon from '@mui/icons-material/VpnKey'
|
|
import { keyframes } from '@mui/system'
|
|
import { useEffect, useRef, useState } from 'react'
|
|
import MuiDynamicInput from '../../utills/MuiDynamicInput'
|
|
import axiosInstance from '../../api/axiosInstance'
|
|
import toast from 'react-hot-toast'
|
|
import { Link } from 'react-router-dom'
|
|
// Keyframe animations
|
|
const scaleIn = keyframes`
|
|
0% { transform: scale(0); opacity: 0; }
|
|
50% { transform: scale(1.2); }
|
|
100% { transform: scale(1); opacity: 1; }
|
|
`
|
|
|
|
const pulse = keyframes`
|
|
0% { box-shadow: 0 0 0 0 rgba(76, 175, 80, 0.4); }
|
|
70% { box-shadow: 0 0 0 20px rgba(76, 175, 80, 0); }
|
|
100% { box-shadow: 0 0 0 0 rgba(76, 175, 80, 0); }
|
|
`
|
|
|
|
const fadeInUp = keyframes`
|
|
0% { opacity: 0; transform: translateY(20px); }
|
|
100% { opacity: 1; transform: translateY(0); }
|
|
`
|
|
|
|
const shake = keyframes`
|
|
0%, 100% { transform: translateX(0); }
|
|
25% { transform: translateX(-5px); }
|
|
75% { transform: translateX(5px); }
|
|
`
|
|
|
|
// Success Modal Component
|
|
function SuccessModal({ open, onClose, title, message }) {
|
|
return (
|
|
<Dialog
|
|
open={open}
|
|
onClose={onClose}
|
|
TransitionComponent={Zoom}
|
|
TransitionProps={{ timeout: 400 }}
|
|
PaperProps={{
|
|
sx: {
|
|
borderRadius: 4,
|
|
minWidth: { xs: 280, sm: 400 },
|
|
overflow: 'visible',
|
|
},
|
|
}}
|
|
>
|
|
<DialogContent
|
|
sx={{
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
alignItems: 'center',
|
|
py: 5,
|
|
px: 4,
|
|
textAlign: 'center',
|
|
}}
|
|
>
|
|
<Box
|
|
sx={{
|
|
width: 100,
|
|
height: 100,
|
|
borderRadius: '50%',
|
|
backgroundColor: 'success.light',
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
animation: `${scaleIn} 0.5s ease-out, ${pulse} 1.5s ease-out 0.5s`,
|
|
mb: 3,
|
|
}}
|
|
>
|
|
<CheckCircleIcon
|
|
sx={{
|
|
fontSize: 60,
|
|
color: 'success.main',
|
|
animation: `${scaleIn} 0.6s ease-out 0.2s both`,
|
|
}}
|
|
/>
|
|
</Box>
|
|
|
|
<Fade in={open} timeout={800}>
|
|
<Typography
|
|
variant="h5"
|
|
sx={{
|
|
fontWeight: 700,
|
|
color: 'success.main',
|
|
mb: 1,
|
|
animation: `${fadeInUp} 0.5s ease-out 0.3s both`,
|
|
}}
|
|
>
|
|
{title}
|
|
</Typography>
|
|
</Fade>
|
|
|
|
<Fade in={open} timeout={1000}>
|
|
<Typography
|
|
variant="body1"
|
|
sx={{
|
|
color: 'text.secondary',
|
|
mb: 3,
|
|
animation: `${fadeInUp} 0.5s ease-out 0.5s both`,
|
|
}}
|
|
>
|
|
{message}
|
|
</Typography>
|
|
</Fade>
|
|
|
|
<Fade in={open} timeout={1200}>
|
|
<Button
|
|
variant="contained"
|
|
onClick={onClose}
|
|
sx={{
|
|
mt: 1,
|
|
px: 5,
|
|
py: 1.5,
|
|
borderRadius: 50,
|
|
backgroundColor: 'success.main',
|
|
fontWeight: 600,
|
|
textTransform: 'none',
|
|
fontSize: '1rem',
|
|
animation: `${fadeInUp} 0.5s ease-out 0.7s both`,
|
|
'&:hover': { backgroundColor: 'success.dark' },
|
|
}}
|
|
>
|
|
Back to Login
|
|
</Button>
|
|
</Fade>
|
|
</DialogContent>
|
|
</Dialog>
|
|
)
|
|
}
|
|
|
|
// OTP Input Component
|
|
function OTPInput({ value, onChange, error, disabled }) {
|
|
const inputRefs = useRef([])
|
|
const otpLength = 4
|
|
|
|
const handleChange = (index, e) => {
|
|
const val = e.target.value
|
|
if (!/^\d*$/.test(val)) return
|
|
|
|
const newOtp = value.split('')
|
|
newOtp[index] = val.slice(-1)
|
|
const otpString = newOtp.join('')
|
|
onChange({ target: { name: 'otp', value: otpString } })
|
|
|
|
// Move to next input
|
|
if (val && index < otpLength - 1) {
|
|
inputRefs.current[index + 1]?.focus()
|
|
}
|
|
}
|
|
|
|
const handleKeyDown = (index, e) => {
|
|
if (e.key === 'Backspace' && !value[index] && index > 0) {
|
|
inputRefs.current[index - 1]?.focus()
|
|
}
|
|
}
|
|
|
|
const handlePaste = (e) => {
|
|
e.preventDefault()
|
|
const pastedData = e.clipboardData.getData('text').slice(0, otpLength)
|
|
if (/^\d+$/.test(pastedData)) {
|
|
onChange({ target: { name: 'otp', value: pastedData } })
|
|
const focusIndex = Math.min(pastedData.length, otpLength - 1)
|
|
inputRefs.current[focusIndex]?.focus()
|
|
}
|
|
}
|
|
|
|
return (
|
|
<Box sx={{ mb: 2 }}>
|
|
<Box
|
|
sx={{
|
|
display: 'flex',
|
|
gap: { xs: 1, sm: 1.5 },
|
|
justifyContent: 'center',
|
|
animation: error ? `${shake} 0.5s ease-out` : 'none',
|
|
}}
|
|
>
|
|
{Array.from({ length: otpLength }).map((_, index) => (
|
|
<input
|
|
key={index}
|
|
ref={(el) => (inputRefs.current[index] = el)}
|
|
type="text"
|
|
inputMode="numeric"
|
|
maxLength={1}
|
|
value={value[index] || ''}
|
|
onChange={(e) => handleChange(index, e)}
|
|
onKeyDown={(e) => handleKeyDown(index, e)}
|
|
onPaste={handlePaste}
|
|
disabled={disabled}
|
|
style={{
|
|
width: '45px',
|
|
height: '55px',
|
|
textAlign: 'center',
|
|
fontSize: '24px',
|
|
fontWeight: 600,
|
|
border: `2px solid ${error ? '#f44336' : value[index] ? '#4CAF50' : '#ddd'}`,
|
|
borderRadius: '12px',
|
|
outline: 'none',
|
|
transition: 'all 0.2s',
|
|
backgroundColor: disabled ? '#f5f5f5' : 'white',
|
|
}}
|
|
onFocus={(e) => {
|
|
e.target.style.borderColor = '#4CAF50'
|
|
e.target.style.boxShadow = '0 0 0 3px rgba(76, 175, 80, 0.2)'
|
|
}}
|
|
onBlur={(e) => {
|
|
e.target.style.borderColor = value[index] ? '#4CAF50' : '#ddd'
|
|
e.target.style.boxShadow = 'none'
|
|
}}
|
|
/>
|
|
))}
|
|
</Box>
|
|
{error && (
|
|
<Typography
|
|
color="error"
|
|
variant="caption"
|
|
sx={{ display: 'block', textAlign: 'center', mt: 1 }}
|
|
>
|
|
{error}
|
|
</Typography>
|
|
)}
|
|
</Box>
|
|
)
|
|
}
|
|
|
|
|
|
const ForgotPassworForm = () => {
|
|
|
|
const steps = ['Enter Mobile', 'Verify OTP', 'Reset Password']
|
|
|
|
const [activeStep, setActiveStep] = useState(0)
|
|
const [loading, setLoading] = useState(false)
|
|
const [successModal, setSuccessModal] =useState(false)
|
|
const [resendTimer, setResendTimer] = useState(0)
|
|
|
|
const [formData, setFormData] = useState({
|
|
mobile: '',
|
|
otp: '',
|
|
newPassword: '',
|
|
confirmPassword: '',
|
|
})
|
|
|
|
const [errors, setErrors] = useState({})
|
|
|
|
// Resend OTP Timer
|
|
useEffect(() => {
|
|
let interval
|
|
if (resendTimer > 0) {
|
|
interval = setInterval(() => {
|
|
setResendTimer((prev) => prev - 1)
|
|
}, 1000)
|
|
}
|
|
return () => clearInterval(interval)
|
|
}, [resendTimer])
|
|
|
|
const handleChange = (e) => {
|
|
const { name, value } = e.target
|
|
setFormData((prev) => ({ ...prev, [name]: value }))
|
|
if (errors[name]) {
|
|
setErrors((prev) => ({ ...prev, [name]: '' }))
|
|
}
|
|
}
|
|
|
|
// Step 1: Validate Mobile
|
|
const validateMobile = () => {
|
|
const newErrors = {}
|
|
if (!formData.mobile) {
|
|
newErrors.mobile = 'Mobile number is required'
|
|
} else if (!/^\d{10}$/.test(formData.mobile)) {
|
|
newErrors.mobile = 'Enter a valid 10-digit mobile number'
|
|
}
|
|
setErrors(newErrors)
|
|
return Object.keys(newErrors).length === 0
|
|
}
|
|
|
|
// Step 2: Validate OTP
|
|
const validateOTP = () => {
|
|
const newErrors = {}
|
|
if (!formData.otp || formData.otp.length !== 4) {
|
|
newErrors.otp = 'Please enter the 6-digit OTP'
|
|
}
|
|
setErrors(newErrors)
|
|
return Object.keys(newErrors).length === 0
|
|
}
|
|
|
|
// Step 3: Validate Password
|
|
const validatePassword = () => {
|
|
const newErrors = {}
|
|
if (!formData.newPassword) {
|
|
newErrors.newPassword = 'New password is required'
|
|
} else if (formData.newPassword.length < 8) {
|
|
newErrors.newPassword = 'Password must be at least 8 characters'
|
|
} else if (!/(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/.test(formData.newPassword)) {
|
|
newErrors.newPassword = 'Must include uppercase, lowercase, and number'
|
|
}
|
|
|
|
if (!formData.confirmPassword) {
|
|
newErrors.confirmPassword = 'Please confirm your password'
|
|
} else if (formData.newPassword !== formData.confirmPassword) {
|
|
newErrors.confirmPassword = 'Passwords do not match'
|
|
}
|
|
|
|
setErrors(newErrors)
|
|
return Object.keys(newErrors).length === 0
|
|
}
|
|
|
|
// Submit Mobile
|
|
const handleMobileSubmit = async () => {
|
|
if (!validateMobile()) return
|
|
|
|
setLoading(true)
|
|
try {
|
|
const response = await axiosInstance.post(`/forgot_password_send_otp?mobile=${formData.mobile}`)
|
|
toast.success(response.data?.message || "OTP sent successfully")
|
|
setLoading(false)
|
|
setActiveStep(1)
|
|
setResendTimer(120) // 2 minutes timer
|
|
} catch (error) {
|
|
setLoading(false)
|
|
console.error("Forgot password error:", error)
|
|
const errorMessage = error.response?.data?.message || error.response?.data?.error || "Failed to send OTP"
|
|
toast.error(errorMessage)
|
|
}
|
|
}
|
|
|
|
// Verify OTP
|
|
const handleOTPSubmit = async () => {
|
|
if (!validateOTP()) return
|
|
|
|
setLoading(true)
|
|
try {
|
|
const response = await axiosInstance.post(`/forgot_password_verify_otp?mobile=${formData.mobile}&otp=${formData.otp}`)
|
|
toast.success(response.data?.message || "OTP Verified Successfully")
|
|
setLoading(false)
|
|
setActiveStep(2)
|
|
} catch (error) {
|
|
setLoading(false)
|
|
console.error("OTP verification error:", error)
|
|
const errorMessage = error.response?.data?.message || error.response?.data?.error || "Invalid OTP"
|
|
toast.error(errorMessage)
|
|
}
|
|
}
|
|
|
|
// Resend OTP
|
|
const handleResendOTP = () => {
|
|
handleMobileSubmit()
|
|
}
|
|
|
|
// Submit New Password
|
|
const handlePasswordSubmit = async () => {
|
|
if (!validatePassword()) return
|
|
|
|
setLoading(true)
|
|
try {
|
|
const encodedPassword = encodeURIComponent(formData.newPassword)
|
|
const response = await axiosInstance.post(`/forgot_password_update?mobile=${formData.mobile}&otp=${formData.otp}&password=${encodedPassword}`)
|
|
toast.success(response.data?.message || "Password updated successfully")
|
|
setLoading(false)
|
|
setSuccessModal(true)
|
|
} catch (error) {
|
|
setLoading(false)
|
|
console.error("Password update error:", error)
|
|
const errorMessage = error.response?.data?.message || error.response?.data?.error || "Failed to update password"
|
|
toast.error(errorMessage)
|
|
}
|
|
}
|
|
|
|
const handleBack = () => {
|
|
if (activeStep > 0) {
|
|
setActiveStep((prev) => prev - 1)
|
|
setErrors({})
|
|
}
|
|
}
|
|
|
|
const handleSuccessClose = () => {
|
|
setSuccessModal(false)
|
|
setActiveStep(0)
|
|
setFormData({ mobile: '', otp: '', newPassword: '', confirmPassword: '' })
|
|
// Navigate to login page
|
|
console.log('Navigate to login...')
|
|
}
|
|
|
|
const formatTime = (seconds) => {
|
|
const mins = Math.floor(seconds / 60)
|
|
const secs = seconds % 60
|
|
return `${mins}:${secs.toString().padStart(2, '0')}`
|
|
}
|
|
|
|
// Render step icon
|
|
const getStepIcon = (step) => {
|
|
switch (step) {
|
|
case 0:
|
|
return <PhoneAndroidIcon />
|
|
case 1:
|
|
return <VpnKeyIcon />
|
|
case 2:
|
|
return <LockResetIcon />
|
|
default:
|
|
return null
|
|
}
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
<>
|
|
<Container maxWidth="sm" sx={{ py: 4, minHeight: '100vh' }}>
|
|
<Card
|
|
elevation={8}
|
|
sx={{
|
|
borderRadius: 4,
|
|
overflow: 'hidden',
|
|
}}
|
|
>
|
|
{/* Header */}
|
|
<Box
|
|
sx={{
|
|
background: 'linear-gradient(135deg, #034E08 0%, #034E08 100%)',
|
|
py: 3,
|
|
px: 4,
|
|
position: 'relative',
|
|
}}
|
|
>
|
|
{activeStep > 0 && (
|
|
<IconButton
|
|
onClick={handleBack}
|
|
sx={{
|
|
position: 'absolute',
|
|
left: 16,
|
|
top: '50%',
|
|
transform: 'translateY(-50%)',
|
|
color: 'white',
|
|
}}
|
|
>
|
|
<ArrowBackIcon />
|
|
</IconButton>
|
|
)}
|
|
<Typography
|
|
variant="h5"
|
|
component="h1"
|
|
sx={{
|
|
color: 'white',
|
|
fontWeight: 600,
|
|
textAlign: 'center',
|
|
}}
|
|
>
|
|
Forgot Password
|
|
</Typography>
|
|
</Box>
|
|
|
|
{/* Stepper */}
|
|
<Box sx={{ px: { xs: 2, sm: 4 }, pt: 3 }}>
|
|
<Stepper activeStep={activeStep} alternativeLabel>
|
|
{steps.map((label, index) => (
|
|
<Step key={label}>
|
|
<StepLabel
|
|
StepIconProps={{
|
|
sx: {
|
|
'&.Mui-active': { color: '#034E08' },
|
|
'&.Mui-completed': { color: '#034E08' },
|
|
},
|
|
}}
|
|
>
|
|
<Typography
|
|
variant="caption"
|
|
sx={{
|
|
display: { xs: 'none', sm: 'block' },
|
|
fontWeight: activeStep === index ? 600 : 400,
|
|
}}
|
|
>
|
|
{label}
|
|
</Typography>
|
|
</StepLabel>
|
|
</Step>
|
|
))}
|
|
</Stepper>
|
|
</Box>
|
|
|
|
{/* Form Content */}
|
|
<Box sx={{ p: 4 }}>
|
|
{/* Step 1: Mobile */}
|
|
{activeStep === 0 && (
|
|
<Fade in={activeStep === 0}>
|
|
<Box>
|
|
<Box
|
|
sx={{
|
|
display: 'flex',
|
|
justifyContent: 'center',
|
|
mb: 3,
|
|
}}
|
|
>
|
|
<Box
|
|
sx={{
|
|
width: 80,
|
|
height: 80,
|
|
borderRadius: '50%',
|
|
backgroundColor: 'rgba(76, 175, 80, 0.1)',
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
}}
|
|
>
|
|
<PhoneAndroidIcon sx={{ fontSize: 40, color: '#A70710' }} />
|
|
</Box>
|
|
</Box>
|
|
|
|
<Typography
|
|
variant="body1"
|
|
sx={{ textAlign: 'center', mb: 3, color: 'text.secondary' }}
|
|
>
|
|
Enter your mobile number and we'll send you an OTP to reset
|
|
your password.
|
|
</Typography>
|
|
|
|
<MuiDynamicInput
|
|
type="tel"
|
|
name="mobile"
|
|
label="Mobile Number"
|
|
value={formData.mobile}
|
|
onChange={handleChange}
|
|
error={errors.mobile}
|
|
inputProps={{ maxLength: 10 }}
|
|
autoFocus
|
|
required
|
|
/>
|
|
|
|
<Button
|
|
fullWidth
|
|
variant="contained"
|
|
onClick={handleMobileSubmit}
|
|
disabled={loading}
|
|
sx={{
|
|
mt: 2,
|
|
py: 1.5,
|
|
borderRadius: 50,
|
|
backgroundColor: '#A70710',
|
|
fontSize: '1rem',
|
|
fontWeight: 600,
|
|
'&:hover': { backgroundColor: '#A70710' },
|
|
'&:disabled': { backgroundColor: '#A70710', color: 'white' },
|
|
}}
|
|
>
|
|
{loading ? (
|
|
<CircularProgress size={24} sx={{ color: 'white' }} />
|
|
) : (
|
|
'Send OTP'
|
|
)}
|
|
</Button>
|
|
|
|
<Box sx={{ mt: 3, textAlign: 'center' }}>
|
|
<Link
|
|
to="/login"
|
|
sx={{
|
|
color: '#A70710',
|
|
fontWeight: 500,
|
|
textDecoration: 'none',
|
|
'&:hover': { textDecoration: 'underline' },
|
|
}}
|
|
>
|
|
Back to Login
|
|
</Link>
|
|
</Box>
|
|
</Box>
|
|
</Fade>
|
|
)}
|
|
|
|
{/* Step 2: OTP Verification */}
|
|
{activeStep === 1 && (
|
|
<Fade in={activeStep === 1}>
|
|
<Box>
|
|
<Box
|
|
sx={{
|
|
display: 'flex',
|
|
justifyContent: 'center',
|
|
mb: 3,
|
|
}}
|
|
>
|
|
<Box
|
|
sx={{
|
|
width: 80,
|
|
height: 80,
|
|
borderRadius: '50%',
|
|
backgroundColor: 'rgba(76, 175, 80, 0.1)',
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
}}
|
|
>
|
|
<VpnKeyIcon sx={{ fontSize: 40, color: '#4CAF50' }} />
|
|
</Box>
|
|
</Box>
|
|
|
|
<Typography
|
|
variant="body1"
|
|
sx={{ textAlign: 'center', mb: 1, color: 'text.secondary' }}
|
|
>
|
|
We've sent a 4-digit OTP to
|
|
</Typography>
|
|
<Typography
|
|
variant="body1"
|
|
sx={{
|
|
textAlign: 'center',
|
|
mb: 3,
|
|
fontWeight: 600,
|
|
color: '#4CAF50',
|
|
}}
|
|
>
|
|
{formData.mobile}
|
|
</Typography>
|
|
|
|
<OTPInput
|
|
value={formData.otp}
|
|
onChange={handleChange}
|
|
error={errors.otp}
|
|
disabled={loading}
|
|
/>
|
|
|
|
{/* Timer and Resend */}
|
|
<Box sx={{ textAlign: 'center', mb: 3 }}>
|
|
{resendTimer > 0 ? (
|
|
<Typography variant="body2" color="text.secondary">
|
|
Resend OTP in{' '}
|
|
<Typography
|
|
component="span"
|
|
sx={{ fontWeight: 600, color: '#FF9800' }}
|
|
>
|
|
{formatTime(resendTimer)}
|
|
</Typography>
|
|
</Typography>
|
|
) : (
|
|
<Link
|
|
component="button"
|
|
onClick={handleResendOTP}
|
|
sx={{
|
|
color: '#FF9800',
|
|
fontWeight: 600,
|
|
textDecoration: 'none',
|
|
'&:hover': { textDecoration: 'underline' },
|
|
}}
|
|
>
|
|
Resend OTP
|
|
</Link>
|
|
)}
|
|
</Box>
|
|
|
|
<Button
|
|
fullWidth
|
|
variant="contained"
|
|
onClick={handleOTPSubmit}
|
|
disabled={loading || formData.otp.length !== 4}
|
|
sx={{
|
|
py: 1.5,
|
|
borderRadius: 50,
|
|
backgroundColor: '#FF9800',
|
|
fontSize: '1rem',
|
|
fontWeight: 600,
|
|
'&:hover': { backgroundColor: '#F57C00' },
|
|
'&:disabled': { backgroundColor: '#FFB74D', color: 'white' },
|
|
}}
|
|
>
|
|
{loading ? (
|
|
<CircularProgress size={24} sx={{ color: 'white' }} />
|
|
) : (
|
|
'Verify OTP'
|
|
)}
|
|
</Button>
|
|
</Box>
|
|
</Fade>
|
|
)}
|
|
|
|
{/* Step 3: Reset Password */}
|
|
{activeStep === 2 && (
|
|
<Fade in={activeStep === 2}>
|
|
<Box>
|
|
<Box
|
|
sx={{
|
|
display: 'flex',
|
|
justifyContent: 'center',
|
|
mb: 3,
|
|
}}
|
|
>
|
|
<Box
|
|
sx={{
|
|
width: 80,
|
|
height: 80,
|
|
borderRadius: '50%',
|
|
backgroundColor: 'rgba(76, 175, 80, 0.1)',
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
}}
|
|
>
|
|
<LockResetIcon sx={{ fontSize: 40, color: '#4CAF50' }} />
|
|
</Box>
|
|
</Box>
|
|
|
|
<Typography
|
|
variant="body1"
|
|
sx={{ textAlign: 'center', mb: 3, color: 'text.secondary' }}
|
|
>
|
|
Create a strong password with at least 8 characters including
|
|
uppercase, lowercase, and numbers.
|
|
</Typography>
|
|
|
|
<MuiDynamicInput
|
|
type="password"
|
|
name="newPassword"
|
|
label="New Password"
|
|
value={formData.newPassword}
|
|
onChange={handleChange}
|
|
error={errors.newPassword}
|
|
showPasswordToggle
|
|
required
|
|
/>
|
|
|
|
<MuiDynamicInput
|
|
type="password"
|
|
name="confirmPassword"
|
|
label="Confirm Password"
|
|
value={formData.confirmPassword}
|
|
onChange={handleChange}
|
|
error={errors.confirmPassword}
|
|
showPasswordToggle
|
|
required
|
|
/>
|
|
|
|
<Button
|
|
fullWidth
|
|
variant="contained"
|
|
onClick={handlePasswordSubmit}
|
|
disabled={loading}
|
|
sx={{
|
|
mt: 2,
|
|
py: 1.5,
|
|
borderRadius: 50,
|
|
backgroundColor: '#FF9800',
|
|
fontSize: '1rem',
|
|
fontWeight: 600,
|
|
'&:hover': { backgroundColor: '#F57C00' },
|
|
'&:disabled': { backgroundColor: '#FFB74D', color: 'white' },
|
|
}}
|
|
>
|
|
{loading ? (
|
|
<CircularProgress size={24} sx={{ color: 'white' }} />
|
|
) : (
|
|
'Reset Password'
|
|
)}
|
|
</Button>
|
|
</Box>
|
|
</Fade>
|
|
)}
|
|
</Box>
|
|
</Card>
|
|
|
|
{/* Success Modal */}
|
|
<SuccessModal
|
|
open={successModal}
|
|
onClose={handleSuccessClose}
|
|
title="Password Reset Successful!"
|
|
message="Your password has been reset successfully. You can now login with your new password."
|
|
/>
|
|
</Container>
|
|
</>
|
|
)
|
|
}
|
|
|
|
export default ForgotPassworForm
|