fixed
This commit is contained in:
parent
30057d56c0
commit
396567cada
@ -1,5 +1,6 @@
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
import { NavLink, useNavigate } from 'react-router-dom';
|
||||
import { logout as logoutApi } from '@/services/auth/authService';
|
||||
|
||||
const logoSrc = '/assets/images/FCSCLogo.svg';
|
||||
const dashboardIconActiveSrc = '/assets/images/cuida_dashboard-outline.svg';
|
||||
@ -18,15 +19,25 @@ const NavItem = ({ label, to, iconActive, iconInactive, end = false }) => (
|
||||
end={end}
|
||||
className={({ isActive }) =>
|
||||
`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 }) => (
|
||||
<>
|
||||
{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>
|
||||
</>
|
||||
@ -45,26 +56,31 @@ const AdminHeader = () => {
|
||||
const profileButtonRef = useRef(null);
|
||||
const mobileMenuButtonRef = useRef(null);
|
||||
|
||||
// Handle click outside for profile dropdown and mobile menu
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event) => {
|
||||
// Close profile dropdown if clicked outside
|
||||
if (profileOpen && profileRef.current && !profileRef.current.contains(event.target) &&
|
||||
profileButtonRef.current && !profileButtonRef.current.contains(event.target)) {
|
||||
if (
|
||||
profileOpen &&
|
||||
profileRef.current &&
|
||||
!profileRef.current.contains(event.target) &&
|
||||
profileButtonRef.current &&
|
||||
!profileButtonRef.current.contains(event.target)
|
||||
) {
|
||||
setProfileOpen(false);
|
||||
}
|
||||
|
||||
// Close mobile menu if clicked outside
|
||||
if (open && mobileMenuRef.current && !mobileMenuRef.current.contains(event.target) &&
|
||||
mobileMenuButtonRef.current && !mobileMenuButtonRef.current.contains(event.target)) {
|
||||
|
||||
if (
|
||||
open &&
|
||||
mobileMenuRef.current &&
|
||||
!mobileMenuRef.current.contains(event.target) &&
|
||||
mobileMenuButtonRef.current &&
|
||||
!mobileMenuButtonRef.current.contains(event.target)
|
||||
) {
|
||||
setOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Add event listener when the component mounts
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
|
||||
// Clean up the event listener when the component unmounts
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
};
|
||||
@ -82,22 +98,26 @@ const AdminHeader = () => {
|
||||
setUserEmail(parsed.email);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// ignore corrupt storage
|
||||
}
|
||||
} catch {}
|
||||
}, []);
|
||||
|
||||
const handleLogout = () => {
|
||||
// 🚀 UPDATED: LOGOUT VIA API
|
||||
const handleLogout = async () => {
|
||||
try {
|
||||
sessionStorage.removeItem('auth_token');
|
||||
sessionStorage.removeItem('user_profile');
|
||||
sessionStorage.removeItem('user_role');
|
||||
sessionStorage.removeItem('establishment_id');
|
||||
} catch {
|
||||
// ignore storage errors
|
||||
await logoutApi(); // <-- API CALL
|
||||
|
||||
sessionStorage.clear();
|
||||
localStorage.removeItem('auth_token');
|
||||
localStorage.removeItem('user_profile');
|
||||
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 (
|
||||
@ -105,52 +125,51 @@ const AdminHeader = () => {
|
||||
<div className="max-w-[1280px] mx-auto px-4">
|
||||
<div className="flex items-center h-20 gap-6 ">
|
||||
<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 ">
|
||||
<NavItem to="/admin/dashboard" end 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} />
|
||||
{/* <NavItem to="admin/configuration/admin-users"label="Users" iconActive={userActiveSrc} iconInactive={userInactiveSrc} /> */}
|
||||
<NavItem
|
||||
to="/admin/dashboard"
|
||||
end
|
||||
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>
|
||||
|
||||
<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}>
|
||||
<button
|
||||
ref={profileButtonRef}
|
||||
type="button"
|
||||
onClick={() => setProfileOpen((value) => !value)}
|
||||
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'}
|
||||
</button>
|
||||
|
||||
{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="px-4 py-3 border-b border-[#F1F5F9] text-left space-y-1">
|
||||
<p
|
||||
className="text-sm font-semibold text-[#232528] truncate"
|
||||
title={userName}
|
||||
>
|
||||
{userName?.replace(/([a-z])([A-Z])/g, '$1 $2')}
|
||||
</p>
|
||||
<p className="text-xs text-[#6B7280] truncate" title={userEmail}>{userEmail}</p>
|
||||
<p className="text-sm font-semibold text-[#232528] truncate">
|
||||
{userName?.replace(/([a-z])([A-Z])/g, '$1 $2')}
|
||||
</p>
|
||||
<p className="text-xs text-[#6B7280] truncate">
|
||||
{userEmail}
|
||||
</p>
|
||||
</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
|
||||
type="button"
|
||||
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
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
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 className="ml-auto lg:hidden" ref={mobileMenuRef}>
|
||||
<button
|
||||
ref={mobileMenuButtonRef}
|
||||
type="button"
|
||||
aria-label="Open Menu"
|
||||
className="inline-flex items-center justify-center h-10 w-10 rounded-full border border-[#E5E7EB]"
|
||||
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">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
|
||||
<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"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{open && (
|
||||
<div className="lg:hidden pb-6 border-t border-[#E5E7EB]">
|
||||
<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 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} />
|
||||
<NavItem
|
||||
to="/admin/dashboard"
|
||||
end
|
||||
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>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import { NavLink, useNavigate } from 'react-router-dom';
|
||||
|
||||
import authService from "@/services/auth/authService";
|
||||
|
||||
const logoSrc = '/assets/images/FCSCLogo.svg';
|
||||
const surveyIconActiveSrc = '/assets/images/Vector.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 overviewIconActiveSrc = '/assets/images/mdi_file-find-outline.svg';
|
||||
const overviewIconInactiveSrc = '/assets/images/mdi_file-find-outline-active.svg';
|
||||
|
||||
|
||||
const HeaderBar = () => {
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const [userName, setUserName] = React.useState('User');
|
||||
@ -19,199 +20,174 @@ const HeaderBar = () => {
|
||||
const profileRef = React.useRef(null);
|
||||
const navigate = useNavigate();
|
||||
const establishmentId = localStorage.getItem("establishment_id") || "";
|
||||
|
||||
// Close dropdown when clicking outside
|
||||
|
||||
// Close dropdown on outside click
|
||||
React.useEffect(() => {
|
||||
const handleClickOutside = (event) => {
|
||||
if (profileRef.current && !profileRef.current.contains(event.target)) {
|
||||
setUserOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
};
|
||||
return () => document.removeEventListener('mousedown', handleClickOutside);
|
||||
}, []);
|
||||
|
||||
|
||||
React.useEffect(() => {
|
||||
let parsed;
|
||||
try {
|
||||
const stored = localStorage.getItem('user_profile');
|
||||
if (stored) {
|
||||
parsed = JSON.parse(stored);
|
||||
}
|
||||
} catch (error) {
|
||||
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');
|
||||
const parsed = stored ? JSON.parse(stored) : {};
|
||||
|
||||
setUserName(parsed?.name || parsed?.username || parsed?.email || 'User');
|
||||
setUserEmail(parsed?.email || '');
|
||||
|
||||
} 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);
|
||||
navigate('/index');
|
||||
};
|
||||
|
||||
|
||||
const onChangePassword = () => {
|
||||
setUserOpen(false);
|
||||
navigate('/reset-password');
|
||||
};
|
||||
|
||||
|
||||
const initials = React.useMemo(() => {
|
||||
const source = userName || userEmail;
|
||||
if (!source) return 'U';
|
||||
const parts = source.trim().split(' ');
|
||||
const letters = parts.slice(0, 2).map((part) => part.charAt(0)).join('');
|
||||
return letters ? letters.toUpperCase() : 'U';
|
||||
const letters = parts.slice(0, 2).map((p) => p[0]).join('');
|
||||
return letters.toUpperCase() || 'U';
|
||||
}, [userEmail, userName]);
|
||||
|
||||
return (
|
||||
<header className="bg-white border-b border-b-[#E2E8F0]">
|
||||
<div className="max-w-[1280px] mx-auto px-4">
|
||||
<div className="flex items-center justify-between h-16">
|
||||
<img src={logoSrc} alt="FCSC" className="w-[131px] h-[40px] opacity-100 rotate-0" />
|
||||
<nav className="hidden md:flex items-center gap-8 text-[14px] leading-5">
|
||||
<img src={logoSrc} alt="FCSC" className="w-[131px] h-[40px]" />
|
||||
|
||||
{/* DESKTOP NAV */}
|
||||
<nav className="hidden md:flex items-center gap-8 text-[14px]">
|
||||
<NavLink
|
||||
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'}`}
|
||||
aria-current="page"
|
||||
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 }) => (
|
||||
<>
|
||||
<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>
|
||||
</>
|
||||
)}
|
||||
</NavLink>
|
||||
|
||||
<NavLink
|
||||
to="/survey"
|
||||
className={({ isActive, isPending }) => `inline-flex items-center gap-2 pb-1 border-b-2 ${
|
||||
isActive
|
||||
? 'text-[#92722A] font-medium border-[#92722A]'
|
||||
: (window.location.pathname === '/dashboard' ||
|
||||
window.location.pathname === '/overview' ||
|
||||
window.location.pathname.startsWith('/edit-profile'))
|
||||
? 'text-gray-400 cursor-not-allowed'
|
||||
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'
|
||||
}`}
|
||||
onClick={(e) => {
|
||||
if (window.location.pathname === '/dashboard' ||
|
||||
window.location.pathname === '/overview' ||
|
||||
window.location.pathname.startsWith('/edit-profile')) {
|
||||
e.preventDefault();
|
||||
}
|
||||
}}
|
||||
}`
|
||||
}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<>
|
||||
<img
|
||||
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' : ''}`}
|
||||
/>
|
||||
<img src={isActive ? surveyIconActiveSrc : surveyIconInactiveSrc} className="h-[18px] w-[18px]" />
|
||||
<span>Survey</span>
|
||||
</>
|
||||
)}
|
||||
</NavLink>
|
||||
|
||||
<NavLink
|
||||
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 }) => (
|
||||
<>
|
||||
<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>
|
||||
</>
|
||||
)}
|
||||
</NavLink>
|
||||
<NavLink
|
||||
to={`/edit-profile/${establishmentId}`}
|
||||
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] ${
|
||||
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'}`}
|
||||
|
||||
<NavLink
|
||||
to={`/edit-profile/${establishmentId}`}
|
||||
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 }) => (
|
||||
<>
|
||||
<img src={isActive ? historyIconActiveSrc : historyIconInactiveSrc} alt="History" className="h-[18px] w-[18px]" />
|
||||
<span>History</span>
|
||||
</>
|
||||
)}
|
||||
</NavLink> */}
|
||||
<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" />
|
||||
</svg>
|
||||
<span>Profile</span>
|
||||
</NavLink>
|
||||
|
||||
{/* PROFILE DROPDOWN */}
|
||||
<div className="relative" ref={profileRef}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setUserOpen((v) => !v);
|
||||
}}
|
||||
className="h-8 w-8 rounded-full bg-gray-200 grid place-items-center text-[12px] text-gray-700 cursor-pointer"
|
||||
aria-label="Profile"
|
||||
aria-expanded={userOpen}
|
||||
className="h-8 w-8 rounded-full bg-gray-200 grid place-items-center text-[12px]"
|
||||
>
|
||||
{initials}
|
||||
</button>
|
||||
|
||||
{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="px-4 py-3 border-b border-[#F1F5F9] text-left space-y-1">
|
||||
<p className="text-sm font-semibold text-[#232528] truncate" title={userName}>
|
||||
{userName?.replace(/([a-z])([A-Z])/g, '$1 $2')}
|
||||
</p>
|
||||
{userEmail && <p className="text-xs text-[#6B7280] truncate" title={userEmail}>{userEmail}</p>}
|
||||
<div className="absolute right-0 mt-3 w-64 rounded-[12px] bg-white shadow z-50">
|
||||
<div className="px-4 py-3 ">
|
||||
<p className="text-sm font-semibold">{userName}</p>
|
||||
{userEmail && <p className="text-xs text-gray-600">{userEmail}</p>}
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
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
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
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
|
||||
</button>
|
||||
@ -219,82 +195,32 @@ const HeaderBar = () => {
|
||||
)}
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
{/* MOBILE MENU BUTTON */}
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Open Menu"
|
||||
className="md:hidden inline-flex items-center justify-center h-9 w-9 rounded hover:bg-gray-100"
|
||||
onClick={() => setOpen((v) => !v)}
|
||||
className="md:hidden 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">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* MOBILE NAV */}
|
||||
{open && (
|
||||
<div className="md:hidden pb-3">
|
||||
<nav className="flex flex-col gap-2 text-[14px] leading-5">
|
||||
<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'}`}>
|
||||
<img src={dashboardIconInactiveSrc} alt="Dashboard" className="h-[18px] w-[18px]" />
|
||||
<span>Dashboard</span>
|
||||
</NavLink>
|
||||
<NavLink
|
||||
to="/survey"
|
||||
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>
|
||||
<nav className="flex flex-col gap-2 text-[14px]">
|
||||
<NavLink to="/dashboard" className="px-2 py-2">Dashboard</NavLink>
|
||||
<NavLink to="/survey" className="px-2 py-2">Survey</NavLink>
|
||||
<NavLink to="/overview" className="px-2 py-2">Overview</NavLink>
|
||||
|
||||
<button className="px-2 py-2" onClick={() => navigate('/reset-password')}>
|
||||
Change Password
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
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 className="px-2 py-2 text-red-600" onClick={onLogout}>
|
||||
Logout
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
@ -303,5 +229,5 @@ const HeaderBar = () => {
|
||||
</header>
|
||||
);
|
||||
};
|
||||
|
||||
export default HeaderBar;
|
||||
|
||||
export default HeaderBar;
|
||||
|
||||
@ -1,22 +1,43 @@
|
||||
import apiClient from '@/services/api/apiClient';
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
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 {
|
||||
login,
|
||||
changePassword,
|
||||
requestPasswordReset,
|
||||
logout,
|
||||
};
|
||||
|
||||
Loading…
Reference in New Issue
Block a user