165 lines
5.3 KiB
TypeScript

import React, { useEffect, useState } from 'react';
import { useNavigate, useLocation } from 'react-router-dom';
import { useTheme } from '../contexts/ThemeContext';
import { useLanguage } from '../contexts/LanguageContext';
import { useTranslation } from 'react-i18next';
import { Moon, Sun, Languages, LogOut, UserCircle, Menu } from 'lucide-react';
import NotificationBell from './NotificationBell';
import { useSidebarLayout } from '../contexts/SidebarLayoutContext';
interface HeaderProps {
userEmail?: string;
}
const getStoredUserDisplayName = (): string => {
try {
const user = localStorage.getItem('user');
if (!user) return '';
const parsed = JSON.parse(user);
return parsed.full_name || parsed.email || parsed.user_id || parsed.name || '';
} catch {
return '';
}
};
const Header: React.FC<HeaderProps> = ({ userEmail }) => {
const navigate = useNavigate();
const location = useLocation();
const { theme, toggleTheme } = useTheme();
const { language, changeLanguage } = useLanguage();
const { t } = useTranslation();
const [userDisplayName, setUserDisplayName] = useState<string>(getStoredUserDisplayName);
const { openMobileSidebar } = useSidebarLayout();
useEffect(() => {
const fetchUserDisplayName = async () => {
try {
const userResponse = await fetch('/api/method/frappe.auth.get_logged_user', {
method: 'GET',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
});
const userData = await userResponse.json();
const email = userData.message;
if (email) {
const fullNameResponse = await fetch(
`/api/resource/User/${encodeURIComponent(email)}?fields=["full_name"]`,
{
method: 'GET',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
}
);
const fullNameData = await fullNameResponse.json();
setUserDisplayName(fullNameData.data?.full_name || email);
return;
}
} catch (error) {
console.error('Error fetching user display name:', error);
}
if (userEmail) {
setUserDisplayName(userEmail);
}
};
fetchUserDisplayName();
}, [userEmail]);
const handleLogout = async () => {
localStorage.removeItem('user');
localStorage.removeItem('sid');
try {
const csrfToken = document.cookie
.split('; ')
.find(row => row.startsWith('X-Frappe-CSRF-Token='))
?.split('=')[1] || '';
await fetch('/api/method/frappe.auth.logout', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Frappe-CSRF-Token': csrfToken,
},
credentials: 'include',
});
await fetch('/?cmd=web_logout', {
credentials: 'include',
});
} catch (err) {
console.error('Logout error:', err);
} finally {
window.location.href = '/asm_app/login';
}
};
const isProfileActive = location.pathname === '/user-profile';
return (
<header className="h-14 bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 px-4 flex items-center justify-between gap-2 flex-shrink-0">
<button
type="button"
onClick={openMobileSidebar}
className="lg:hidden p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors text-gray-700 dark:text-gray-300"
aria-label={t('common.menu', { defaultValue: 'Open menu' })}
>
<Menu size={20} />
</button>
<div className="flex items-center justify-end gap-2 flex-1 min-w-0">
<button
type="button"
onClick={() => navigate('/user-profile')}
className={`
flex items-center gap-2 px-3 py-2 rounded-lg transition-colors text-white
${isProfileActive
? 'bg-indigo-700 ring-2 ring-indigo-300 dark:ring-indigo-500'
: 'bg-indigo-600 hover:bg-indigo-700'}
`}
aria-label={userDisplayName || t('common.userProfile', { defaultValue: 'User Profile' })}
>
{userDisplayName && (
<span className="text-sm font-medium max-w-[180px] truncate">
{userDisplayName}
</span>
)}
<UserCircle size={20} className="flex-shrink-0" />
</button>
<div className="relative">
<NotificationBell />
</div>
<button
onClick={() => changeLanguage(language === 'en' ? 'ar' : 'en')}
className="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors text-gray-700 dark:text-gray-300"
title={t('common.language')}
>
<Languages size={20} />
</button>
<button
onClick={toggleTheme}
className="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors text-gray-700 dark:text-gray-300"
title={theme === 'light' ? t('common.darkMode') : t('common.lightMode')}
>
{theme === 'light' ? <Moon size={20} /> : <Sun size={20} />}
</button>
<button
onClick={handleLogout}
className="p-2 rounded-lg hover:bg-red-50 dark:hover:bg-red-900/20 transition-colors text-red-600 dark:text-red-400"
title={t('common.logout')}
>
<LogOut size={20} />
</button>
</div>
</header>
);
};
export default Header;