feat: Implement password change functionality with client-side validation, API integration, and UI feedback for loading, success, and errors.
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { TwoFactorSettings } from './TwoFactorSettings';
|
import { TwoFactorSettings } from './TwoFactorSettings';
|
||||||
|
import api from '@/services/api';
|
||||||
|
|
||||||
interface PasswordSecurityFormProps {
|
interface PasswordSecurityFormProps {
|
||||||
onSave?: (data: { currentPassword: string; newPassword: string }) => void;
|
onSave?: (data: { currentPassword: string; newPassword: string }) => void;
|
||||||
@@ -20,23 +21,81 @@ export function PasswordSecurityForm({ onSave }: PasswordSecurityFormProps) {
|
|||||||
confirm: false,
|
confirm: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [error, setError] = useState('');
|
||||||
|
const [success, setSuccess] = useState('');
|
||||||
|
|
||||||
const handleChange = (field: string, value: string) => {
|
const handleChange = (field: string, value: string) => {
|
||||||
setFormData((prev) => ({ ...prev, [field]: value }));
|
setFormData((prev) => ({ ...prev, [field]: value }));
|
||||||
|
// Clear messages when user starts typing
|
||||||
|
if (error) setError('');
|
||||||
|
if (success) setSuccess('');
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleShowPassword = (field: 'current' | 'new' | 'confirm') => {
|
const toggleShowPassword = (field: 'current' | 'new' | 'confirm') => {
|
||||||
setShowPasswords((prev) => ({ ...prev, [field]: !prev[field] }));
|
setShowPasswords((prev) => ({ ...prev, [field]: !prev[field] }));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSave = () => {
|
const validatePassword = (password: string): boolean => {
|
||||||
if (formData.newPassword !== formData.confirmPassword) {
|
// Minimum 8 characters with at least one uppercase, lowercase, and number
|
||||||
alert('Passwords do not match');
|
const regex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/;
|
||||||
|
return regex.test(password);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSave = async () => {
|
||||||
|
setError('');
|
||||||
|
setSuccess('');
|
||||||
|
|
||||||
|
// Validate fields
|
||||||
|
if (!formData.currentPassword) {
|
||||||
|
setError('Current password is required');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (onSave) {
|
|
||||||
onSave({ currentPassword: formData.currentPassword, newPassword: formData.newPassword });
|
if (!formData.newPassword) {
|
||||||
} else {
|
setError('New password is required');
|
||||||
console.log('Updating password');
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!validatePassword(formData.newPassword)) {
|
||||||
|
setError('Password must be at least 8 characters with uppercase, lowercase, and number');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (formData.newPassword !== formData.confirmPassword) {
|
||||||
|
setError('Passwords do not match');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await api.post('/auth/change-password', {
|
||||||
|
currentPassword: formData.currentPassword,
|
||||||
|
newPassword: formData.newPassword,
|
||||||
|
});
|
||||||
|
|
||||||
|
setSuccess('Password changed successfully');
|
||||||
|
setFormData({
|
||||||
|
currentPassword: '',
|
||||||
|
newPassword: '',
|
||||||
|
confirmPassword: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
// Call onSave callback if provided
|
||||||
|
if (onSave) {
|
||||||
|
onSave({ currentPassword: formData.currentPassword, newPassword: formData.newPassword });
|
||||||
|
}
|
||||||
|
} catch (err: unknown) {
|
||||||
|
const error = err as { response?: { data?: { message?: string | string[] } } };
|
||||||
|
const message = error.response?.data?.message;
|
||||||
|
if (Array.isArray(message)) {
|
||||||
|
setError(message[0]);
|
||||||
|
} else {
|
||||||
|
setError(message || 'Failed to change password. Please try again.');
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -53,6 +112,20 @@ export function PasswordSecurityForm({ onSave }: PasswordSecurityFormProps) {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Error Message */}
|
||||||
|
{error && (
|
||||||
|
<div className="mb-4 p-3 bg-red-50 border border-red-200 text-red-600 rounded-[10px] text-[13px] font-serif">
|
||||||
|
{error}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Success Message */}
|
||||||
|
{success && (
|
||||||
|
<div className="mb-4 p-3 bg-green-50 border border-green-200 text-green-600 rounded-[10px] text-[13px] font-serif">
|
||||||
|
{success}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Form Fields */}
|
{/* Form Fields */}
|
||||||
<div className="space-y-6 max-w-md">
|
<div className="space-y-6 max-w-md">
|
||||||
{/* Current Password */}
|
{/* Current Password */}
|
||||||
@@ -132,9 +205,10 @@ export function PasswordSecurityForm({ onSave }: PasswordSecurityFormProps) {
|
|||||||
<div className="mt-8">
|
<div className="mt-8">
|
||||||
<button
|
<button
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
className="px-8 py-3 bg-[#e58625] text-white rounded-[15px] font-fractul font-medium text-[14px] hover:bg-[#d47920] transition-colors"
|
disabled={loading}
|
||||||
|
className="px-8 py-3 bg-[#e58625] text-white rounded-[15px] font-fractul font-medium text-[14px] hover:bg-[#d47920] transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
>
|
>
|
||||||
Update Password
|
{loading ? 'Updating...' : 'Update Password'}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user