This commit is contained in:
Malini 2025-12-05 13:09:46 +05:30
parent 30057d56c0
commit 396567cada
3 changed files with 255 additions and 255 deletions

View File

@ -1,5 +1,6 @@
import React, { useRef, useEffect } from 'react'; import React, { useRef, useEffect } from 'react';
import { NavLink, useNavigate } from 'react-router-dom'; import { NavLink, useNavigate } from 'react-router-dom';
import { logout as logoutApi } from '@/services/auth/authService';
const logoSrc = '/assets/images/FCSCLogo.svg'; const logoSrc = '/assets/images/FCSCLogo.svg';
const dashboardIconActiveSrc = '/assets/images/cuida_dashboard-outline.svg'; const dashboardIconActiveSrc = '/assets/images/cuida_dashboard-outline.svg';
@ -18,15 +19,25 @@ const NavItem = ({ label, to, iconActive, iconInactive, end = false }) => (
end={end} end={end}
className={({ isActive }) => className={({ isActive }) =>
`inline-flex items-center gap-2 pb-1 border-b-2 text-[14px] leading-5 ` + `inline-flex items-center gap-2 pb-1 border-b-2 text-[14px] leading-5 ` +
(isActive ? 'text-[#92722A] font-medium border-[#92722A]' : 'text-[#232528] border-transparent hover:text-gray-900') (isActive
? 'text-[#92722A] font-medium border-[#92722A]'
: 'text-[#232528] border-transparent hover:text-gray-900')
} }
> >
{({ isActive }) => ( {({ isActive }) => (
<> <>
{iconActive && iconInactive ? ( {iconActive && iconInactive ? (
<img src={isActive ? iconActive : iconInactive} alt={label} className="h-[18px] w-[18px]" /> <img
src={isActive ? iconActive : iconInactive}
alt={label}
className="h-[18px] w-[18px]"
/>
) : ( ) : (
<span className={`inline-block h-[18px] w-[18px] rounded ${isActive ? 'bg-[#92722A]' : 'bg-[#C9CDD1]'}`} /> <span
className={`inline-block h-[18px] w-[18px] rounded ${
isActive ? 'bg-[#92722A]' : 'bg-[#C9CDD1]'
}`}
/>
)} )}
<span>{label}</span> <span>{label}</span>
</> </>
@ -45,26 +56,31 @@ const AdminHeader = () => {
const profileButtonRef = useRef(null); const profileButtonRef = useRef(null);
const mobileMenuButtonRef = useRef(null); const mobileMenuButtonRef = useRef(null);
// Handle click outside for profile dropdown and mobile menu
useEffect(() => { useEffect(() => {
const handleClickOutside = (event) => { const handleClickOutside = (event) => {
// Close profile dropdown if clicked outside if (
if (profileOpen && profileRef.current && !profileRef.current.contains(event.target) && profileOpen &&
profileButtonRef.current && !profileButtonRef.current.contains(event.target)) { profileRef.current &&
!profileRef.current.contains(event.target) &&
profileButtonRef.current &&
!profileButtonRef.current.contains(event.target)
) {
setProfileOpen(false); setProfileOpen(false);
} }
// Close mobile menu if clicked outside if (
if (open && mobileMenuRef.current && !mobileMenuRef.current.contains(event.target) && open &&
mobileMenuButtonRef.current && !mobileMenuButtonRef.current.contains(event.target)) { mobileMenuRef.current &&
!mobileMenuRef.current.contains(event.target) &&
mobileMenuButtonRef.current &&
!mobileMenuButtonRef.current.contains(event.target)
) {
setOpen(false); setOpen(false);
} }
}; };
// Add event listener when the component mounts
document.addEventListener('mousedown', handleClickOutside); document.addEventListener('mousedown', handleClickOutside);
// Clean up the event listener when the component unmounts
return () => { return () => {
document.removeEventListener('mousedown', handleClickOutside); document.removeEventListener('mousedown', handleClickOutside);
}; };
@ -82,22 +98,26 @@ const AdminHeader = () => {
setUserEmail(parsed.email); setUserEmail(parsed.email);
} }
} }
} catch { } catch {}
// ignore corrupt storage
}
}, []); }, []);
const handleLogout = () => { // 🚀 UPDATED: LOGOUT VIA API
const handleLogout = async () => {
try { try {
sessionStorage.removeItem('auth_token'); await logoutApi(); // <-- API CALL
sessionStorage.removeItem('user_profile');
sessionStorage.removeItem('user_role'); sessionStorage.clear();
sessionStorage.removeItem('establishment_id'); localStorage.removeItem('auth_token');
} catch { localStorage.removeItem('user_profile');
// ignore storage errors localStorage.removeItem('user_role');
localStorage.removeItem('establishment_id');
setProfileOpen(false);
navigate('/index');
} catch (error) {
console.error('Logout failed:', error);
navigate('/index');
} }
setProfileOpen(false);
navigate('/index');
}; };
return ( return (
@ -105,52 +125,51 @@ const AdminHeader = () => {
<div className="max-w-[1280px] mx-auto px-4"> <div className="max-w-[1280px] mx-auto px-4">
<div className="flex items-center h-20 gap-6 "> <div className="flex items-center h-20 gap-6 ">
<img src={logoSrc} alt="FCSC" className="w-[140px] h-[44px] " /> <img src={logoSrc} alt="FCSC" className="w-[140px] h-[44px] " />
<nav className="flex-1 flex justify-end items-center gap-6 xl:gap-8 text-[14px] leading-5 ml-auto "> <nav className="flex-1 flex justify-end items-center gap-6 xl:gap-8 text-[14px] leading-5 ml-auto ">
<NavItem to="/admin/dashboard" end label="Dashboard" iconActive={dashboardIconActiveSrc} iconInactive={dashboardIconInactiveSrc} /> <NavItem
<NavItem to="/admin/validations" label="Submissions" iconActive={checkCircleActiveSrc} iconInactive={manageSubmissionInactiveSrc} /> to="/admin/dashboard"
<NavItem to="/admin/configuration" label="Configuration" iconActive={configActiveSrc} iconInactive={configInactiveSrc} /> end
{/* <NavItem to="admin/configuration/admin-users"label="Users" iconActive={userActiveSrc} iconInactive={userInactiveSrc} /> */} label="Dashboard"
iconActive={dashboardIconActiveSrc}
iconInactive={dashboardIconInactiveSrc}
/>
<NavItem
to="/admin/validations"
label="Submissions"
iconActive={checkCircleActiveSrc}
iconInactive={manageSubmissionInactiveSrc}
/>
<NavItem
to="/admin/configuration"
label="Configuration"
iconActive={configActiveSrc}
iconInactive={configInactiveSrc}
/>
</nav> </nav>
<div className="hidden lg:flex items-center gap-3 ml-auto"> <div className="hidden lg:flex items-center gap-3 ml-auto">
{/* <button
type="button"
aria-label="Notifications"
className="inline-flex h-9 w-9 items-center justify-center rounded-full border border-transparent hover:border-[#E5E7EB]"
>
<img src={bellIconSrc} alt="Notifications" className="h-[18px] w-[18px]" />
</button> */}
<div className="relative" ref={profileRef}> <div className="relative" ref={profileRef}>
<button <button
ref={profileButtonRef} ref={profileButtonRef}
type="button" type="button"
onClick={() => setProfileOpen((value) => !value)} onClick={() => setProfileOpen((value) => !value)}
className="h-9 w-9 rounded-full bg-[#F2F2F2] grid place-items-center text-[13px] font-medium text-[#232528]" className="h-9 w-9 rounded-full bg-[#F2F2F2] grid place-items-center text-[13px] font-medium text-[#232528]"
aria-haspopup="menu"
aria-expanded={profileOpen}
> >
{userName?.slice(0, 1)?.toUpperCase() || 'U'} {userName?.slice(0, 1)?.toUpperCase() || 'U'}
</button> </button>
{profileOpen && ( {profileOpen && (
<div className="absolute right-0 mt-3 w-64 rounded-[12px] border border-[#E5E7EB] bg-white shadow-[0_20px_30px_rgba(15,23,42,0.12)] z-50"> <div className="absolute right-0 mt-3 w-64 rounded-[12px] border border-[#E5E7EB] bg-white shadow-[0_20px_30px_rgba(15,23,42,0.12)] z-50">
<div className="px-4 py-3 border-b border-[#F1F5F9] text-left space-y-1"> <div className="px-4 py-3 border-b border-[#F1F5F9] text-left space-y-1">
<p <p className="text-sm font-semibold text-[#232528] truncate">
className="text-sm font-semibold text-[#232528] truncate" {userName?.replace(/([a-z])([A-Z])/g, '$1 $2')}
title={userName} </p>
> <p className="text-xs text-[#6B7280] truncate">
{userName?.replace(/([a-z])([A-Z])/g, '$1 $2')} {userEmail}
</p> </p>
<p className="text-xs text-[#6B7280] truncate" title={userEmail}>{userEmail}</p>
</div> </div>
{/* <button
type="button"
className="w-full px-4 py-2.5 text-center text-sm text-[#232528] hover:bg-[#F2ECCF]"
onClick={() => {
setProfileOpen(false);
navigate('/admin/profile');
}}
>
Profile
</button> */}
<button <button
type="button" type="button"
className="w-full px-4 py-2.5 text-left text-sm text-[#232528] hover:bg-[#F2ECCF]" className="w-full px-4 py-2.5 text-left text-sm text-[#232528] hover:bg-[#F2ECCF]"
@ -161,6 +180,7 @@ const AdminHeader = () => {
> >
Change Password Change Password
</button> </button>
<button <button
type="button" type="button"
className="w-full px-4 py-2.5 text-left text-sm text-[#B52520] hover:bg-[#FEE2E2]" className="w-full px-4 py-2.5 text-left text-sm text-[#B52520] hover:bg-[#FEE2E2]"
@ -172,27 +192,60 @@ const AdminHeader = () => {
)} )}
</div> </div>
</div> </div>
<div className="ml-auto lg:hidden" ref={mobileMenuRef}> <div className="ml-auto lg:hidden" ref={mobileMenuRef}>
<button <button
ref={mobileMenuButtonRef} ref={mobileMenuButtonRef}
type="button" type="button"
aria-label="Open Menu"
className="inline-flex items-center justify-center h-10 w-10 rounded-full border border-[#E5E7EB]" className="inline-flex items-center justify-center h-10 w-10 rounded-full border border-[#E5E7EB]"
onClick={() => setOpen((v) => !v)} onClick={() => setOpen((v) => !v)}
> >
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" /> xmlns="http://www.w3.org/2000/svg"
className="h-5 w-5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M4 6h16M4 12h16M4 18h16"
/>
</svg> </svg>
</button> </button>
</div> </div>
</div> </div>
{open && ( {open && (
<div className="lg:hidden pb-6 border-t border-[#E5E7EB]"> <div className="lg:hidden pb-6 border-t border-[#E5E7EB]">
<nav className="flex flex-col gap-3 pt-4 text-[14px] leading-5"> <nav className="flex flex-col gap-3 pt-4 text-[14px] leading-5">
<NavItem to="/admin/dashboard" end label="Dashboard" iconActive={dashboardIconActiveSrc} iconInactive={dashboardIconInactiveSrc} /> <NavItem
<NavItem to="/admin/validations" label="Manage Submissions" iconActive={checkCircleActiveSrc} iconInactive={manageSubmissionInactiveSrc} /> to="/admin/dashboard"
<NavItem to="/admin/configuration" label="Configuration" iconActive={configActiveSrc} iconInactive={configInactiveSrc} /> end
<NavItem to="/admin/users" label="Users" iconActive={userActiveSrc} iconInactive={userInactiveSrc} /> label="Dashboard"
iconActive={dashboardIconActiveSrc}
iconInactive={dashboardIconInactiveSrc}
/>
<NavItem
to="/admin/validations"
label="Manage Submissions"
iconActive={checkCircleActiveSrc}
iconInactive={manageSubmissionInactiveSrc}
/>
<NavItem
to="/admin/configuration"
label="Configuration"
iconActive={configActiveSrc}
iconInactive={configInactiveSrc}
/>
<NavItem
to="/admin/users"
label="Users"
iconActive={userActiveSrc}
iconInactive={userInactiveSrc}
/>
</nav> </nav>
</div> </div>
)} )}

View File

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import { NavLink, useNavigate } from 'react-router-dom'; import { NavLink, useNavigate } from 'react-router-dom';
import authService from "@/services/auth/authService";
const logoSrc = '/assets/images/FCSCLogo.svg'; const logoSrc = '/assets/images/FCSCLogo.svg';
const surveyIconActiveSrc = '/assets/images/Vector.svg'; const surveyIconActiveSrc = '/assets/images/Vector.svg';
const surveyIconInactiveSrc = '/assets/images/Vectorblack.svg'; const surveyIconInactiveSrc = '/assets/images/Vectorblack.svg';
@ -10,7 +11,7 @@ const historyIconActiveSrc = '/assets/images/mdi_history.svg';
const historyIconInactiveSrc = '/assets/images/history.svg'; const historyIconInactiveSrc = '/assets/images/history.svg';
const overviewIconActiveSrc = '/assets/images/mdi_file-find-outline.svg'; const overviewIconActiveSrc = '/assets/images/mdi_file-find-outline.svg';
const overviewIconInactiveSrc = '/assets/images/mdi_file-find-outline-active.svg'; const overviewIconInactiveSrc = '/assets/images/mdi_file-find-outline-active.svg';
const HeaderBar = () => { const HeaderBar = () => {
const [open, setOpen] = React.useState(false); const [open, setOpen] = React.useState(false);
const [userName, setUserName] = React.useState('User'); const [userName, setUserName] = React.useState('User');
@ -19,199 +20,174 @@ const HeaderBar = () => {
const profileRef = React.useRef(null); const profileRef = React.useRef(null);
const navigate = useNavigate(); const navigate = useNavigate();
const establishmentId = localStorage.getItem("establishment_id") || ""; const establishmentId = localStorage.getItem("establishment_id") || "";
// Close dropdown when clicking outside // Close dropdown on outside click
React.useEffect(() => { React.useEffect(() => {
const handleClickOutside = (event) => { const handleClickOutside = (event) => {
if (profileRef.current && !profileRef.current.contains(event.target)) { if (profileRef.current && !profileRef.current.contains(event.target)) {
setUserOpen(false); setUserOpen(false);
} }
}; };
document.addEventListener('mousedown', handleClickOutside); document.addEventListener('mousedown', handleClickOutside);
return () => { return () => document.removeEventListener('mousedown', handleClickOutside);
document.removeEventListener('mousedown', handleClickOutside);
};
}, []); }, []);
React.useEffect(() => { React.useEffect(() => {
let parsed;
try { try {
const stored = localStorage.getItem('user_profile'); const stored = localStorage.getItem('user_profile');
if (stored) { const parsed = stored ? JSON.parse(stored) : {};
parsed = JSON.parse(stored);
} setUserName(parsed?.name || parsed?.username || parsed?.email || 'User');
} catch (error) { setUserEmail(parsed?.email || '');
parsed = undefined;
}
const name = parsed?.name || parsed?.username || parsed?.email || '';
const email = parsed?.email || '';
if (name) {
setUserName(name);
}
if (email) {
setUserEmail(email);
}
}, []);
const onLogout = () => {
try {
sessionStorage.removeItem('auth_token');
sessionStorage.removeItem('user_profile');
sessionStorage.removeItem('user_role');
sessionStorage.removeItem('establishment_id');
} catch {} } catch {}
}, []);
// ================================
// BACKEND LOGOUT API CONNECTED
// ================================
const onLogout = async () => {
try {
await authService.logout(); // <-- LOGOUT API CALL
} catch (error) {
console.error("Logout API failed:", error);
}
// Clear storage after API
sessionStorage.removeItem('auth_token');
sessionStorage.removeItem('user_profile');
sessionStorage.removeItem('user_role');
sessionStorage.removeItem('establishment_id');
localStorage.removeItem('auth_token');
localStorage.removeItem('user_profile');
localStorage.removeItem('user_role');
localStorage.removeItem('establishment_id');
setUserOpen(false); setUserOpen(false);
navigate('/index'); navigate('/index');
}; };
const onChangePassword = () => { const onChangePassword = () => {
setUserOpen(false); setUserOpen(false);
navigate('/reset-password'); navigate('/reset-password');
}; };
const initials = React.useMemo(() => { const initials = React.useMemo(() => {
const source = userName || userEmail; const source = userName || userEmail;
if (!source) return 'U';
const parts = source.trim().split(' '); const parts = source.trim().split(' ');
const letters = parts.slice(0, 2).map((part) => part.charAt(0)).join(''); const letters = parts.slice(0, 2).map((p) => p[0]).join('');
return letters ? letters.toUpperCase() : 'U'; return letters.toUpperCase() || 'U';
}, [userEmail, userName]); }, [userEmail, userName]);
return ( return (
<header className="bg-white border-b border-b-[#E2E8F0]"> <header className="bg-white border-b border-b-[#E2E8F0]">
<div className="max-w-[1280px] mx-auto px-4"> <div className="max-w-[1280px] mx-auto px-4">
<div className="flex items-center justify-between h-16"> <div className="flex items-center justify-between h-16">
<img src={logoSrc} alt="FCSC" className="w-[131px] h-[40px] opacity-100 rotate-0" /> <img src={logoSrc} alt="FCSC" className="w-[131px] h-[40px]" />
<nav className="hidden md:flex items-center gap-8 text-[14px] leading-5">
{/* DESKTOP NAV */}
<nav className="hidden md:flex items-center gap-8 text-[14px]">
<NavLink <NavLink
to="/dashboard" to="/dashboard"
className={({ isActive }) => `inline-flex items-center gap-2 pb-1 border-b-2 ${isActive ? 'text-[#92722A] font-medium border-[#92722A]' : 'text-[#232528] hover:text-gray-900 border-transparent'}`} className={({ isActive }) =>
aria-current="page" `inline-flex items-center gap-2 pb-1 border-b-2 ${
isActive
? 'text-[#92722A] font-medium border-[#92722A]'
: 'text-[#232528] hover:text-gray-900 border-transparent'
}`
}
> >
{({ isActive }) => ( {({ isActive }) => (
<> <>
<img src={isActive ? dashboardIconActiveSrc : dashboardIconInactiveSrc} alt="Dashboard" className="h-[18px] w-[18px]" /> <img src={isActive ? dashboardIconActiveSrc : dashboardIconInactiveSrc} className="h-[18px] w-[18px]" />
<span>Dashboard</span> <span>Dashboard</span>
</> </>
)} )}
</NavLink> </NavLink>
<NavLink <NavLink
to="/survey" to="/survey"
className={({ isActive, isPending }) => `inline-flex items-center gap-2 pb-1 border-b-2 ${ className={({ isActive }) =>
isActive `inline-flex items-center gap-2 pb-1 border-b-2 ${
? 'text-[#92722A] font-medium border-[#92722A]' isActive
: (window.location.pathname === '/dashboard' || ? 'text-[#92722A] font-medium border-[#92722A]'
window.location.pathname === '/overview' ||
window.location.pathname.startsWith('/edit-profile'))
? 'text-gray-400 cursor-not-allowed'
: 'text-[#232528] hover:text-gray-900 border-transparent' : 'text-[#232528] hover:text-gray-900 border-transparent'
}`} }`
onClick={(e) => { }
if (window.location.pathname === '/dashboard' ||
window.location.pathname === '/overview' ||
window.location.pathname.startsWith('/edit-profile')) {
e.preventDefault();
}
}}
> >
{({ isActive }) => ( {({ isActive }) => (
<> <>
<img <img src={isActive ? surveyIconActiveSrc : surveyIconInactiveSrc} className="h-[18px] w-[18px]" />
src={isActive ? surveyIconActiveSrc : surveyIconInactiveSrc}
alt="Survey"
className={`h-[18px] w-[18px] ${(window.location.pathname === '/dashboard' ||
window.location.pathname === '/overview' ||
window.location.pathname.startsWith('/edit-profile')) ? 'opacity-50' : ''}`}
/>
<span>Survey</span> <span>Survey</span>
</> </>
)} )}
</NavLink> </NavLink>
<NavLink <NavLink
to="/overview" to="/overview"
className={({ isActive }) => `inline-flex items-center gap-2 pb-1 border-b-2 ${isActive ? 'text-[#92722A] font-medium border-[#92722A]' : 'text-[#232528] hover:text-gray-900 border-transparent'}`} className={({ isActive }) =>
`inline-flex items-center gap-2 pb-1 border-b-2 ${
isActive
? 'text-[#92722A] font-medium border-[#92722A]'
: 'text-[#232528] hover:text-gray-900 border-transparent'
}`
}
> >
{({ isActive }) => ( {({ isActive }) => (
<> <>
<img src={isActive ? overviewIconActiveSrc : overviewIconInactiveSrc} alt="Overview" className="h-[18px] w-[18px]" /> <img src={isActive ? overviewIconActiveSrc : overviewIconInactiveSrc} className="h-[18px] w-[18px]" />
<span>Overview</span> <span>Overview</span>
</> </>
)} )}
</NavLink> </NavLink>
<NavLink
to={`/edit-profile/${establishmentId}`} <NavLink
className={({ isActive }) => to={`/edit-profile/${establishmentId}`}
`inline-flex items-center gap-2 pb-1 border-b-2 ${ className={({ isActive }) =>
isActive `inline-flex items-center gap-2 pb-1 border-b-2 ${
? "text-[#92722A] font-medium border-[#92722A]" isActive
: "text-[#232528] hover:text-gray-900 border-transparent" ? "text-[#92722A] font-medium border-[#92722A]"
}` : "text-[#232528] hover:text-gray-900 border-transparent"
} }`
> }
{({ isActive }) => (
<>
<svg
className={`h-[16px] w-[18px] ${
isActive ? "text-[#92722A]" : "text-[#232528]"
}`}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"
/>
</svg>
<span>Profile</span>
</>
)}
</NavLink>
{/* <NavLink
to="/history"
className={({ isActive }) => `inline-flex items-center gap-2 pb-1 border-b-2 ${isActive ? 'text-[#92722A] font-medium border-[#92722A]' : 'text-[#232528] hover:text-gray-900 border-transparent'}`}
> >
{({ isActive }) => ( <svg className="h-[16px] w-[18px]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
<img src={isActive ? historyIconActiveSrc : historyIconInactiveSrc} alt="History" className="h-[18px] w-[18px]" /> </svg>
<span>History</span> <span>Profile</span>
</> </NavLink>
)}
</NavLink> */} {/* PROFILE DROPDOWN */}
<div className="relative" ref={profileRef}> <div className="relative" ref={profileRef}>
<button <button
type="button"
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
setUserOpen((v) => !v); setUserOpen((v) => !v);
}} }}
className="h-8 w-8 rounded-full bg-gray-200 grid place-items-center text-[12px] text-gray-700 cursor-pointer" className="h-8 w-8 rounded-full bg-gray-200 grid place-items-center text-[12px]"
aria-label="Profile"
aria-expanded={userOpen}
> >
{initials} {initials}
</button> </button>
{userOpen && ( {userOpen && (
<div className="absolute right-0 mt-3 w-64 rounded-[12px] border border-[#E2E8F0] bg-white shadow-[0_20px_30px_rgba(15,23,42,0.12)] z-50"> <div className="absolute right-0 mt-3 w-64 rounded-[12px] bg-white shadow z-50">
<div className="px-4 py-3 border-b border-[#F1F5F9] text-left space-y-1"> <div className="px-4 py-3 ">
<p className="text-sm font-semibold text-[#232528] truncate" title={userName}> <p className="text-sm font-semibold">{userName}</p>
{userName?.replace(/([a-z])([A-Z])/g, '$1 $2')} {userEmail && <p className="text-xs text-gray-600">{userEmail}</p>}
</p>
{userEmail && <p className="text-xs text-[#6B7280] truncate" title={userEmail}>{userEmail}</p>}
</div> </div>
<button <button
type="button"
onClick={onChangePassword} onClick={onChangePassword}
className="w-full px-4 py-2.5 text-left text-sm text-[#232528] hover:bg-[#F2ECCF]" className="w-full px-4 py-2.5 text-left text-sm hover:bg-gray-100"
> >
Change Password Change Password
</button> </button>
<button <button
type="button"
onClick={onLogout} onClick={onLogout}
className="w-full px-4 py-2.5 text-left text-sm text-[#B91C1C] hover:bg-[#FEE2E2] transition-colors" className="w-full px-4 py-2.5 text-left text-sm text-red-600 hover:bg-red-100"
> >
Logout Logout
</button> </button>
@ -219,82 +195,32 @@ const HeaderBar = () => {
)} )}
</div> </div>
</nav> </nav>
{/* MOBILE MENU BUTTON */}
<button <button
type="button" className="md:hidden h-9 w-9 rounded hover:bg-gray-100"
aria-label="Open Menu" onClick={() => setOpen(v => !v)}
className="md:hidden inline-flex items-center justify-center h-9 w-9 rounded hover:bg-gray-100"
onClick={() => setOpen((v) => !v)}
> >
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
</svg> </svg>
</button> </button>
</div> </div>
{/* MOBILE NAV */}
{open && ( {open && (
<div className="md:hidden pb-3"> <div className="md:hidden pb-3">
<nav className="flex flex-col gap-2 text-[14px] leading-5"> <nav className="flex flex-col gap-2 text-[14px]">
<NavLink to="/dashboard" onClick={() => setOpen(false)} className={({ isActive }) => `inline-flex items-center gap-2 px-2 py-2 rounded border-b-2 ${isActive ? 'text-[#92722A] bg-[#F2ECCF] border-[#92722A]' : 'text-[#232528] hover:bg-gray-100 border-transparent'}`}> <NavLink to="/dashboard" className="px-2 py-2">Dashboard</NavLink>
<img src={dashboardIconInactiveSrc} alt="Dashboard" className="h-[18px] w-[18px]" /> <NavLink to="/survey" className="px-2 py-2">Survey</NavLink>
<span>Dashboard</span> <NavLink to="/overview" className="px-2 py-2">Overview</NavLink>
</NavLink>
<NavLink <button className="px-2 py-2" onClick={() => navigate('/reset-password')}>
to="/survey" Change Password
onClick={(e) => {
if (window.location.pathname === '/dashboard') {
e.preventDefault();
} else {
setOpen(false);
}
}}
className={({ isActive }) => `inline-flex items-center gap-2 px-2 py-2 rounded border-b-2 ${
isActive
? 'text-[#92722A] bg-[#F2ECCF] border-[#92722A]'
: window.location.pathname === '/dashboard'
? 'text-gray-400 cursor-not-allowed bg-gray-100'
: 'text-[#232528] hover:bg-gray-100 border-transparent'
}`}
>
<img
src={surveyIconInactiveSrc}
alt="Survey"
className={`h-[18px] w-[18px] ${window.location.pathname === '/dashboard' ? 'opacity-50' : ''}`}
/>
<span>Survey</span>
</NavLink>
<NavLink to="/overview" onClick={() => setOpen(false)} className={({ isActive }) => `inline-flex items-center gap-2 px-2 py-2 rounded border-b-2 ${isActive ? 'text-[#92722A] bg-[#F2ECCF] border-[#92722A]' : 'text-[#232528] hover:bg-gray-100 border-transparent'}`}>
<img src={overviewIconInactiveSrc} alt="Overview" className="h-[18px] w-[18px]" />
<span>Overview</span>
</NavLink>
<NavLink to="/history" onClick={() => setOpen(false)} className={({ isActive }) => `inline-flex items-center gap-2 px-2 py-2 rounded border-b-2 ${isActive ? 'text-[#92722A] bg-[#F2ECCF] border-[#92722A]' : 'text-[#232528] hover:bg-gray-100 border-transparent'}`}>
<img src={historyIconInactiveSrc} alt="History" className="h-[18px] w-[18px]" />
<span>History</span>
</NavLink>
<button
type="button"
onClick={() => {
setOpen(false);
navigate('/reset-password');
}}
className="inline-flex items-center gap-2 px-2 py-2 rounded text-[#232528] hover:bg-gray-100 text-left"
>
<span>Change Password</span>
</button> </button>
<button
type="button" <button className="px-2 py-2 text-red-600" onClick={onLogout}>
onClick={() => { Logout
setOpen(false);
navigate('/profile');
}}
className="inline-flex items-center gap-2 px-2 py-2 rounded text-[#232528] hover:bg-gray-100 text-left"
>
<span>Edit Profile</span>
</button>
<button
type="button"
onClick={onLogout}
className="inline-flex items-center gap-2 px-2 py-2 rounded text-[#B91C1C] hover:bg-[#FEE2E2] text-left"
>
<span>Logout</span>
</button> </button>
</nav> </nav>
</div> </div>
@ -303,5 +229,5 @@ const HeaderBar = () => {
</header> </header>
); );
}; };
export default HeaderBar; export default HeaderBar;

View File

@ -1,22 +1,43 @@
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 , withCredentials: true}); const response = await apiClient.post(
'/auth/login',
{ email, password },
{ skipAuth: true, withCredentials: true }
);
return response.data; return response.data;
}; };
export const changePassword = async ({ currentPassword, newPassword }) => { export const changePassword = async ({ currentPassword, newPassword }) => {
const response = await apiClient.post('/auth/change-password', { currentPassword, newPassword }); const response = await apiClient.post('/auth/change-password', {
currentPassword,
newPassword,
});
return response.data; return response.data;
}; };
export const requestPasswordReset = async ({ email }) => { export const requestPasswordReset = async ({ email }) => {
const response = await apiClient.post('/auth/forgot-password', { email }, { skipAuth: true }); const response = await apiClient.post(
'/auth/forgot-password',
{ email },
{ skipAuth: true }
);
return response.data; return response.data;
}; };
export const logout = async () => {
const response = await apiClient.get('/auth/logout', {
withCredentials: true, // Important for session cookie
skipAuth: false, // This API requires user to be logged in
});
return response.data;
};
export default { export default {
login, login,
changePassword, changePassword,
requestPasswordReset, requestPasswordReset,
logout,
}; };