thirukalyanamweb/src/components/auth/ChangePasswordForm.jsx
2025-11-22 17:07:12 +05:30

613 lines
17 KiB
JavaScript

import * as React from 'react'
import {
Box,
Card,
Typography,
Button,
Container,
Dialog,
DialogContent,
Zoom,
Fade,
LinearProgress,
CircularProgress,
IconButton,
} from '@mui/material'
import CheckCircleIcon from '@mui/icons-material/CheckCircle'
import LockIcon from '@mui/icons-material/Lock'
import ArrowBackIcon from '@mui/icons-material/ArrowBack'
import CheckIcon from '@mui/icons-material/Check'
import CloseIcon from '@mui/icons-material/Close'
import { keyframes } from '@mui/system'
import MuiDynamicInput from '../../utills/MuiDynamicInput'
// 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 float = keyframes`
0%, 100% { transform: translateY(0px); }
50% { transform: translateY(-20px); }
`
// 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' },
}}
>
Continue
</Button>
</Fade>
</DialogContent>
</Dialog>
)
}
// Password Strength Indicator
function PasswordStrength({ password }) {
const getStrength = () => {
let strength = 0
if (password.length >= 8) strength += 25
if (/[a-z]/.test(password)) strength += 25
if (/[A-Z]/.test(password)) strength += 25
if (/\d/.test(password)) strength += 15
if (/[!@#$%^&*(),.?":{}|<>]/.test(password)) strength += 10
return Math.min(strength, 100)
}
const strength = getStrength()
const getColor = () => {
if (strength < 30) return 'error'
if (strength < 60) return 'warning'
if (strength < 80) return 'info'
return 'success'
}
const getLabel = () => {
if (strength < 30) return 'Weak'
if (strength < 60) return 'Fair'
if (strength < 80) return 'Good'
return 'Strong'
}
if (!password) return null
return (
<Box sx={{ mt: 1, mb: 2 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 0.5 }}>
<Typography variant="caption" color="text.secondary">
Password Strength
</Typography>
<Typography variant="caption" color={getColor() + '.main'} fontWeight={600}>
{getLabel()}
</Typography>
</Box>
<LinearProgress
variant="determinate"
value={strength}
color={getColor()}
sx={{ height: 6, borderRadius: 3 }}
/>
</Box>
)
}
// Password Requirements Component
function PasswordRequirements({ password }) {
const requirements = [
{ label: 'At least 8 characters', met: password.length >= 8 },
{ label: 'One lowercase letter', met: /[a-z]/.test(password) },
{ label: 'One uppercase letter', met: /[A-Z]/.test(password) },
{ label: 'One number', met: /\d/.test(password) },
{ label: 'One special character', met: /[!@#$%^&*(),.?":{}|<>]/.test(password) },
]
return (
<Box sx={{ mb: 2 }}>
{requirements.map((req, index) => (
<Box
key={index}
sx={{
display: 'flex',
alignItems: 'center',
gap: 1,
py: 0.3,
}}
>
{req.met ? (
<CheckIcon sx={{ fontSize: 16, color: 'success.main' }} />
) : (
<CloseIcon sx={{ fontSize: 16, color: 'text.disabled' }} />
)}
<Typography
variant="caption"
sx={{
color: req.met ? 'success.main' : 'text.disabled',
transition: 'color 0.3s',
}}
>
{req.label}
</Typography>
</Box>
))}
</Box>
)
}
const ChangePasswordPage = () => {
const [loading, setLoading] = React.useState(false)
const [successModal, setSuccessModal] = React.useState(false)
const [formData, setFormData] = React.useState({
currentPassword: '',
newPassword: '',
confirmPassword: '',
})
const [errors, setErrors] = React.useState({})
const handleChange = (e) => {
const { name, value } = e.target
setFormData((prev) => ({ ...prev, [name]: value }))
if (errors[name]) {
setErrors((prev) => ({ ...prev, [name]: '' }))
}
}
const validateForm = () => {
const newErrors = {}
if (!formData.currentPassword) {
newErrors.currentPassword = 'Current password is required'
}
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'
}
if (formData.currentPassword === formData.newPassword) {
newErrors.newPassword = 'New password must be different from current'
}
setErrors(newErrors)
return Object.keys(newErrors).length === 0
}
const handleSubmit = async () => {
if (!validateForm()) return
setLoading(true)
setTimeout(() => {
setLoading(false)
setSuccessModal(true)
console.log('Change Password Data:', {
currentPassword: formData.currentPassword,
newPassword: formData.newPassword,
})
}, 1500)
}
const handleSuccessClose = () => {
setSuccessModal(false)
setFormData({ currentPassword: '', newPassword: '', confirmPassword: '' })
console.log('Navigate to profile or dashboard...')
}
const handleBack = () => {
console.log('Navigate back...')
}
return (
<Box
sx={{
minHeight: '100vh',
backgroundColor: '#f5f5f5',
display: 'flex',
alignItems: 'center',
py: 4,
}}
>
<Container maxWidth="lg">
<Card
elevation={12}
sx={{
borderRadius: 4,
overflow: 'hidden',
display: 'flex',
flexDirection: { xs: 'column', md: 'row' },
minHeight: { md: 600 },
}}
>
{/* Left Side - Image */}
<Box
sx={{
flex: { md: 1 },
background: 'linear-gradient(135deg, #4CAF50 0%, #2E7D32 100%)',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
p: { xs: 4, md: 6 },
position: 'relative',
overflow: 'hidden',
minHeight: { xs: 250, md: 'auto' },
}}
>
{/* Background Decorations */}
<Box
sx={{
position: 'absolute',
top: -50,
left: -50,
width: 200,
height: 200,
borderRadius: '50%',
backgroundColor: 'rgba(255,255,255,0.1)',
}}
/>
<Box
sx={{
position: 'absolute',
bottom: -80,
right: -80,
width: 300,
height: 300,
borderRadius: '50%',
backgroundColor: 'rgba(255,255,255,0.05)',
}}
/>
{/* Lock Icon with Animation */}
<Box
sx={{
width: 120,
height: 120,
borderRadius: '50%',
backgroundColor: 'rgba(255,255,255,0.2)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
mb: 4,
animation: `${float} 3s ease-in-out infinite`,
}}
>
<LockIcon sx={{ fontSize: 60, color: 'white' }} />
</Box>
<Typography
variant="h4"
sx={{
color: 'white',
fontWeight: 700,
textAlign: 'center',
mb: 2,
}}
>
Secure Your Account
</Typography>
<Typography
variant="body1"
sx={{
color: 'rgba(255,255,255,0.9)',
textAlign: 'center',
maxWidth: 350,
lineHeight: 1.8,
}}
>
Keep your account safe by regularly updating your password. Choose
a strong password that you don't use elsewhere.
</Typography>
{/* Security Tips */}
<Box
sx={{
mt: 4,
p: 2,
backgroundColor: 'rgba(255,255,255,0.1)',
borderRadius: 2,
maxWidth: 350,
}}
>
<Typography
variant="subtitle2"
sx={{ color: 'white', fontWeight: 600, mb: 1 }}
>
Security Tips:
</Typography>
<Typography
variant="caption"
sx={{ color: 'rgba(255,255,255,0.8)', display: 'block' }}
>
• Use a mix of letters, numbers & symbols
</Typography>
<Typography
variant="caption"
sx={{ color: 'rgba(255,255,255,0.8)', display: 'block' }}
>
• Avoid personal information
</Typography>
<Typography
variant="caption"
sx={{ color: 'rgba(255,255,255,0.8)', display: 'block' }}
>
• Don't reuse passwords from other sites
</Typography>
</Box>
</Box>
{/* Right Side - Form */}
<Box
sx={{
flex: { md: 1 },
display: 'flex',
flexDirection: 'column',
p: { xs: 3, sm: 4, md: 6 },
backgroundColor: 'white',
}}
>
{/* Header */}
<Box sx={{ display: 'flex', alignItems: 'center', mb: 4 }}>
<IconButton onClick={handleBack} sx={{ mr: 1 }}>
<ArrowBackIcon />
</IconButton>
<Typography variant="h5" sx={{ fontWeight: 700, color: '#333' }}>
Change Password
</Typography>
</Box>
<Typography
variant="body2"
sx={{ color: 'text.secondary', mb: 4 }}
>
Your password must be different from previously used passwords.
</Typography>
{/* Form Fields */}
<Box sx={{ flex: 1 }}>
<MuiDynamicInput
type="password"
name="currentPassword"
label="Current Password"
value={formData.currentPassword}
onChange={handleChange}
error={errors.currentPassword}
showPasswordToggle
required
/>
<MuiDynamicInput
type="password"
name="newPassword"
label="New Password"
value={formData.newPassword}
onChange={handleChange}
error={errors.newPassword}
showPasswordToggle
required
/>
{/* Password Strength Indicator */}
<PasswordStrength password={formData.newPassword} />
{/* Password Requirements */}
<PasswordRequirements password={formData.newPassword} />
<MuiDynamicInput
type="password"
name="confirmPassword"
label="Confirm New Password"
value={formData.confirmPassword}
onChange={handleChange}
error={errors.confirmPassword}
showPasswordToggle
required
/>
{/* Match Indicator */}
{formData.confirmPassword && (
<Box
sx={{
display: 'flex',
alignItems: 'center',
gap: 1,
mt: -1,
mb: 2,
}}
>
{formData.newPassword === formData.confirmPassword ? (
<>
<CheckIcon sx={{ fontSize: 16, color: 'success.main' }} />
<Typography variant="caption" color="success.main">
Passwords match
</Typography>
</>
) : (
<>
<CloseIcon sx={{ fontSize: 16, color: 'error.main' }} />
<Typography variant="caption" color="error.main">
Passwords do not match
</Typography>
</>
)}
</Box>
)}
</Box>
{/* Submit Button */}
<Button
fullWidth
variant="contained"
onClick={handleSubmit}
disabled={loading}
sx={{
mt: 3,
py: 1.5,
borderRadius: 50,
backgroundColor: '#FF9800',
fontSize: '1rem',
fontWeight: 600,
textTransform: 'uppercase',
boxShadow: '0 4px 12px rgba(255, 152, 0, 0.4)',
'&:hover': {
backgroundColor: '#F57C00',
boxShadow: '0 6px 16px rgba(255, 152, 0, 0.5)',
},
'&:disabled': {
backgroundColor: '#FFB74D',
color: 'white',
},
}}
>
{loading ? (
<CircularProgress size={24} sx={{ color: 'white' }} />
) : (
'Update Password'
)}
</Button>
{/* Cancel Link */}
<Button
fullWidth
variant="text"
onClick={handleBack}
sx={{
mt: 2,
color: 'text.secondary',
textTransform: 'none',
'&:hover': {
backgroundColor: 'transparent',
color: '#FF9800',
},
}}
>
Cancel
</Button>
</Box>
</Card>
</Container>
{/* Success Modal */}
<SuccessModal
open={successModal}
onClose={handleSuccessClose}
title="Password Changed!"
message="Your password has been updated successfully. Please use your new password for future logins."
/>
</Box>
)
}
export default ChangePasswordPage