feat: add password requirements validation and indicators in ChangePasswordModal and DetailUser components
This commit is contained in:
@@ -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 (!@#$%^&*(),.?":{}|<>)
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
|
||||
Reference in New Issue
Block a user