fixed security issues bugs and graph added for products
This commit is contained in:
parent
2bda726d40
commit
30057d56c0
527
ipi-survey-platform/package-lock.json
generated
527
ipi-survey-platform/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -11,13 +11,16 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.12.2",
|
"axios": "^1.12.2",
|
||||||
|
"chart.js": "^4.5.1",
|
||||||
"lucide-react": "^0.548.0",
|
"lucide-react": "^0.548.0",
|
||||||
"react": "^19.1.1",
|
"react": "^19.1.1",
|
||||||
|
"react-chartjs-2": "^5.3.1",
|
||||||
"react-dom": "^19.1.1",
|
"react-dom": "^19.1.1",
|
||||||
"react-google-recaptcha": "^3.1.0",
|
"react-google-recaptcha": "^3.1.0",
|
||||||
"react-icons": "^5.5.0",
|
"react-icons": "^5.5.0",
|
||||||
"react-router-dom": "^7.9.4",
|
"react-router-dom": "^7.9.4",
|
||||||
"react-toastify": "^11.0.5"
|
"react-toastify": "^11.0.5",
|
||||||
|
"recharts": "^3.5.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.36.0",
|
"@eslint/js": "^9.36.0",
|
||||||
|
|||||||
@ -17,30 +17,27 @@ import EditCompanyProfile from '@/pages/Admin/configuration/EditCompanyProfile';
|
|||||||
import Navbar from './pages/LandingPage/component/Navbar';
|
import Navbar from './pages/LandingPage/component/Navbar';
|
||||||
const RequireRole = ({ allowedRoles = [], children }) => {
|
const RequireRole = ({ allowedRoles = [], children }) => {
|
||||||
let role;
|
let role;
|
||||||
let token;
|
|
||||||
try {
|
try {
|
||||||
role = sessionStorage.getItem('user_role');
|
role = localStorage.getItem("user_role");
|
||||||
token = sessionStorage.getItem('auth_token');
|
} catch {
|
||||||
} catch (error) {
|
|
||||||
role = null;
|
role = null;
|
||||||
token = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const normalizedRole = String(role || '').toLowerCase();
|
const normalizedRole = String(role || "").toLowerCase();
|
||||||
const isAllowed =
|
|
||||||
Boolean(token) &&
|
const isAllowed = allowedRoles.some(
|
||||||
allowedRoles.some(
|
(allowed) => String(allowed || "").toLowerCase() === normalizedRole
|
||||||
(allowed) => String(allowed || '').toLowerCase() === normalizedRole
|
);
|
||||||
);
|
|
||||||
|
|
||||||
if (isAllowed) {
|
if (isAllowed) {
|
||||||
return children;
|
return children;
|
||||||
}
|
}
|
||||||
|
|
||||||
// return <Navigate to="/login" replace />;
|
return <Navigate to="/index" replace />;
|
||||||
return <Navigate to="/index" replace />
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const SessionWarningModal = ({ show, remainingTime, onExtend, onLogout }) => {
|
const SessionWarningModal = ({ show, remainingTime, onExtend, onLogout }) => {
|
||||||
if (!show) return null;
|
if (!show) return null;
|
||||||
|
|
||||||
|
|||||||
@ -72,7 +72,7 @@ const AdminHeader = () => {
|
|||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
try {
|
try {
|
||||||
const stored = sessionStorage.getItem('user_profile');
|
const stored = localStorage.getItem('user_profile');
|
||||||
if (stored) {
|
if (stored) {
|
||||||
const parsed = JSON.parse(stored);
|
const parsed = JSON.parse(stored);
|
||||||
if (parsed?.name || parsed?.username) {
|
if (parsed?.name || parsed?.username) {
|
||||||
|
|||||||
@ -33,7 +33,8 @@ const SubmissionTable = ({ selectedQuarter, selectedYear }) => {
|
|||||||
params.append('year', year);
|
params.append('year', year);
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await apiClient.get(`/admin_dashboard?${params.toString()}`);
|
// const response = await apiClient.get(`/admin_dashboard?${params.toString()}`);
|
||||||
|
const response = await apiClient.get(`/admin_dashboard?${params.toString()}`, { withCredentials: true });
|
||||||
const result = response?.data;
|
const result = response?.data;
|
||||||
if (result?.status === 'success') {
|
if (result?.status === 'success') {
|
||||||
// Map the data to include the updated_at as reviewerOn
|
// Map the data to include the updated_at as reviewerOn
|
||||||
@ -139,7 +140,7 @@ const SubmissionTable = ({ selectedQuarter, selectedYear }) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const userProfile = JSON.parse(sessionStorage.getItem("user_profile"));
|
const userProfile = JSON.parse(localStorage.getItem("user_profile"));
|
||||||
|
|
||||||
const handleActionClick = (submission, type) => {
|
const handleActionClick = (submission, type) => {
|
||||||
setSelectedSubmission(submission);
|
setSelectedSubmission(submission);
|
||||||
@ -181,7 +182,7 @@ const handleApproveConfirm = async () => {
|
|||||||
if (ok) {
|
if (ok) {
|
||||||
toast.success('Submission approved successfully');
|
toast.success('Submission approved successfully');
|
||||||
|
|
||||||
const currentUser = JSON.parse(sessionStorage.getItem("user_profile"))?.name || 'Admin';
|
const currentUser = JSON.parse(localStorage.getItem("user_profile"))?.name || 'Admin';
|
||||||
const updatedAt = response?.data?.updated_at || new Date().toISOString();
|
const updatedAt = response?.data?.updated_at || new Date().toISOString();
|
||||||
|
|
||||||
// Update the status of the current item in the data array
|
// Update the status of the current item in the data array
|
||||||
@ -235,7 +236,7 @@ const handleRejectConfirm = async () => {
|
|||||||
if (ok) {
|
if (ok) {
|
||||||
toast.error('Submission has been rejected');
|
toast.error('Submission has been rejected');
|
||||||
|
|
||||||
const currentUser = JSON.parse(sessionStorage.getItem("user_profile"))?.name || 'Admin';
|
const currentUser = JSON.parse(localStorage.getItem("user_profile"))?.name || 'Admin';
|
||||||
const updatedAt = response?.data?.updated_at || new Date().toISOString();
|
const updatedAt = response?.data?.updated_at || new Date().toISOString();
|
||||||
|
|
||||||
// Update the status of the current item in the data array
|
// Update the status of the current item in the data array
|
||||||
@ -309,7 +310,7 @@ const tableRows = filtered.map((item) => [
|
|||||||
renderStatusBadge(item?.status),
|
renderStatusBadge(item?.status),
|
||||||
// Reviewer - show reviewer_name after approval/rejection, otherwise show '-'
|
// Reviewer - show reviewer_name after approval/rejection, otherwise show '-'
|
||||||
item?.status?.toLowerCase() === 'approved' || item?.status?.toLowerCase() === 'rejected'
|
item?.status?.toLowerCase() === 'approved' || item?.status?.toLowerCase() === 'rejected'
|
||||||
? (item?.reviewer_name || JSON.parse(sessionStorage.getItem("user_profile"))?.name || 'Admin')
|
? (item?.reviewer_name || JSON.parse(localStorage.getItem("user_profile"))?.name || 'Admin')
|
||||||
: '-',
|
: '-',
|
||||||
// Reviewed On - show updated_at after approval/rejection, otherwise show '-'
|
// Reviewed On - show updated_at after approval/rejection, otherwise show '-'
|
||||||
item?.status?.toLowerCase() === 'approved' || item?.status?.toLowerCase() === 'rejected'
|
item?.status?.toLowerCase() === 'approved' || item?.status?.toLowerCase() === 'rejected'
|
||||||
|
|||||||
@ -36,7 +36,7 @@ export const WelcomeSection = ({ data = null, loading = false, error = '' }) =>
|
|||||||
Welcome{' '}
|
Welcome{' '}
|
||||||
{(() => {
|
{(() => {
|
||||||
try {
|
try {
|
||||||
const userProfile = JSON.parse(sessionStorage.getItem('user_profile'));
|
const userProfile = JSON.parse(localStorage.getItem('user_profile'));
|
||||||
return userProfile?.name ? userProfile.name : '';
|
return userProfile?.name ? userProfile.name : '';
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error reading user_profile from sessionStorage:', error);
|
console.error('Error reading user_profile from sessionStorage:', error);
|
||||||
|
|||||||
@ -18,7 +18,7 @@ const HeaderBar = () => {
|
|||||||
const [userOpen, setUserOpen] = React.useState(false);
|
const [userOpen, setUserOpen] = React.useState(false);
|
||||||
const profileRef = React.useRef(null);
|
const profileRef = React.useRef(null);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const establishmentId = sessionStorage.getItem("establishment_id") || "";
|
const establishmentId = localStorage.getItem("establishment_id") || "";
|
||||||
|
|
||||||
// Close dropdown when clicking outside
|
// Close dropdown when clicking outside
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
@ -37,7 +37,7 @@ const HeaderBar = () => {
|
|||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
let parsed;
|
let parsed;
|
||||||
try {
|
try {
|
||||||
const stored = sessionStorage.getItem('user_profile');
|
const stored = localStorage.getItem('user_profile');
|
||||||
if (stored) {
|
if (stored) {
|
||||||
parsed = JSON.parse(stored);
|
parsed = JSON.parse(stored);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,10 @@
|
|||||||
import React, { useEffect, useState, useMemo } from 'react';
|
import React, { useEffect, useState, useMemo } from 'react';
|
||||||
|
|
||||||
|
// Get and encode establishment ID for URL
|
||||||
|
const getSafeEstablishmentId = () => {
|
||||||
|
const establishmentId = localStorage.getItem('establishment_id') || '';
|
||||||
|
return encodeURIComponent(establishmentId);
|
||||||
|
};
|
||||||
import { useNavigate, useLocation } from 'react-router-dom';
|
import { useNavigate, useLocation } from 'react-router-dom';
|
||||||
import { getEstablishmentProducts } from '../../../services/submissions/submissionService';
|
import { getEstablishmentProducts } from '../../../services/submissions/submissionService';
|
||||||
import Table from '@/components/common/Table';
|
import Table from '@/components/common/Table';
|
||||||
@ -109,7 +115,7 @@ const EstablishmentInfo = ({
|
|||||||
// Fetch establishment products
|
// Fetch establishment products
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const fetchProducts = async () => {
|
const fetchProducts = async () => {
|
||||||
const establishmentId = sessionStorage.getItem('establishment_id');
|
const establishmentId = localStorage.getItem('establishment_id');
|
||||||
if (!establishmentId) return;
|
if (!establishmentId) return;
|
||||||
|
|
||||||
setLoadingProducts(true);
|
setLoadingProducts(true);
|
||||||
@ -280,14 +286,14 @@ const EstablishmentInfo = ({
|
|||||||
const handleEditProfile = (e) => {
|
const handleEditProfile = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
const establishmentId = sessionStorage.getItem('establishment_id');
|
const establishmentId = localStorage.getItem('establishment_id');
|
||||||
if (!establishmentId) {
|
if (!establishmentId) {
|
||||||
alert('No establishment ID found in session');
|
alert('No establishment ID found in session');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
localStorage.setItem('returnTo', window.location.pathname);
|
localStorage.setItem('returnTo', window.location.pathname);
|
||||||
sessionStorage.setItem('edit_profile_from', 'EstablishmentUser');
|
localStorage.setItem('edit_profile_from', 'EstablishmentUser');
|
||||||
navigate(`/edit-profile/${establishmentId}`);
|
navigate(`/edit-profile/${establishmentId}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -687,7 +693,7 @@ const EstablishmentInfo = ({
|
|||||||
<p className="text-sm text-[#043DFF] bg-[#E7F5FF]">
|
<p className="text-sm text-[#043DFF] bg-[#E7F5FF]">
|
||||||
<span className="font-medium">Need to update details?</span> Go to Profile →{' '}
|
<span className="font-medium">Need to update details?</span> Go to Profile →{' '}
|
||||||
<a
|
<a
|
||||||
href={`/admin/configuration/edit-profile/${sessionStorage.getItem('establishment_id') || ''}`}
|
href={`/admin/configuration/edit-profile/${getSafeEstablishmentId()}`}
|
||||||
className="underline font-medium text-[#043DFF] hover:text-[#063B82]"
|
className="underline font-medium text-[#043DFF] hover:text-[#063B82]"
|
||||||
onClick={handleEditProfile}
|
onClick={handleEditProfile}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -503,7 +503,7 @@ const ProductData = ({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchEstablishmentProducts = async () => {
|
const fetchEstablishmentProducts = async () => {
|
||||||
try {
|
try {
|
||||||
const establishmentId = sessionStorage.getItem('establishment_id');
|
const establishmentId = localStorage.getItem('establishment_id');
|
||||||
if (!establishmentId) return;
|
if (!establishmentId) return;
|
||||||
|
|
||||||
const fetchProducts = async () => {
|
const fetchProducts = async () => {
|
||||||
@ -902,7 +902,7 @@ const location = useLocation();
|
|||||||
|
|
||||||
// Fetch previous forecast data when product is selected
|
// Fetch previous forecast data when product is selected
|
||||||
if (selectedProduct?.value) {
|
if (selectedProduct?.value) {
|
||||||
const establishmentId = sessionStorage.getItem('establishment_id') ||
|
const establishmentId = localStorage.getItem('establishment_id') ||
|
||||||
localStorage.getItem('establishmentId') ||
|
localStorage.getItem('establishmentId') ||
|
||||||
localStorage.getItem('establishment_id');
|
localStorage.getItem('establishment_id');
|
||||||
|
|
||||||
@ -915,7 +915,7 @@ const location = useLocation();
|
|||||||
establishmentId,
|
establishmentId,
|
||||||
quarter,
|
quarter,
|
||||||
year,
|
year,
|
||||||
storageSource: sessionStorage.getItem('establishment_id') ? 'sessionStorage' : 'localStorage'
|
storageSource: localStorage.getItem('establishment_id') ? 'sessionStorage' : 'localStorage'
|
||||||
});
|
});
|
||||||
|
|
||||||
// Call handleProductSelect with the product ID
|
// Call handleProductSelect with the product ID
|
||||||
@ -1354,7 +1354,7 @@ const location = useLocation();
|
|||||||
if (selectedProduct?.value) {
|
if (selectedProduct?.value) {
|
||||||
try {
|
try {
|
||||||
// Get establishment ID from sessionStorage or localStorage
|
// Get establishment ID from sessionStorage or localStorage
|
||||||
const establishmentId = sessionStorage.getItem('establishment_id') ||
|
const establishmentId = localStorage.getItem('establishment_id') ||
|
||||||
localStorage.getItem('establishmentId') ||
|
localStorage.getItem('establishmentId') ||
|
||||||
localStorage.getItem('establishment_id');
|
localStorage.getItem('establishment_id');
|
||||||
|
|
||||||
|
|||||||
8
ipi-survey-platform/src/constants/assets.js
Normal file
8
ipi-survey-platform/src/constants/assets.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
export const ASSETS = {
|
||||||
|
IMAGES: {
|
||||||
|
LOGO: '/assets/images/FCSCLogo.svg'
|
||||||
|
},
|
||||||
|
CLASSES: {
|
||||||
|
AUTH_CONTAINER: 'min-h-screen bg-[#F7F7F7] flex items-center justify-center px-4'
|
||||||
|
}
|
||||||
|
};
|
||||||
16
ipi-survey-platform/src/constants/validationMessages.js
Normal file
16
ipi-survey-platform/src/constants/validationMessages.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
export const VALIDATION_MESSAGES = {
|
||||||
|
OLD_PASSWORD_REQUIRED: 'Old password is required',
|
||||||
|
PASSWORD_REQUIRED: 'Password is required',
|
||||||
|
PASSWORD_MIN_LENGTH: 'Password must be at least 8 characters',
|
||||||
|
PASSWORD_UPPERCASE: 'Must contain at least one uppercase letter',
|
||||||
|
PASSWORD_LOWERCASE: 'Must contain at least one lowercase letter',
|
||||||
|
PASSWORD_NUMBER: 'Must contain at least one number',
|
||||||
|
PASSWORD_SPECIAL_CHAR: 'Must contain at least one special character (!@#$%^&*)',
|
||||||
|
CONFIRM_PASSWORD_REQUIRED: 'Please confirm your password',
|
||||||
|
PASSWORDS_DONT_MATCH: 'Passwords do not match',
|
||||||
|
INCORRECT_CURRENT_PASSWORD: 'The current password is incorrect',
|
||||||
|
PASSWORD_SAME_AS_CURRENT: 'New password cannot be the same as current password',
|
||||||
|
PASSWORD_TOO_WEAK: 'Password is too weak. Use a stronger password.',
|
||||||
|
PASSWORD_COMPLEXITY_ERROR: 'Password must meet complexity requirements.'
|
||||||
|
|
||||||
|
};
|
||||||
@ -55,7 +55,7 @@ const AdminDashboard = () => {
|
|||||||
params.append('quarter', quarter || 'All');
|
params.append('quarter', quarter || 'All');
|
||||||
params.append('year', year || 'All');
|
params.append('year', year || 'All');
|
||||||
|
|
||||||
const response = await apiClient.get(`/admin_dashboard?${params.toString()}`);
|
const response = await apiClient.get(`/admin_dashboard?${params.toString()}`, { withCredentials: true });
|
||||||
|
|
||||||
if (!isMounted.current) return;
|
if (!isMounted.current) return;
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -145,7 +145,7 @@ const ManageSubmissions = () => {
|
|||||||
showToast('success', 'Submission approved successfully');
|
showToast('success', 'Submission approved successfully');
|
||||||
// Refresh the submissions list
|
// Refresh the submissions list
|
||||||
const data = await fetchDashboardData(selectedQuarter, selectedYear);
|
const data = await fetchDashboardData(selectedQuarter, selectedYear);
|
||||||
const userProfile = JSON.parse(sessionStorage.getItem("user_profile"));
|
const userProfile = JSON.parse(localStorage.getItem("user_profile"));
|
||||||
const mapped = (data || []).map((item) => ({
|
const mapped = (data || []).map((item) => ({
|
||||||
id: item.id,
|
id: item.id,
|
||||||
establishment: item?.establishment?.factory_name || '-',
|
establishment: item?.establishment?.factory_name || '-',
|
||||||
@ -305,7 +305,8 @@ React.useEffect(() => {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
// First, get the current quarter and year
|
// First, get the current quarter and year
|
||||||
const response = await apiClient.get('/admin_dashboard');
|
// const response = await apiClient.get('/admin_dashboard');
|
||||||
|
const response = await apiClient.get(`/admin_dashboard?${params.toString()}`, { withCredentials: true });
|
||||||
const currentQuarter = response?.data?.selected_quarter || 'Q1';
|
const currentQuarter = response?.data?.selected_quarter || 'Q1';
|
||||||
// const currentQuarter = 'All';
|
// const currentQuarter = 'All';
|
||||||
const currentYear = response?.data?.selected_year || new Date().getFullYear().toString();
|
const currentYear = response?.data?.selected_year || new Date().getFullYear().toString();
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { X } from 'lucide-react';
|
|||||||
import { TextField, SelectField } from '@/components/common/FormControls';
|
import { TextField, SelectField } from '@/components/common/FormControls';
|
||||||
import { createadminusers } from '@/services/configuration/adminService';
|
import { createadminusers } from '@/services/configuration/adminService';
|
||||||
import CustomToast from '@/components/common/CustomToast';
|
import CustomToast from '@/components/common/CustomToast';
|
||||||
|
import { VALIDATION_MESSAGES } from '../../../../constants/validationMessages';
|
||||||
|
|
||||||
const AddAdminUsers = ({ isOpen, onClose, onSave }) => {
|
const AddAdminUsers = ({ isOpen, onClose, onSave }) => {
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
@ -49,7 +50,7 @@ const AddAdminUsers = ({ isOpen, onClose, onSave }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (formData.password !== formData.confirmPassword) {
|
if (formData.password !== formData.confirmPassword) {
|
||||||
newErrors.confirmPassword = 'Passwords do not match';
|
newErrors.confirmPassword = VALIDATION_MESSAGES.PASSWORDS_DONT_MATCH;
|
||||||
}
|
}
|
||||||
|
|
||||||
return newErrors;
|
return newErrors;
|
||||||
@ -71,12 +72,12 @@ const AddAdminUsers = ({ isOpen, onClose, onSave }) => {
|
|||||||
}, [isOpen]);
|
}, [isOpen]);
|
||||||
|
|
||||||
const validatePassword = (password) => {
|
const validatePassword = (password) => {
|
||||||
if (!password) return 'Password is required';
|
if (!password) return VALIDATION_MESSAGES.PASSWORD_REQUIRED;
|
||||||
if (password.length < 8) return 'Password must be at least 8 characters';
|
if (password.length < 8) return VALIDATION_MESSAGES.PASSWORD_MIN_LENGTH;
|
||||||
if (!/[A-Z]/.test(password)) return 'Must contain at least one uppercase letter';
|
if (!/[A-Z]/.test(password)) return VALIDATION_MESSAGES.PASSWORD_UPPERCASE;
|
||||||
if (!/[a-z]/.test(password)) return 'Must contain at least one lowercase letter';
|
if (!/[a-z]/.test(password)) return VALIDATION_MESSAGES.PASSWORD_LOWERCASE;
|
||||||
if (!/\d/.test(password)) return 'Must contain at least one number';
|
if (!/\d/.test(password)) return VALIDATION_MESSAGES.PASSWORD_NUMBER;
|
||||||
if (!/[!@#$%^&*]/.test(password)) return 'Must contain at least one special character (!@#$%^&*)';
|
if (!/[!@#$%^&*]/.test(password)) return VALIDATION_MESSAGES.PASSWORD_SPECIAL_CHAR;
|
||||||
return ''; // Return empty string if password is valid
|
return ''; // Return empty string if password is valid
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -269,7 +270,7 @@ const AddAdminUsers = ({ isOpen, onClose, onSave }) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* ✅ Custom Toast Display */}
|
{/*Custom Toast Display */}
|
||||||
{toastData && (
|
{toastData && (
|
||||||
<div className="fixed inset-0 z-[9999]">
|
<div className="fixed inset-0 z-[9999]">
|
||||||
<CustomToast
|
<CustomToast
|
||||||
|
|||||||
@ -5,7 +5,7 @@ const EditUserModal = ({ formData, setFormData, onClose, onUpdate, showToast })
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
// Check if the deactivated user is the currently logged-in user
|
// Check if the deactivated user is the currently logged-in user
|
||||||
const currentUser = JSON.parse(sessionStorage.getItem('user_profile') || '{}');
|
const currentUser = JSON.parse(localStorage.getItem('user_profile') || '{}');
|
||||||
const currentUserEmail = currentUser?.email?.toLowerCase()?.trim();
|
const currentUserEmail = currentUser?.email?.toLowerCase()?.trim();
|
||||||
const updatedUserEmail = formData.email?.toLowerCase()?.trim();
|
const updatedUserEmail = formData.email?.toLowerCase()?.trim();
|
||||||
const shouldLogout = currentUserEmail === updatedUserEmail && formData.status === "Inactive";
|
const shouldLogout = currentUserEmail === updatedUserEmail && formData.status === "Inactive";
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react';
|
|||||||
import Table from '@/components/common/Table';
|
import Table from '@/components/common/Table';
|
||||||
import AddAdminUsers from './AddAdminUsers';
|
import AddAdminUsers from './AddAdminUsers';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { VALIDATION_MESSAGES } from '../../../../constants/validationMessages';
|
||||||
import {
|
import {
|
||||||
getAdminUser,
|
getAdminUser,
|
||||||
getAdminUserById,
|
getAdminUserById,
|
||||||
@ -9,7 +10,7 @@ import {
|
|||||||
deleteAdminUser,
|
deleteAdminUser,
|
||||||
changeAdminUserPassword,
|
changeAdminUserPassword,
|
||||||
} from '@/services/configuration/adminService';
|
} from '@/services/configuration/adminService';
|
||||||
|
|
||||||
import CustomToast from '@/components/common/CustomToast';
|
import CustomToast from '@/components/common/CustomToast';
|
||||||
import { TextField, SelectField } from '@/components/common/FormControls';
|
import { TextField, SelectField } from '@/components/common/FormControls';
|
||||||
import EditUserModal from './EditUserModal';
|
import EditUserModal from './EditUserModal';
|
||||||
@ -484,21 +485,21 @@ const AdminUsers = () => {
|
|||||||
|
|
||||||
// Old password validation
|
// Old password validation
|
||||||
if (!oldPassword?.trim()) {
|
if (!oldPassword?.trim()) {
|
||||||
newErrors.oldPassword = 'Old password is required';
|
newErrors.oldPassword = VALIDATION_MESSAGES.OLD_PASSWORD_REQUIRED;
|
||||||
}
|
}
|
||||||
|
|
||||||
// New password validation
|
// New password validation
|
||||||
if (!newPassword?.trim()) {
|
if (!newPassword?.trim()) {
|
||||||
newErrors.newPassword = 'New password is required';
|
newErrors.newPassword = VALIDATION_MESSAGES.PASSWORD_REQUIRED;
|
||||||
} else if (newPassword.length < 8) {
|
} else if (newPassword.length < 8) {
|
||||||
newErrors.newPassword = 'Password must be at least 8 characters long';
|
newErrors.newPassword = VALIDATION_MESSAGES.PASSWORD_MIN_LENGTH;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Confirm password validation
|
// Confirm password validation
|
||||||
if (!confirmPassword?.trim()) {
|
if (!confirmPassword?.trim()) {
|
||||||
newErrors.confirmPassword = 'Please confirm your password';
|
newErrors.confirmPassword = VALIDATION_MESSAGES.CONFIRM_PASSWORD_REQUIRED;
|
||||||
} else if (newPassword !== confirmPassword) {
|
} else if (newPassword !== confirmPassword) {
|
||||||
newErrors.confirmPassword = 'Passwords do not match';
|
newErrors.confirmPassword = VALIDATION_MESSAGES.PASSWORDS_DONT_MATCH;
|
||||||
}
|
}
|
||||||
|
|
||||||
return newErrors;
|
return newErrors;
|
||||||
@ -555,7 +556,7 @@ const AdminUsers = () => {
|
|||||||
handleCloseResetModal();
|
handleCloseResetModal();
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ Error resetting admin password:', error);
|
console.error('Error resetting admin password:', error);
|
||||||
|
|
||||||
// Handle specific error cases
|
// Handle specific error cases
|
||||||
let errorMessage = 'Failed to reset password';
|
let errorMessage = 'Failed to reset password';
|
||||||
@ -570,27 +571,28 @@ const AdminUsers = () => {
|
|||||||
errorMessage.toLowerCase().includes('incorrect password')) {
|
errorMessage.toLowerCase().includes('incorrect password')) {
|
||||||
setResetErrors(prev => ({
|
setResetErrors(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
oldPassword: 'The current password is incorrect'
|
oldPassword: VALIDATION_MESSAGES.INCORRECT_CURRENT_PASSWORD
|
||||||
}));
|
}));
|
||||||
showToast('The current password you entered is incorrect', 'error');
|
showToast(VALIDATION_MESSAGES.INCORRECT_CURRENT_PASSWORD, 'error');
|
||||||
} else if (errorMessage.toLowerCase().includes('match')) {
|
} else if (errorMessage.toLowerCase().includes('match')) {
|
||||||
setResetErrors(prev => ({
|
setResetErrors(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
confirmPassword: 'New passwords do not match'
|
confirmPassword: VALIDATION_MESSAGES.PASSWORDS_DONT_MATCH
|
||||||
}));
|
}));
|
||||||
|
showToast(VALIDATION_MESSAGES.PASSWORDS_DONT_MATCH, 'error');
|
||||||
showToast('New passwords do not match', 'error');
|
showToast('New passwords do not match', 'error');
|
||||||
} else if (errorMessage.toLowerCase().includes('same') ||
|
} else if (errorMessage.toLowerCase().includes('same') ||
|
||||||
errorMessage.toLowerCase().includes('previous')) {
|
errorMessage.toLowerCase().includes('previous')) {
|
||||||
setResetErrors(prev => ({
|
setResetErrors(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
newPassword: 'New password cannot be the same as current password'
|
newPassword: VALIDATION_MESSAGES.PASSWORD_SAME_AS_CURRENT
|
||||||
}));
|
}));
|
||||||
showToast('New password cannot be the same as current password', 'error');
|
showToast(VALIDATION_MESSAGES.PASSWORD_SAME_AS_CURRENT, 'error');
|
||||||
} else if (errorMessage.toLowerCase().includes('weak') ||
|
} else if (errorMessage.toLowerCase().includes('weak') ||
|
||||||
errorMessage.toLowerCase().includes('strength')) {
|
errorMessage.toLowerCase().includes('strength')) {
|
||||||
setResetErrors(prev => ({
|
setResetErrors(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
newPassword: 'Password is too weak. Use a stronger password.'
|
newPassword: VALIDATION_MESSAGES.PASSWORD_TOO_WEAK
|
||||||
}));
|
}));
|
||||||
showToast('Password is too weak. Please use a stronger password.', 'error');
|
showToast('Password is too weak. Please use a stronger password.', 'error');
|
||||||
} else if (error.status === 404) {
|
} else if (error.status === 404) {
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { Eye, EyeOff } from 'lucide-react';
|
import { Eye, EyeOff } from 'lucide-react';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
import { VALIDATION_MESSAGES } from '../../../../constants/validationMessages';
|
||||||
|
|
||||||
const ResetPasswordModal = ({
|
const ResetPasswordModal = ({
|
||||||
isOpen,
|
isOpen,
|
||||||
@ -22,12 +23,12 @@ const ResetPasswordModal = ({
|
|||||||
const [confirmPasswordTouched, setConfirmPasswordTouched] = useState(false);
|
const [confirmPasswordTouched, setConfirmPasswordTouched] = useState(false);
|
||||||
|
|
||||||
const validatePassword = (password) => {
|
const validatePassword = (password) => {
|
||||||
if (!password) return 'Password is required';
|
if (!password) return VALIDATION_MESSAGES.PASSWORD_REQUIRED;
|
||||||
if (password.length < 8) return 'Password must be at least 8 characters';
|
if (password.length < 8) return VALIDATION_MESSAGES.PASSWORD_MIN_LENGTH;
|
||||||
if (!/[A-Z]/.test(password)) return 'Must contain at least one uppercase letter';
|
if (!/[A-Z]/.test(password)) return VALIDATION_MESSAGES.PASSWORD_UPPERCASE;
|
||||||
if (!/[a-z]/.test(password)) return 'Must contain at least one lowercase letter';
|
if (!/[a-z]/.test(password)) return VALIDATION_MESSAGES.PASSWORD_LOWERCASE;
|
||||||
if (!/\d/.test(password)) return 'Must contain at least one number';
|
if (!/\d/.test(password)) return VALIDATION_MESSAGES.PASSWORD_NUMBER;
|
||||||
if (!/[!@#$%^&*]/.test(password)) return 'Must contain at least one special character (!@#$%^&*)';
|
if (!/[!@#$%^&*]/.test(password)) return VALIDATION_MESSAGES.PASSWORD_SPECIAL_CHAR;
|
||||||
return '';
|
return '';
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -37,7 +38,7 @@ const ResetPasswordModal = ({
|
|||||||
const newErrors = {};
|
const newErrors = {};
|
||||||
|
|
||||||
if (!oldPassword.trim()) {
|
if (!oldPassword.trim()) {
|
||||||
newErrors.oldPassword = 'Old password is required';
|
newErrors.oldPassword = VALIDATION_MESSAGES.OLD_PASSWORD_REQUIRED;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (passwordTouched && passwordError) {
|
if (passwordTouched && passwordError) {
|
||||||
@ -45,9 +46,9 @@ const ResetPasswordModal = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!confirmPassword.trim()) {
|
if (!confirmPassword.trim()) {
|
||||||
newErrors.confirmPassword = 'Please confirm your password';
|
newErrors.confirmPassword = VALIDATION_MESSAGES.CONFIRM_PASSWORD_REQUIRED;
|
||||||
} else if (newPassword !== confirmPassword) {
|
} else if (newPassword !== confirmPassword) {
|
||||||
newErrors.confirmPassword = 'Passwords do not match';
|
newErrors.confirmPassword = VALIDATION_MESSAGES.PASSWORDS_DONT_MATCH;
|
||||||
}
|
}
|
||||||
|
|
||||||
return newErrors;
|
return newErrors;
|
||||||
|
|||||||
@ -174,7 +174,7 @@ const createEmptyProfile = () => ({
|
|||||||
contactMakaniNumber: '',
|
contactMakaniNumber: '',
|
||||||
contactPersonName: '',
|
contactPersonName: '',
|
||||||
contactPersonDesignation: '',
|
contactPersonDesignation: '',
|
||||||
contactCountryCode: '+971',
|
contactCountryCode: '',
|
||||||
contactMobileNumber: '',
|
contactMobileNumber: '',
|
||||||
contactEmail: '',
|
contactEmail: '',
|
||||||
contactWebsite: '',
|
contactWebsite: '',
|
||||||
@ -191,7 +191,7 @@ const createEmptyProfile = () => ({
|
|||||||
corporateMakaniNumber: '',
|
corporateMakaniNumber: '',
|
||||||
corporateContactPersonName: '',
|
corporateContactPersonName: '',
|
||||||
corporateContactPersonDesignation: '',
|
corporateContactPersonDesignation: '',
|
||||||
corporateCountryCode: '+971',
|
corporateCountryCode: '',
|
||||||
corporateMobileNumber: '',
|
corporateMobileNumber: '',
|
||||||
corporateEmail: '',
|
corporateEmail: '',
|
||||||
corporateWebsite: '',
|
corporateWebsite: '',
|
||||||
@ -606,7 +606,7 @@ const CompanyProfile = () => {
|
|||||||
// Load current user from session storage
|
// Load current user from session storage
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
try {
|
try {
|
||||||
const profile = sessionStorage.getItem('user_profile');
|
const profile = localStorage.getItem('user_profile');
|
||||||
if (profile) {
|
if (profile) {
|
||||||
const userData = JSON.parse(profile);
|
const userData = JSON.parse(profile);
|
||||||
setCurrentUser(userData);
|
setCurrentUser(userData);
|
||||||
@ -807,7 +807,7 @@ const CompanyProfile = () => {
|
|||||||
|
|
||||||
const currentUserId = React.useMemo(() => {
|
const currentUserId = React.useMemo(() => {
|
||||||
try {
|
try {
|
||||||
const profile = sessionStorage.getItem('user_profile');
|
const profile = localStorage.getItem('user_profile');
|
||||||
if (!profile) return null;
|
if (!profile) return null;
|
||||||
const parsed = JSON.parse(profile);
|
const parsed = JSON.parse(profile);
|
||||||
return parsed;
|
return parsed;
|
||||||
@ -817,10 +817,10 @@ const CompanyProfile = () => {
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const profile = sessionStorage.getItem('user_profile');
|
const profile = localStorage.getItem('user_profile');
|
||||||
const userProfile = React.useMemo(() => {
|
const userProfile = React.useMemo(() => {
|
||||||
try {
|
try {
|
||||||
const profile = sessionStorage.getItem('user_profile');
|
const profile = localStorage.getItem('user_profile');
|
||||||
return profile ? JSON.parse(profile) : null;
|
return profile ? JSON.parse(profile) : null;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error parsing user profile:', error);
|
console.error('Error parsing user profile:', error);
|
||||||
@ -1935,7 +1935,7 @@ const displayedRows = sortedProfiles.map((item) => {
|
|||||||
const mapApiEstablishmentToProfile = React.useCallback((item) => {
|
const mapApiEstablishmentToProfile = React.useCallback((item) => {
|
||||||
let currentUser = null;
|
let currentUser = null;
|
||||||
try {
|
try {
|
||||||
const profile = sessionStorage.getItem('user_profile');
|
const profile = localStorage.getItem('user_profile');
|
||||||
if (profile) {
|
if (profile) {
|
||||||
currentUser = JSON.parse(profile);
|
currentUser = JSON.parse(profile);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -657,7 +657,7 @@ const EditCompanyProfile = () => {
|
|||||||
required
|
required
|
||||||
error={fieldErrors.userProfileEmail}
|
error={fieldErrors.userProfileEmail}
|
||||||
/>
|
/>
|
||||||
<div className="space-y-1">
|
{/* <div className="space-y-1">
|
||||||
<TextField
|
<TextField
|
||||||
label="Password"
|
label="Password"
|
||||||
value={form.userProfilePassword || ""}
|
value={form.userProfilePassword || ""}
|
||||||
@ -670,7 +670,7 @@ const EditCompanyProfile = () => {
|
|||||||
/>
|
/>
|
||||||
{passwordError && <p className="text-xs text-[#B91C1C]">{passwordError}</p>}
|
{passwordError && <p className="text-xs text-[#B91C1C]">{passwordError}</p>}
|
||||||
{passwordReuseError && <p className="text-xs text-[#B91C1C]">{passwordReuseError}</p>}
|
{passwordReuseError && <p className="text-xs text-[#B91C1C]">{passwordReuseError}</p>}
|
||||||
</div>
|
</div> */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -405,7 +405,7 @@ const IsicHsCodes = () => {
|
|||||||
// Get user profile from session
|
// Get user profile from session
|
||||||
const userProfile = React.useMemo(() => {
|
const userProfile = React.useMemo(() => {
|
||||||
try {
|
try {
|
||||||
const profile = sessionStorage.getItem('user_profile');
|
const profile = localStorage.getItem('user_profile');
|
||||||
return profile ? JSON.parse(profile) : null;
|
return profile ? JSON.parse(profile) : null;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error parsing user profile:', error);
|
console.error('Error parsing user profile:', error);
|
||||||
@ -464,7 +464,7 @@ const IsicHsCodes = () => {
|
|||||||
const createdById = product.created_by;
|
const createdById = product.created_by;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const userProfileStr = sessionStorage.getItem('user_profile');
|
const userProfileStr = localStorage.getItem('user_profile');
|
||||||
if (userProfileStr) {
|
if (userProfileStr) {
|
||||||
const userProfile = JSON.parse(userProfileStr);
|
const userProfile = JSON.parse(userProfileStr);
|
||||||
if (createdById && createdById === userProfile.id) {
|
if (createdById && createdById === userProfile.id) {
|
||||||
@ -643,7 +643,7 @@ const IsicHsCodes = () => {
|
|||||||
let displayName = item.createdBy;
|
let displayName = item.createdBy;
|
||||||
if (displayName === '-' && item.createdById) {
|
if (displayName === '-' && item.createdById) {
|
||||||
try {
|
try {
|
||||||
const userProfileStr = sessionStorage.getItem('user_profile');
|
const userProfileStr = localStorage.getItem('user_profile');
|
||||||
if (userProfileStr) {
|
if (userProfileStr) {
|
||||||
const userProfile = JSON.parse(userProfileStr);
|
const userProfile = JSON.parse(userProfileStr);
|
||||||
if (item.createdById === userProfile.id) {
|
if (item.createdById === userProfile.id) {
|
||||||
@ -962,7 +962,7 @@ const IsicHsCodes = () => {
|
|||||||
// Get user profile from session storage for created_by
|
// Get user profile from session storage for created_by
|
||||||
let userProfile = null;
|
let userProfile = null;
|
||||||
try {
|
try {
|
||||||
const userProfileStr = sessionStorage.getItem('user_profile');
|
const userProfileStr = localStorage.getItem('user_profile');
|
||||||
if (userProfileStr) {
|
if (userProfileStr) {
|
||||||
userProfile = JSON.parse(userProfileStr);
|
userProfile = JSON.parse(userProfileStr);
|
||||||
}
|
}
|
||||||
@ -1129,7 +1129,7 @@ const IsicHsCodes = () => {
|
|||||||
// Get user profile from session storage for created_by
|
// Get user profile from session storage for created_by
|
||||||
let createdByName = '-';
|
let createdByName = '-';
|
||||||
try {
|
try {
|
||||||
const userProfileStr = sessionStorage.getItem('user_profile');
|
const userProfileStr = localStorage.getItem('user_profile');
|
||||||
if (userProfileStr) {
|
if (userProfileStr) {
|
||||||
const userProfile = JSON.parse(userProfileStr);
|
const userProfile = JSON.parse(userProfileStr);
|
||||||
createdByName = userProfile.name || createdByName;
|
createdByName = userProfile.name || createdByName;
|
||||||
|
|||||||
@ -196,7 +196,7 @@ const UnitMaster = () => {
|
|||||||
}, [sortedUnits, searchTerm]);
|
}, [sortedUnits, searchTerm]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const userProfile = sessionStorage.getItem('user_profile');
|
const userProfile = localStorage.getItem('user_profile');
|
||||||
if (userProfile) {
|
if (userProfile) {
|
||||||
try {
|
try {
|
||||||
setCurrentUser(JSON.parse(userProfile));
|
setCurrentUser(JSON.parse(userProfile));
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useNavigate, useLocation } from 'react-router-dom';
|
import { useNavigate, useLocation } from 'react-router-dom';
|
||||||
import apiClient from '@/services/api/apiClient';
|
import apiClient from '@/services/api/apiClient';
|
||||||
|
import { VALIDATION_MESSAGES } from '@/constants/validationMessages';
|
||||||
|
import { ASSETS } from '@/constants/assets';
|
||||||
|
|
||||||
const logoSrc = '/assets/images/FCSCLogo.svg';
|
// const logoSrc = ASSETS.IMAGES.LOGO;
|
||||||
|
|
||||||
const ToggleableInput = ({
|
const ToggleableInput = ({
|
||||||
label,
|
label,
|
||||||
@ -81,15 +83,15 @@ const ChangePassword = () => {
|
|||||||
|
|
||||||
const validatePassword = (password) => {
|
const validatePassword = (password) => {
|
||||||
const rules = [
|
const rules = [
|
||||||
{ regex: /.{8,}/, msg: "At least 8 characters" },
|
{ regex: /.{8,}/, msg: VALIDATION_MESSAGES.PASSWORD_MIN_LENGTH },
|
||||||
{ regex: /[A-Z]/, msg: "At least one uppercase letter" },
|
{ regex: /[A-Z]/, msg: VALIDATION_MESSAGES.PASSWORD_UPPERCASE },
|
||||||
{ regex: /[a-z]/, msg: "At least one lowercase letter" },
|
{ regex: /[a-z]/, msg: VALIDATION_MESSAGES.PASSWORD_LOWERCASE },
|
||||||
{ regex: /[0-9]/, msg: "At least one number" },
|
{ regex: /[0-9]/, msg: VALIDATION_MESSAGES.PASSWORD_NUMBER },
|
||||||
{ regex: /[!@#$%^&*(),.?\":{}|<>]/, msg: "At least one special character" },
|
{ regex: /[!@#$%^&*(),.?\":{}|<>]/, msg: VALIDATION_MESSAGES.PASSWORD_SPECIAL_CHAR },
|
||||||
];
|
];
|
||||||
|
|
||||||
const failedRule = rules.find(r => !r.regex.test(password));
|
const failedRule = rules.find(r => !r.regex.test(password));
|
||||||
return failedRule ? `Password must meet complexity requirements.` : "";
|
return failedRule ? VALIDATION_MESSAGES.PASSWORD_COMPLEXITY_ERROR : "";
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = async (e) => {
|
const handleSubmit = async (e) => {
|
||||||
@ -105,11 +107,11 @@ const handleSubmit = async (e) => {
|
|||||||
let hasError = false;
|
let hasError = false;
|
||||||
|
|
||||||
if (!otp.trim()) {
|
if (!otp.trim()) {
|
||||||
setOtpError("Verification code is required.");
|
setOtpError("Verification code is required");
|
||||||
hasError = true;
|
hasError = true;
|
||||||
}
|
}
|
||||||
if (!newPassword.trim()) {
|
if (!newPassword.trim()) {
|
||||||
setNewPasswordError("New password is required.");
|
setNewPasswordError(VALIDATION_MESSAGES.PASSWORD_REQUIRED);
|
||||||
hasError = true;
|
hasError = true;
|
||||||
}
|
}
|
||||||
if (!confirmPassword.trim()) {
|
if (!confirmPassword.trim()) {
|
||||||
@ -126,7 +128,7 @@ if (passwordError) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (newPassword !== confirmPassword) {
|
if (newPassword !== confirmPassword) {
|
||||||
setConfirmPasswordError("Passwords do not match.");
|
setConfirmPasswordError(VALIDATION_MESSAGES.PASSWORDS_DONT_MATCH);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,11 +170,11 @@ if (newPassword !== confirmPassword) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-[#F7F7F7] flex items-center justify-center px-4">
|
<div className={ASSETS.CLASSES.AUTH_CONTAINER}>
|
||||||
<div className="w-full max-w-md">
|
<div className="w-full max-w-md">
|
||||||
<div className="bg-white rounded-lg shadow-sm ring-1 ring-gray-200 overflow-hidden">
|
<div className="bg-white rounded-lg shadow-sm ring-1 ring-gray-200 overflow-hidden">
|
||||||
<div className="px-7 py-8 border-b border-gray-200 bg-[#F2ECCF] text-center">
|
<div className="px-7 py-8 border-b border-gray-200 bg-[#F2ECCF] text-center">
|
||||||
<img src={logoSrc} alt="FCSC" className="w-[188px] h-[58px] mx-auto mb-2" />
|
<img src={ASSETS.IMAGES.LOGO} alt="FCSC" className="w-[188px] h-[58px] mx-auto mb-2" />
|
||||||
<h1 className="text-base font-semibold text-[#92722A]">Reset Password</h1>
|
<h1 className="text-base font-semibold text-[#92722A]">Reset Password</h1>
|
||||||
<p className="mt-1 text-sm text-gray-600">
|
<p className="mt-1 text-sm text-gray-600">
|
||||||
Enter the verification code sent to your registered email and set a new password.
|
Enter the verification code sent to your registered email and set a new password.
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { requestPasswordReset } from '@/services/auth/authService.js';
|
import { requestPasswordReset } from '@/services/auth/authService.js';
|
||||||
|
import { ASSETS } from '@/constants/assets';
|
||||||
const logoSrc = '/assets/images/FCSCLogo.svg';
|
const CONTAINER_CLASS = "min-h-screen bg-[#F7F7F7] flex items-center justify-center px-4";
|
||||||
|
|
||||||
const ForgotPassword = () => {
|
const ForgotPassword = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@ -38,11 +38,11 @@ const ForgotPassword = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-[#F7F7F7] flex items-center justify-center px-4">
|
<div className={CONTAINER_CLASS}>
|
||||||
<div className="w-full max-w-md">
|
<div className="w-full max-w-md">
|
||||||
<div className="bg-white rounded-lg shadow-sm ring-1 ring-gray-200 overflow-hidden">
|
<div className="bg-white rounded-lg shadow-sm ring-1 ring-gray-200 overflow-hidden">
|
||||||
<div className="px-7 py-8 border-b border-gray-200 bg-[#F2ECCF] text-center">
|
<div className="px-7 py-8 border-b border-gray-200 bg-[#F2ECCF] text-center">
|
||||||
<img src={logoSrc} alt="FCSC" className="w-[188px] h-[58px] mx-auto mb-3" />
|
<img src={ASSETS.IMAGES.LOGO} alt="FCSC" className="w-[188px] h-[58px] mx-auto mb-3" />
|
||||||
<h1 className="text-base font-semibold text-[#92722A]">Reset Password</h1>
|
<h1 className="text-base font-semibold text-[#92722A]">Reset Password</h1>
|
||||||
<p className="mt-1 text-sm text-gray-600">Enter your email and we will send you instructions to reset your password.</p>
|
<p className="mt-1 text-sm text-gray-600">Enter your email and we will send you instructions to reset your password.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -152,138 +152,111 @@ const Login = () => {
|
|||||||
return valid;
|
return valid;
|
||||||
};
|
};
|
||||||
|
|
||||||
const submit = async (e) => {
|
const submit = async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (isLocked) {
|
if (isLocked) {
|
||||||
const minutes = Math.floor(remainingTime / 60);
|
const minutes = Math.floor(remainingTime / 60);
|
||||||
const seconds = remainingTime % 60;
|
const seconds = remainingTime % 60;
|
||||||
showToast('error', `Account locked. Please wait ${minutes}m ${seconds}s or contact support.`);
|
showToast('error', `Account locked. Please wait ${minutes}m ${seconds}s or contact support.`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!validateForm()) return;
|
if (!validateForm()) return;
|
||||||
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await login({ email, password });
|
const response = await login({ email, password });
|
||||||
|
console.log("Login response:", response);
|
||||||
if (response?.status !== 'success' || !response?.data) {
|
|
||||||
throw new Error('Invalid credentials');
|
// Check if login was successful
|
||||||
}
|
if (response?.status === 'success' && response?.data) {
|
||||||
|
const userData = response.data;
|
||||||
const token = response.data;
|
console.log("User data:", userData);
|
||||||
sessionStorage.setItem('auth_token', token);
|
|
||||||
|
// Clear any existing failed attempts
|
||||||
let payload;
|
const storedAttempts = JSON.parse(localStorage.getItem("login_attempts") || "{}");
|
||||||
try {
|
|
||||||
const [, base64Payload] = token.split('.');
|
|
||||||
payload = JSON.parse(atob(base64Payload));
|
|
||||||
} catch {
|
|
||||||
throw new Error('Invalid token format.');
|
|
||||||
}
|
|
||||||
|
|
||||||
const role = payload?.role ?? (payload?.is_admin ? 'Admin' : undefined);
|
|
||||||
if (role) sessionStorage.setItem('user_role', role);
|
|
||||||
|
|
||||||
const profile = {
|
|
||||||
id: payload?.id || payload?.user_id || '',
|
|
||||||
name: payload?.name || payload?.username || '',
|
|
||||||
email: payload?.email || '',
|
|
||||||
};
|
|
||||||
sessionStorage.setItem('user_profile', JSON.stringify(profile));
|
|
||||||
|
|
||||||
const establishmentId = payload?.establishment_id ?? payload?.establishmentId;
|
|
||||||
const isAdmin =
|
|
||||||
String(role || '').toLowerCase() === 'admin' || payload?.is_admin === true;
|
|
||||||
const successMessage = response?.message || 'Login successful.';
|
|
||||||
|
|
||||||
// Clear login attempts for this email
|
|
||||||
const storedAttempts = JSON.parse(localStorage.getItem('login_attempts') || '{}');
|
|
||||||
if (storedAttempts[email]) {
|
if (storedAttempts[email]) {
|
||||||
delete storedAttempts[email];
|
delete storedAttempts[email];
|
||||||
localStorage.setItem('login_attempts', JSON.stringify(storedAttempts));
|
localStorage.setItem("login_attempts", JSON.stringify(storedAttempts));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Store user data in localStorage
|
||||||
|
localStorage.setItem('user_role', userData.role);
|
||||||
|
|
||||||
|
// Store establishment_id if user is an EstablishmentUser
|
||||||
|
if (userData.role === 'EstablishmentUser' && userData.establishment_id) {
|
||||||
|
localStorage.setItem('establishment_id', userData.establishment_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store user profile
|
||||||
|
const userProfile = {
|
||||||
|
id: userData.id,
|
||||||
|
name: userData.name,
|
||||||
|
email: userData.email
|
||||||
|
};
|
||||||
|
localStorage.setItem('user_profile', JSON.stringify(userProfile));
|
||||||
|
|
||||||
|
// Reset login attempt counters
|
||||||
setFailedAttempts(0);
|
setFailedAttempts(0);
|
||||||
setIsLocked(false);
|
setIsLocked(false);
|
||||||
setRemainingTime(0);
|
setRemainingTime(0);
|
||||||
|
|
||||||
if (countdownIntervalRef.current) {
|
if (countdownIntervalRef.current) {
|
||||||
clearInterval(countdownIntervalRef.current);
|
clearInterval(countdownIntervalRef.current);
|
||||||
countdownIntervalRef.current = null;
|
countdownIntervalRef.current = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isAdmin) {
|
// Show success message
|
||||||
sessionStorage.removeItem('establishment_id');
|
showToast('success', response.message || 'Login successful');
|
||||||
showToast('success', successMessage);
|
|
||||||
scheduleNavigate('/admin/dashboard');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rememberMe) {
|
|
||||||
sessionStorage.setItem('remember_me', 'true');
|
|
||||||
sessionStorage.setItem('remembered_email', email);
|
|
||||||
sessionStorage.setItem('remembered_password', password);
|
|
||||||
} else {
|
|
||||||
sessionStorage.removeItem('remember_me');
|
|
||||||
sessionStorage.removeItem('remembered_email');
|
|
||||||
sessionStorage.removeItem('remembered_password');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (establishmentId) {
|
|
||||||
sessionStorage.setItem('establishment_id', String(establishmentId));
|
|
||||||
showToast('success', successMessage);
|
|
||||||
scheduleNavigate('/dashboard');
|
|
||||||
} else {
|
|
||||||
showToast('error', 'Email or password is incorrect.');
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Login failed', err);
|
|
||||||
|
|
||||||
// Get the error message from the API response
|
// Redirect based on role
|
||||||
const errorMessage = err.response?.data?.message ||
|
if (userData.role === 'Admin') {
|
||||||
err.message ||
|
navigate('/admin/dashboard', { replace: true });
|
||||||
'An error occurred during login. Please try again.';
|
|
||||||
|
|
||||||
// Show the exact error message for inactive users
|
|
||||||
if (errorMessage.toLowerCase().includes('inactive')) {
|
|
||||||
showToast('error', errorMessage);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// For other errors, show the generic message and update login attempts
|
|
||||||
const storedAttempts = JSON.parse(localStorage.getItem('login_attempts') || '{}');
|
|
||||||
const attempts = (storedAttempts[email]?.attempts || 0) + 1;
|
|
||||||
|
|
||||||
// Update attempts for this email
|
|
||||||
const updatedAttempts = {
|
|
||||||
...storedAttempts,
|
|
||||||
[email]: {
|
|
||||||
attempts,
|
|
||||||
lockUntil: attempts >= MAX_FAILED_ATTEMPTS
|
|
||||||
? Date.now() + LOCK_DURATION_MINUTES * 60 * 1000
|
|
||||||
: null
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
localStorage.setItem('login_attempts', JSON.stringify(updatedAttempts));
|
|
||||||
setFailedAttempts(attempts);
|
|
||||||
|
|
||||||
if (attempts >= MAX_FAILED_ATTEMPTS) {
|
|
||||||
const lockUntil = Date.now() + LOCK_DURATION_MINUTES * 60 * 1000;
|
|
||||||
setIsLocked(true);
|
|
||||||
setLockExpiry(lockUntil);
|
|
||||||
startCountdown(lockUntil);
|
|
||||||
showToast('error', `Too many failed attempts. Account locked for ${LOCK_DURATION_MINUTES} minutes.`);
|
|
||||||
} else {
|
} else {
|
||||||
const remainingAttempts = MAX_FAILED_ATTEMPTS - attempts;
|
navigate('/dashboard');
|
||||||
showToast('error', `Email or password is incorrect. ${remainingAttempts} attempt${remainingAttempts !== 1 ? 's' : ''} remaining.`);
|
|
||||||
}
|
}
|
||||||
} finally {
|
} else {
|
||||||
setLoading(false);
|
throw new Error(response?.message || 'Invalid credentials');
|
||||||
}
|
}
|
||||||
};
|
} catch (err) {
|
||||||
|
console.error('Login failed', err);
|
||||||
|
const errorMessage = err.response?.data?.message ||
|
||||||
|
err.message ||
|
||||||
|
'An error occurred during login. Please try again.';
|
||||||
|
|
||||||
|
// Handle failed login attempts
|
||||||
|
const storedAttempts = JSON.parse(localStorage.getItem("login_attempts") || "{}");
|
||||||
|
const attempts = (storedAttempts[email]?.attempts || 0) + 1;
|
||||||
|
|
||||||
|
const updatedAttempts = {
|
||||||
|
...storedAttempts,
|
||||||
|
[email]: {
|
||||||
|
attempts,
|
||||||
|
lockUntil: attempts >= MAX_FAILED_ATTEMPTS
|
||||||
|
? Date.now() + LOCK_DURATION_MINUTES * 60 * 1000
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
localStorage.setItem("login_attempts", JSON.stringify(updatedAttempts));
|
||||||
|
setFailedAttempts(attempts);
|
||||||
|
|
||||||
|
if (attempts >= MAX_FAILED_ATTEMPTS) {
|
||||||
|
const lockUntil = Date.now() + LOCK_DURATION_MINUTES * 60 * 1000;
|
||||||
|
setIsLocked(true);
|
||||||
|
setLockExpiry(lockUntil);
|
||||||
|
startCountdown(lockUntil);
|
||||||
|
showToast('error', `Too many failed attempts. Account locked for ${LOCK_DURATION_MINUTES} minutes.`);
|
||||||
|
} else {
|
||||||
|
const remainingAttempts = MAX_FAILED_ATTEMPTS - attempts;
|
||||||
|
showToast('error', `Email or password is incorrect. ${remainingAttempts} attempt${remainingAttempts !== 1 ? 's' : ''} remaining.`);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const formatTime = (seconds) => {
|
const formatTime = (seconds) => {
|
||||||
const mins = Math.floor(seconds / 60);
|
const mins = Math.floor(seconds / 60);
|
||||||
|
|||||||
@ -32,7 +32,7 @@ const Overview = () => {
|
|||||||
const [submissions, setSubmissions] = useState([]);
|
const [submissions, setSubmissions] = useState([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
const establishmentId = sessionStorage.getItem('establishment_id');
|
const establishmentId = localStorage.getItem('establishment_id');
|
||||||
const [pagination, setPagination] = useState({
|
const [pagination, setPagination] = useState({
|
||||||
currentPage: 1,
|
currentPage: 1,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
|
|||||||
@ -6,6 +6,7 @@ const fallbackToken = import.meta.env.VITE_API_TOKEN;
|
|||||||
|
|
||||||
const apiClient = axios.create({
|
const apiClient = axios.create({
|
||||||
baseURL,
|
baseURL,
|
||||||
|
withCredentials: true,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
Accept: 'application/json',
|
Accept: 'application/json',
|
||||||
@ -19,20 +20,20 @@ apiClient.interceptors.request.use((config) => {
|
|||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!config.headers.Authorization) {
|
// if (!config.headers.Authorization) {
|
||||||
let token;
|
// let token;
|
||||||
try {
|
// try {
|
||||||
token = sessionStorage.getItem('auth_token');
|
// token = sessionStorage.getItem('auth_token');
|
||||||
} catch (error) {
|
// } catch (error) {
|
||||||
token = undefined;
|
// token = undefined;
|
||||||
}
|
// }
|
||||||
if (!token && fallbackToken) {
|
// if (!token && fallbackToken) {
|
||||||
token = fallbackToken;
|
// token = fallbackToken;
|
||||||
}
|
// }
|
||||||
if (token) {
|
// if (token) {
|
||||||
config.headers.Authorization = `Bearer ${token}`;
|
// config.headers.Authorization = `Bearer ${token}`;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
return config;
|
return config;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import apiClient from '@/services/api/apiClient';
|
import apiClient from '@/services/api/apiClient';
|
||||||
|
|
||||||
export const login = async ({ email, password }) => {
|
export const login = async ({ email, password }) => {
|
||||||
const response = await apiClient.post('/auth/login', { email, password }, { skipAuth: true });
|
const response = await apiClient.post('/auth/login', { email, password }, { skipAuth: true , withCredentials: true});
|
||||||
return response.data;
|
return response.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,8 @@ const admin = '/admin_users';
|
|||||||
|
|
||||||
export const getAdminUser = async () => {
|
export const getAdminUser = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await getRequest(admin);
|
// const response = await getRequest(admin);
|
||||||
|
const response = await getRequest(admin, { withCredentials: true });
|
||||||
return response.data || [];
|
return response.data || [];
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching units:', error);
|
console.error('Error fetching units:', error);
|
||||||
@ -44,13 +45,6 @@ export const getAdminUserById = async (id) => {
|
|||||||
export const changeAdminUserPassword = async (id, passwordData) => {
|
export const changeAdminUserPassword = async (id, passwordData) => {
|
||||||
try {
|
try {
|
||||||
if (!id) throw new Error("Invalid user ID");
|
if (!id) throw new Error("Invalid user ID");
|
||||||
|
|
||||||
console.log('Debug: Password data received:', {
|
|
||||||
hasOldPassword: !!passwordData.old_password,
|
|
||||||
hasNewPassword: !!passwordData.new_password,
|
|
||||||
hasConfirmPassword: !!passwordData.confirm_password
|
|
||||||
});
|
|
||||||
|
|
||||||
// Prepare the request data according to API specification
|
// Prepare the request data according to API specification
|
||||||
const requestData = {
|
const requestData = {
|
||||||
old_password: passwordData.old_password,
|
old_password: passwordData.old_password,
|
||||||
@ -69,7 +63,7 @@ export const changeAdminUserPassword = async (id, passwordData) => {
|
|||||||
console.error("Error changing admin user password:", error);
|
console.error("Error changing admin user password:", error);
|
||||||
|
|
||||||
if (error.response) {
|
if (error.response) {
|
||||||
console.error("🔍 Debug: API error response:", {
|
console.error("Debug: API error response:", {
|
||||||
status: error.response.status,
|
status: error.response.status,
|
||||||
data: error.response.data,
|
data: error.response.data,
|
||||||
headers: error.response.headers
|
headers: error.response.headers
|
||||||
|
|||||||
@ -161,6 +161,26 @@ export const getEstablishmentProducts = async (establishmentId, config = {}) =>
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getProductSubmissionHistory = async (establishmentId, productId, config = {}) => {
|
||||||
|
try {
|
||||||
|
if (!establishmentId || !productId) {
|
||||||
|
throw new Error('Establishment ID and Product ID are required to fetch product submission history');
|
||||||
|
}
|
||||||
|
const response = await getRequest('/submissions/getProductSubmissionHistory', {
|
||||||
|
...config,
|
||||||
|
params: {
|
||||||
|
establishment_id: establishmentId,
|
||||||
|
product_id: productId,
|
||||||
|
...(config.params || {})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching product submission history:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
getSubmissions,
|
getSubmissions,
|
||||||
submitSurvey,
|
submitSurvey,
|
||||||
@ -171,5 +191,6 @@ export default {
|
|||||||
getPreviousForecastData,
|
getPreviousForecastData,
|
||||||
getSubmissionHistoryByEstablishment,
|
getSubmissionHistoryByEstablishment,
|
||||||
getSubmissionAuditHistory,
|
getSubmissionAuditHistory,
|
||||||
getEstablishmentProducts
|
getEstablishmentProducts,
|
||||||
|
getProductSubmissionHistory
|
||||||
};
|
};
|
||||||
@ -15,7 +15,7 @@ export const resolveEstablishmentId = (inputId) => {
|
|||||||
}
|
}
|
||||||
let storedId;
|
let storedId;
|
||||||
try {
|
try {
|
||||||
storedId = sessionStorage.getItem('establishment_id');
|
storedId = localStorage.getItem('establishment_id');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
storedId = undefined;
|
storedId = undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user