feat: add password requirements validation and indicators in ChangePasswordModal and DetailUser components

This commit is contained in:
2025-10-14 15:42:28 +07:00
parent eb90d89e0e
commit 9f6cb66c37
4 changed files with 214 additions and 27 deletions

View File

@@ -1,5 +1,6 @@
import React, { useEffect, useState } from 'react';
import { Modal, Input, Divider, Typography, Switch, Button, ConfigProvider, Select } from 'antd';
import { CheckCircleFilled, CloseCircleFilled } from '@ant-design/icons';
import { NotifAlert, NotifOk } from '../../../components/Global/ToastNotif';
import { createUser, updateUser } from '../../../api/user';
import { getAllRole } from '../../../api/role';
@@ -25,13 +26,41 @@ const DetailUser = (props) => {
};
const [FormData, setFormData] = useState(defaultData);
const [originalEmail, setOriginalEmail] = useState(''); // Track original email
const [errors, setErrors] = useState({});
// Password requirements state
const [passwordRequirements, setPasswordRequirements] = useState({
minLength: false,
hasUppercase: false,
hasLowercase: false,
hasNumber: false,
hasSpecialChar: false,
});
const handleCancel = () => {
props.setSelectedData(null);
props.setActionMode('list');
setFormData(defaultData);
setErrors({});
setPasswordRequirements({
minLength: false,
hasUppercase: false,
hasLowercase: false,
hasNumber: false,
hasSpecialChar: false,
});
};
// Check password requirements
const checkPasswordRequirements = (password) => {
setPasswordRequirements({
minLength: password.length >= 8,
hasUppercase: /[A-Z]/.test(password),
hasLowercase: /[a-z]/.test(password),
hasNumber: /\d/.test(password),
hasSpecialChar: /[!@#$%^&*(),.?":{}|<>]/.test(password),
});
};
const validatePhone = (phone) => {
@@ -145,10 +174,20 @@ const DetailUser = (props) => {
// Backend expects field names with 'user_' prefix
const payload = {
user_fullname: FormData.user_fullname,
user_email: FormData.user_email,
user_phone: phone,
};
// For update mode: only send email if it has changed
if (FormData.user_id) {
// Only include email if it has changed from original
if (FormData.user_email !== originalEmail) {
payload.user_email = FormData.user_email;
}
} else {
// For create mode: always send email
payload.user_email = FormData.user_email;
}
// Only add role_id if it exists (backend requires number >= 1, no null)
if (FormData.role_id) {
payload.role_id = FormData.role_id;
@@ -163,6 +202,7 @@ const DetailUser = (props) => {
// For update mode:
// - Don't send 'user_name' (username is immutable)
// - Don't send 'is_active' (backend validation schema doesn't allow it)
// - Only send email if it has changed
try {
console.log('Payload being sent:', payload);
@@ -214,6 +254,12 @@ const DetailUser = (props) => {
...FormData,
[name]: value,
});
// Check password requirements on password change
if (name === 'password') {
checkPasswordRequirements(value);
}
// Clear error for this field
if (errors[name]) {
setErrors({
@@ -288,8 +334,11 @@ const DetailUser = (props) => {
password: '',
confirmPassword: '',
});
// Store original email for comparison
setOriginalEmail(props.selectedData.user_email || '');
} else {
setFormData(defaultData);
setOriginalEmail('');
}
setErrors({});
@@ -451,6 +500,63 @@ const DetailUser = (props) => {
{errors.password}
</Text>
)}
{/* Password Requirements Indicator */}
<div style={{ marginTop: '8px', padding: '8px', backgroundColor: '#f5f5f5', borderRadius: '4px' }}>
<Text style={{ fontSize: '12px', fontWeight: '500', color: '#666' }}>Password harus memenuhi:</Text>
<div style={{ marginTop: '4px' }}>
<div style={{ display: 'flex', alignItems: 'center', marginBottom: '2px' }}>
{passwordRequirements.minLength ? (
<CheckCircleFilled style={{ color: '#52c41a', fontSize: '14px', marginRight: '6px' }} />
) : (
<CloseCircleFilled style={{ color: '#ff4d4f', fontSize: '14px', marginRight: '6px' }} />
)}
<Text style={{ fontSize: '12px', color: passwordRequirements.minLength ? '#52c41a' : '#ff4d4f' }}>
Minimal 8 karakter
</Text>
</div>
<div style={{ display: 'flex', alignItems: 'center', marginBottom: '2px' }}>
{passwordRequirements.hasUppercase ? (
<CheckCircleFilled style={{ color: '#52c41a', fontSize: '14px', marginRight: '6px' }} />
) : (
<CloseCircleFilled style={{ color: '#ff4d4f', fontSize: '14px', marginRight: '6px' }} />
)}
<Text style={{ fontSize: '12px', color: passwordRequirements.hasUppercase ? '#52c41a' : '#ff4d4f' }}>
Minimal 1 huruf besar (A-Z)
</Text>
</div>
<div style={{ display: 'flex', alignItems: 'center', marginBottom: '2px' }}>
{passwordRequirements.hasLowercase ? (
<CheckCircleFilled style={{ color: '#52c41a', fontSize: '14px', marginRight: '6px' }} />
) : (
<CloseCircleFilled style={{ color: '#ff4d4f', fontSize: '14px', marginRight: '6px' }} />
)}
<Text style={{ fontSize: '12px', color: passwordRequirements.hasLowercase ? '#52c41a' : '#ff4d4f' }}>
Minimal 1 huruf kecil (a-z)
</Text>
</div>
<div style={{ display: 'flex', alignItems: 'center', marginBottom: '2px' }}>
{passwordRequirements.hasNumber ? (
<CheckCircleFilled style={{ color: '#52c41a', fontSize: '14px', marginRight: '6px' }} />
) : (
<CloseCircleFilled style={{ color: '#ff4d4f', fontSize: '14px', marginRight: '6px' }} />
)}
<Text style={{ fontSize: '12px', color: passwordRequirements.hasNumber ? '#52c41a' : '#ff4d4f' }}>
Minimal 1 angka (0-9)
</Text>
</div>
<div style={{ display: 'flex', alignItems: 'center', marginBottom: '2px' }}>
{passwordRequirements.hasSpecialChar ? (
<CheckCircleFilled style={{ color: '#52c41a', fontSize: '14px', marginRight: '6px' }} />
) : (
<CloseCircleFilled style={{ color: '#ff4d4f', fontSize: '14px', marginRight: '6px' }} />
)}
<Text style={{ fontSize: '12px', color: passwordRequirements.hasSpecialChar ? '#52c41a' : '#ff4d4f' }}>
Minimal 1 karakter spesial (!@#$%^&*(),.?":&#123;&#125;|&lt;&gt;)
</Text>
</div>
</div>
</div>
</div>
<div style={{ marginBottom: 12 }}>