From e00ecbf116e46ead32718f2166b4da9a5fec9334 Mon Sep 17 00:00:00 2001 From: vinix Date: Fri, 10 Oct 2025 15:49:31 +0700 Subject: [PATCH] feat: add change password functionality for users Add ChangePasswordModal component: - Create modal with new password and confirmation fields - Implement password validation (min 8 chars, uppercase, lowercase, number, special char) - Add real-time error validation and display - Show password requirements info box - Display username for confirmation - Add loading state during password change - Success/error notifications Update IndexUser: - Add state management for change password modal - Pass props to ListUser and ChangePasswordModal - Integrate ChangePasswordModal component Update ListUser: - Add KeyOutlined icon for change password button - Add purple change password button in action column - Implement showChangePasswordModal function - Update columns to include change password handler - Increase action column width to 18% API endpoint: PUT /api/user/change-password/:id --- src/pages/user/IndexUser.jsx | 11 + .../user/component/ChangePasswordModal.jsx | 258 ++++++++++++++++++ 2 files changed, 269 insertions(+) create mode 100644 src/pages/user/component/ChangePasswordModal.jsx diff --git a/src/pages/user/IndexUser.jsx b/src/pages/user/IndexUser.jsx index b03733a..974f40f 100644 --- a/src/pages/user/IndexUser.jsx +++ b/src/pages/user/IndexUser.jsx @@ -2,6 +2,7 @@ import React, { memo, useState, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import ListUser from './component/ListUser'; import DetailUser from './component/DetailUser'; +import ChangePasswordModal from './component/ChangePasswordModal'; import { useBreadcrumb } from '../../layout/LayoutBreadcrumb'; import { Typography } from 'antd'; @@ -15,6 +16,8 @@ const IndexUser = memo(function IndexUser() { const [selectedData, setSelectedData] = useState(null); const [readOnly, setReadOnly] = useState(false); const [showModal, setShowmodal] = useState(false); + const [showChangePasswordModal, setShowChangePasswordModal] = useState(false); + const [selectedUserForPassword, setSelectedUserForPassword] = useState(null); const setMode = (param) => { setShowmodal(true); @@ -63,6 +66,8 @@ const IndexUser = memo(function IndexUser() { selectedData={selectedData} setSelectedData={setSelectedData} readOnly={readOnly} + setShowChangePasswordModal={setShowChangePasswordModal} + setSelectedUserForPassword={setSelectedUserForPassword} /> + ); }); diff --git a/src/pages/user/component/ChangePasswordModal.jsx b/src/pages/user/component/ChangePasswordModal.jsx new file mode 100644 index 0000000..5e83330 --- /dev/null +++ b/src/pages/user/component/ChangePasswordModal.jsx @@ -0,0 +1,258 @@ +import React, { useState, useEffect } from 'react'; +import { Modal, Input, Typography, Button, ConfigProvider } from 'antd'; +import { NotifAlert, NotifOk } from '../../../components/Global/ToastNotif'; +import { changePassword } from '../../../api/user'; + +const { Text } = Typography; + +const ChangePasswordModal = (props) => { + const [confirmLoading, setConfirmLoading] = useState(false); + const [formData, setFormData] = useState({ + newPassword: '', + confirmPassword: '', + }); + const [errors, setErrors] = useState({}); + + const validatePassword = (password) => { + if (!password) return 'Password wajib diisi'; + + // Must be at least 8 characters long + if (password.length < 8) { + return 'Password must be at least 8 characters long'; + } + + // Must contain at least one uppercase letter + if (!/[A-Z]/.test(password)) { + return 'Password must contain at least one uppercase letter'; + } + + // Must contain at least one lowercase letter + if (!/[a-z]/.test(password)) { + return 'Password must contain at least one lowercase letter'; + } + + // Must contain at least one number + if (!/\d/.test(password)) { + return 'Password must contain at least one number'; + } + + // Must contain at least one special character + if (!/[!@#$%^&*(),.?":{}|<>]/.test(password)) { + return 'Password must contain at least one special character'; + } + + return null; + }; + + const validateForm = () => { + const newErrors = {}; + + const passwordError = validatePassword(formData.newPassword); + if (passwordError) { + newErrors.newPassword = passwordError; + } + + if (!formData.confirmPassword) { + newErrors.confirmPassword = 'Konfirmasi password wajib diisi'; + } else if (formData.newPassword !== formData.confirmPassword) { + newErrors.confirmPassword = 'Password tidak cocok'; + } + + setErrors(newErrors); + return Object.keys(newErrors).length === 0; + }; + + const handleCancel = () => { + props.setShowModal(false); + props.setSelectedUser(null); + setFormData({ + newPassword: '', + confirmPassword: '', + }); + setErrors({}); + }; + + const handleSave = async () => { + if (!validateForm()) { + NotifOk({ + icon: 'warning', + title: 'Peringatan', + message: 'Mohon periksa kembali form Anda', + }); + return; + } + + setConfirmLoading(true); + + try { + const response = await changePassword(props.selectedUser.user_id, formData.newPassword); + + console.log('Change Password Response:', response); + + if (response && response.statusCode === 200) { + NotifOk({ + icon: 'success', + title: 'Berhasil', + message: `Password untuk user "${props.selectedUser.user_fullname}" berhasil diubah.`, + }); + + handleCancel(); + } else { + NotifAlert({ + icon: 'error', + title: 'Gagal', + message: response?.message || 'Terjadi kesalahan saat mengubah password.', + }); + } + } catch (error) { + console.error('Change Password Error:', error); + NotifAlert({ + icon: 'error', + title: 'Error', + message: error.message || 'Terjadi kesalahan pada server. Coba lagi nanti.', + }); + } + + setConfirmLoading(false); + }; + + const handleInputChange = (e) => { + const { name, value } = e.target; + setFormData({ + ...formData, + [name]: value, + }); + // Clear error for this field + if (errors[name]) { + setErrors({ + ...errors, + [name]: null, + }); + } + }; + + useEffect(() => { + if (!props.showModal) { + setFormData({ + newPassword: '', + confirmPassword: '', + }); + setErrors({}); + } + }, [props.showModal]); + + return ( + + + + + + + + , + ]} + width={500} + > +
+
+ Username + +
+ +
+ Password Baru + * + + {errors.newPassword && ( + {errors.newPassword} + )} +
+ +
+ Konfirmasi Password + * + + {errors.confirmPassword && ( + + {errors.confirmPassword} + + )} +
+ +
+ + Persyaratan password: +
    +
  • Minimal 8 karakter
  • +
  • Minimal 1 huruf besar (A-Z)
  • +
  • Minimal 1 huruf kecil (a-z)
  • +
  • Minimal 1 angka (0-9)
  • +
  • Minimal 1 karakter spesial (!@#$%^&*)
  • +
+
+
+
+
+ ); +}; + +export default ChangePasswordModal;