diff --git a/ipi-survey-platform/src/pages/Admin/configuration/AdminUsers/AddAdminUsers.jsx b/ipi-survey-platform/src/pages/Admin/configuration/AdminUsers/AddAdminUsers.jsx
index 57e8d44..8a09366 100644
--- a/ipi-survey-platform/src/pages/Admin/configuration/AdminUsers/AddAdminUsers.jsx
+++ b/ipi-survey-platform/src/pages/Admin/configuration/AdminUsers/AddAdminUsers.jsx
@@ -30,20 +30,31 @@ const AddAdminUsers = ({ isOpen, onClose, onSave }) => {
setToastData(null);
}, 4000);
};
- const validateForm = () => {
- const newErrors = {};
- if (!formData.name.trim()) newErrors.name = 'Name is required';
- if (!formData.email.trim()) newErrors.email = 'Email is required';
- else if (!/^\S+@\S+\.\S+$/.test(formData.email))
- newErrors.email = 'Please enter a valid email';
- if (!formData.status) newErrors.status = 'Status is required';
- if (!formData.password) newErrors.password = 'Password is required';
- else if (formData.password.length < 12)
- newErrors.password = 'Password must be at least 12 characters';
- if (formData.password !== formData.confirmPassword)
- newErrors.confirmPassword = 'Passwords do not match';
- return newErrors;
- };
+ const validateForm = () => {
+ const newErrors = {};
+
+ if (!formData.name.trim()) newErrors.name = 'Name is required';
+
+ if (!formData.email.trim()) newErrors.email = 'Email is required';
+ else if (!/^\S+@\S+\.\S+$/.test(formData.email))
+ newErrors.email = 'Please enter a valid email';
+
+ if (!formData.status) newErrors.status = 'Status is required';
+
+ if (!formData.password) {
+ newErrors.password = 'Password is required';
+ } else if (formData.password.length < 8) {
+ newErrors.password = 'Password must be at least 8 characters';
+ } else if (formData.password.length > 12) {
+ newErrors.password = 'Password cannot exceed 12 characters';
+ }
+
+ if (formData.password !== formData.confirmPassword)
+ newErrors.confirmPassword = 'Passwords do not match';
+
+ return newErrors;
+};
+
useEffect(() => {
if (isOpen) {
diff --git a/ipi-survey-platform/src/pages/Admin/configuration/AdminUsers/ListAdminUsers.jsx b/ipi-survey-platform/src/pages/Admin/configuration/AdminUsers/ListAdminUsers.jsx
index 8017102..52c2e48 100644
--- a/ipi-survey-platform/src/pages/Admin/configuration/AdminUsers/ListAdminUsers.jsx
+++ b/ipi-survey-platform/src/pages/Admin/configuration/AdminUsers/ListAdminUsers.jsx
@@ -15,7 +15,7 @@ import { TextField, SelectField } from '@/components/common/FormControls';
import EditUserModal from './EditUserModal';
import DeleteUserModal from './DeleteUserModal';
import ResetPasswordModal from './ResetPasswordModal';
-
+
const searchIconSrc = '/assets/images/material-symbols_search-rounded.svg';
const exportIconSrc = '/assets/images/DownloadSimple.svg';
const addIconSrc = '/assets/images/ic_baseline-plus.svg';
@@ -29,7 +29,7 @@ const inactiveUserIconSrc = '/assets/images/inactive-user.svg';
const resetPasswordIconSrc = '/assets/images/hugeicons_reset-password.svg';
const resetPasswordInactiveIconSrc =
'/assets/images/hugeicons_reset-password-inactive.svg';
-
+
const SummaryCard = ({ label, count, icon, iconBg = '#FDF7EB' }) => (
@@ -44,7 +44,7 @@ const SummaryCard = ({ label, count, icon, iconBg = '#FDF7EB' }) => (
{count}
);
-
+
const StatusBadge = ({ status }) => {
const map = {
Active: 'bg-green-50 text-green-700 ring-1 ring-green-200',
@@ -60,7 +60,7 @@ const StatusBadge = ({ status }) => {
);
};
-
+
const AdminUsers = () => {
const [isAddUserModalOpen, setIsAddUserModalOpen] = useState(false);
const [isEditModalOpen, setIsEditModalOpen] = useState(false);
@@ -71,7 +71,7 @@ const AdminUsers = () => {
const [saving, setSaving] = useState(false);
const [currentPage, setCurrentPage] = useState(1);
const pageSize = 10;
-
+
const [showDeleteModal, setShowDeleteModal] = useState(false);
const [selectedUser, setSelectedUser] = useState(null);
const [deletingRow, setDeletingRow] = useState(null);
@@ -83,7 +83,7 @@ const AdminUsers = () => {
const [errors, setErrors] = useState({});
const [toastData, setToastData] = useState(null);
const navigate = useNavigate();
-
+
// Reset password modal state
const [isResetModalOpen, setIsResetModalOpen] = useState(false);
const [resetUser, setResetUser] = useState(null);
@@ -92,7 +92,7 @@ const AdminUsers = () => {
const [confirmPassword, setConfirmPassword] = useState('');
const [resettingPassword, setResettingPassword] = useState(false);
const [resetErrors, setResetErrors] = useState({});
-
+
// Helper: show toast
const showToast = (message, type = 'success') => {
setToastData({ message, type });
@@ -103,14 +103,14 @@ const AdminUsers = () => {
setToastData(null);
}, 4000);
};
-
+
// Fetch users
useEffect(() => {
const fetchUsers = async () => {
try {
setLoading(true);
const res = await getAdminUser();
-
+
const usersData = Array.isArray(res)
? res
: Array.isArray(res?.data)
@@ -118,13 +118,13 @@ const AdminUsers = () => {
: Array.isArray(res?.data?.data)
? res.data.data
: null;
-
+
if (!usersData) {
showToast('Failed to load users', 'error');
setUsers([]);
return;
}
-
+
const mappedUsers = usersData.map((user) => {
const id = user.id ?? user._id ?? user.user_id ?? null;
const name = user.name ?? user.fullName ?? 'N/A';
@@ -151,7 +151,7 @@ const AdminUsers = () => {
raw: user,
};
});
-
+
setUsers(mappedUsers);
} catch (error) {
console.error('Error fetching admin users:', error);
@@ -161,14 +161,14 @@ const AdminUsers = () => {
setLoading(false);
}
};
-
+
fetchUsers();
}, []);
-
+
const totalUsers = users.length;
const activeUsers = users.filter((user) => user.status === 'Active').length;
const inactiveUsers = totalUsers - activeUsers;
-
+
const summaryItems = [
{
label: 'Total Users',
@@ -189,10 +189,10 @@ const AdminUsers = () => {
iconBg: '#F5F5F7',
},
];
-
+
const headers = ['Name', 'Email', 'Last Login', 'Status', 'Actions'];
const columnWidths = [100, 130, 150, 100, 130];
-
+
const rows = users.map((user) => [
user.name,
user.email,
@@ -200,7 +200,7 @@ const AdminUsers = () => {
,
'actions',
]);
-
+
const handleFormChange = (e) => {
if (!e) return;
if (e.target && e.target.name !== undefined) {
@@ -212,30 +212,30 @@ const AdminUsers = () => {
if (errors[e.name]) setErrors((prev) => ({ ...prev, [e.name]: '' }));
}
};
-
+
const handleUpdateUser = async () => {
try {
setLoading(true);
-
+
const updatedUser = {
name: formData.name,
email: formData.email,
is_active: formData.status === "Active",
};
-
+
const res = await updateAdminUser(currentUser.id, updatedUser);
-
+
const success =
res?.status === "success" ||
res?.code === 200 ||
res?.data?.status === "success" ||
(!res.error && res);
-
+
if (!success) throw new Error(res?.message || "Failed to update user");
-
+
showToast("User updated successfully!", "success");
setIsEditModalOpen(false);
-
+
setUsers((prev) =>
prev.map((u) =>
u.id === currentUser.id
@@ -249,7 +249,7 @@ const AdminUsers = () => {
: u
)
);
-
+
setCurrentUser(null);
} catch (error) {
console.error("Error updating user:", error);
@@ -258,27 +258,27 @@ const AdminUsers = () => {
setLoading(false);
}
};
-
+
const validateForm = () => {
const newErrors = {};
if (!formData.name || !formData.name.trim()) newErrors.name = 'Name is required';
if (!formData.status) newErrors.status = 'Status is required';
return newErrors;
};
-
+
const handleDeleteClick = (user) => {
setSelectedUser(user);
setShowDeleteModal(true);
};
-
+
const handleConfirmDelete = async () => {
try {
if (!selectedUser) return;
setDeletingRow(selectedUser.id);
await deleteAdminUser(selectedUser.id);
-
+
showToast("User deleted successfully!", "success");
-
+
setUsers((prev) => prev.filter((u) => u.id !== selectedUser.id));
} catch (error) {
showToast("Failed to delete user!", "error");
@@ -287,26 +287,26 @@ const AdminUsers = () => {
setShowDeleteModal(false);
}
};
-
+
const handleEdit = async (user) => {
if (!user || !user.id) {
showToast('Invalid user selected', 'error');
return;
}
-
+
setEditingRow(user);
setLoading(true);
-
+
try {
const res = await getAdminUserById(user.id);
const payload = res?.data ?? res;
if (!payload) throw new Error('No user data returned from API');
-
+
const name = payload.name ?? payload.fullName ?? user.name ?? '';
const email = payload.email ?? user.email ?? '';
const is_active =
payload.is_active !== undefined ? !!payload.is_active : !!user.is_active;
-
+
setCurrentUser({
...payload,
id: user.id,
@@ -314,13 +314,13 @@ const AdminUsers = () => {
email,
is_active,
});
-
+
setFormData({
name,
email,
status: is_active ? 'Active' : 'Inactive',
});
-
+
setErrors({});
setIsEditModalOpen(true);
} catch (error) {
@@ -330,7 +330,7 @@ const AdminUsers = () => {
setLoading(false);
}
};
-
+
const handleOpenResetModal = (user) => {
setResetUser(user);
setOldPassword('');
@@ -339,7 +339,7 @@ const AdminUsers = () => {
setResetErrors({});
setIsResetModalOpen(true);
};
-
+
const handleCloseResetModal = () => {
setResetUser(null);
setOldPassword('');
@@ -349,60 +349,67 @@ const AdminUsers = () => {
setIsResetModalOpen(false);
setResettingPassword(false);
};
-
+
// Validate reset password form - Updated to allow 8 to 12 characters
- const validateResetForm = () => {
- const newErrors = {};
-
- if (!oldPassword.trim()) {
- newErrors.oldPassword = 'Old password is required';
- }
-
- if (!newPassword.trim()) {
- newErrors.newPassword = 'New password is required';
- } else if (newPassword.length < 8) {
- newErrors.newPassword = 'Password must be at least 8 characters';
- }
-
- if (!confirmPassword.trim()) {
- newErrors.confirmPassword = 'Please confirm your password';
- } else if (newPassword !== confirmPassword) {
- newErrors.confirmPassword = 'Passwords do not match';
- }
-
- return newErrors;
- };
-
+ const validateResetForm = () => {
+ const newErrors = {};
+
+ // Old password validation
+ if (!oldPassword?.trim()) {
+ newErrors.oldPassword = 'Old password is required';
+ }
+
+ // New password validation
+ if (!newPassword?.trim()) {
+ newErrors.newPassword = 'New password is required';
+ } else if (newPassword.length < 8) {
+ newErrors.newPassword = 'Password must be at least 8 characters long';
+ }
+
+ // Confirm password validation
+ if (!confirmPassword?.trim()) {
+ newErrors.confirmPassword = 'Please confirm your password';
+ } else if (newPassword !== confirmPassword) {
+ newErrors.confirmPassword = 'Passwords do not match';
+ }
+
+ return newErrors;
+};
+
+
+
+
+
// Reset Password API Integration
const handleResetPassword = async () => {
if (!resetUser || !resetUser.id) {
showToast('No user selected for password reset', 'error');
return;
}
-
+
// Validate form
const formErrors = validateResetForm();
if (Object.keys(formErrors).length > 0) {
setResetErrors(formErrors);
return;
}
-
+
try {
setResettingPassword(true);
-
+
// Prepare data according to API specification
const passwordData = {
old_password: oldPassword,
new_password: newPassword,
confirm_password: confirmPassword
};
-
+
console.log('đ Initiating admin password reset for user:', resetUser.id);
-
+
const res = await changeAdminUserPassword(resetUser.id, passwordData);
-
+
console.log('â
Admin password reset response:', res);
-
+
// Check for success based on common response patterns
const success =
res?.status === "success" ||
@@ -413,26 +420,26 @@ const AdminUsers = () => {
res?.message?.toLowerCase().includes("updated") ||
res?.message?.toLowerCase().includes("changed") ||
(!res.error && res);
-
+
if (!success) {
throw new Error(res?.message || res?.data?.message || "Failed to reset password");
}
-
+
showToast('Admin password reset successfully!', 'success');
handleCloseResetModal();
-
+
} catch (error) {
console.error('â Error resetting admin password:', error);
-
+
// Handle specific error cases
let errorMessage = 'Failed to reset password';
-
+
if (error.message) {
errorMessage = error.message;
}
-
+
// Check for common admin password change errors
- if (errorMessage.toLowerCase().includes('old password') ||
+ if (errorMessage.toLowerCase().includes('old password') ||
errorMessage.toLowerCase().includes('current password') ||
errorMessage.toLowerCase().includes('incorrect password')) {
setResetErrors(prev => ({
@@ -446,14 +453,14 @@ const AdminUsers = () => {
confirmPassword: 'New passwords do not match'
}));
showToast('New passwords do not match', 'error');
- } else if (errorMessage.toLowerCase().includes('same') ||
+ } else if (errorMessage.toLowerCase().includes('same') ||
errorMessage.toLowerCase().includes('previous')) {
setResetErrors(prev => ({
...prev,
newPassword: 'New password cannot be the same as current password'
}));
showToast('New password cannot be the same as current password', 'error');
- } else if (errorMessage.toLowerCase().includes('weak') ||
+ } else if (errorMessage.toLowerCase().includes('weak') ||
errorMessage.toLowerCase().includes('strength')) {
setResetErrors(prev => ({
...prev,
@@ -469,13 +476,13 @@ const AdminUsers = () => {
setResettingPassword(false);
}
};
-
+
const handleDelete = (rowIndex) => {
const user = users[rowIndex];
setSelectedUser(user);
setShowDeleteModal(true);
};
-
+
const handleEditSubmit = async (e) => {
e.preventDefault();
const formErrors = validateForm();
@@ -484,12 +491,12 @@ const AdminUsers = () => {
showToast('Please fix the form errors', 'error');
return;
}
-
+
if (!currentUser || !currentUser.id) {
showToast('No user selected for update', 'error');
return;
}
-
+
setSaving(true);
try {
const payload = {
@@ -497,16 +504,16 @@ const AdminUsers = () => {
email: formData.email,
is_active: formData.status === 'Active',
};
-
+
const res = await updateAdminUser(currentUser.id, payload);
const success =
res?.status === 'success' ||
res?.status === 200 ||
res?.data?.status === 'success' ||
(!res.error && res);
-
+
if (!success) throw new Error(res?.message || 'Failed to update user');
-
+
setUsers((prev) =>
prev.map((u) =>
u.id === currentUser.id
@@ -520,7 +527,7 @@ const AdminUsers = () => {
: u
)
);
-
+
showToast('User updated successfully', 'success');
setIsEditModalOpen(false);
setEditingRow(null);
@@ -532,7 +539,7 @@ const AdminUsers = () => {
setSaving(false);
}
};
-
+
const tableToolbar = (
Admin Users
@@ -554,26 +561,26 @@ const AdminUsers = () => {
type="button"
onClick={() => {
if (!users.length) return;
-
+
const csvHeader = ['Name', 'Email', 'Status', 'Last Login'].join(',');
-
+
const csvRows = users.map((user) =>
[user.name, user.email, user.status, user.lastLogin]
.map((value) => `"${String(value ?? '').replace(/"/g, '""')}"`)
.join(',')
);
-
+
const blob = new Blob([csvHeader + '\n' + csvRows.join('\n')], {
type: 'text/csv;charset=utf-8;',
});
-
+
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', 'admin-users.csv');
document.body.appendChild(link);
link.click();
-
+
document.body.removeChild(link);
URL.revokeObjectURL(url);
}}
@@ -587,7 +594,7 @@ const AdminUsers = () => {
Export CSV
-
+
);
-
+
return (
{/* Summary cards */}
@@ -608,7 +615,7 @@ const AdminUsers = () => {
))}
-
+
{/* Table */}