* Add next suggestions and remove console logs * Add pomodoro timer * Add pomodoro switch in settings * Fix pomodoro setting * Add timezones to settings * Fix an issue with password reset * Cleanup * Sort tags alphabetically * Clean up today's view * Add an indicator for repeatedly added to today * Refactor tags * Add due date today item * Move recurrence to the subtitle area * Fix today layout * Add a badge to Inbox items * Move inbox badge to sidebar * Add quotes and progress bar * Add translations for quotes * Fix test issues * Add helper script for docker local * Set up overdue tasks * Add linux/arm/v7 build to deploy script * Add linux/arm/v7 build to deploy script pt2 * Fix an issue with helmet and SSL * Add volume db persistence * Fix cog icon issues
172 lines
5.6 KiB
TypeScript
172 lines
5.6 KiB
TypeScript
import React, { useState, useRef, useEffect } from "react";
|
|
import { Link, useNavigate } from "react-router-dom";
|
|
import { UserIcon, Bars3Icon } from "@heroicons/react/24/solid";
|
|
import { useTranslation } from "react-i18next";
|
|
import PomodoroTimer from "./Shared/PomodoroTimer";
|
|
|
|
interface NavbarProps {
|
|
isDarkMode: boolean;
|
|
toggleDarkMode: () => void;
|
|
currentUser: {
|
|
email: string;
|
|
avatarUrl?: string;
|
|
};
|
|
setCurrentUser: React.Dispatch<React.SetStateAction<any>>;
|
|
isSidebarOpen: boolean;
|
|
setIsSidebarOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
|
}
|
|
|
|
const Navbar: React.FC<NavbarProps> = ({
|
|
isDarkMode,
|
|
toggleDarkMode,
|
|
currentUser,
|
|
setCurrentUser,
|
|
isSidebarOpen,
|
|
setIsSidebarOpen,
|
|
}) => {
|
|
const { t } = useTranslation();
|
|
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
|
|
const [pomodoroEnabled, setPomodoroEnabled] = useState(true); // Default to true
|
|
const dropdownRef = useRef<HTMLDivElement>(null);
|
|
const navigate = useNavigate();
|
|
|
|
useEffect(() => {
|
|
const handleClickOutside = (event: MouseEvent) => {
|
|
if (
|
|
dropdownRef.current &&
|
|
!dropdownRef.current.contains(event.target as Node)
|
|
) {
|
|
setIsDropdownOpen(false);
|
|
}
|
|
};
|
|
document.addEventListener("mousedown", handleClickOutside);
|
|
return () => {
|
|
document.removeEventListener("mousedown", handleClickOutside);
|
|
};
|
|
}, []);
|
|
|
|
// Fetch user's pomodoro setting
|
|
useEffect(() => {
|
|
const fetchProfile = async () => {
|
|
try {
|
|
const response = await fetch('/api/profile', {
|
|
credentials: 'include'
|
|
});
|
|
if (response.ok) {
|
|
const profile = await response.json();
|
|
setPomodoroEnabled(profile.pomodoro_enabled !== undefined ? profile.pomodoro_enabled : true);
|
|
}
|
|
} catch (error) {
|
|
console.error('Error fetching profile:', error);
|
|
// Keep default value (true) if fetch fails
|
|
}
|
|
};
|
|
|
|
fetchProfile();
|
|
|
|
// Listen for Pomodoro setting changes from ProfileSettings
|
|
const handlePomodoroSettingChange = (event: CustomEvent) => {
|
|
setPomodoroEnabled(event.detail.enabled);
|
|
};
|
|
|
|
window.addEventListener('pomodoroSettingChanged', handlePomodoroSettingChange as EventListener);
|
|
|
|
return () => {
|
|
window.removeEventListener('pomodoroSettingChanged', handlePomodoroSettingChange as EventListener);
|
|
};
|
|
}, []);
|
|
|
|
const toggleDropdown = () => {
|
|
setIsDropdownOpen(!isDropdownOpen);
|
|
};
|
|
|
|
const handleLogout = async () => {
|
|
try {
|
|
const response = await fetch('/api/logout', {
|
|
method: 'GET',
|
|
credentials: 'include',
|
|
});
|
|
|
|
if (response.ok) {
|
|
setCurrentUser(null);
|
|
navigate('/login');
|
|
} else {
|
|
console.error('Logout failed:', await response.json());
|
|
}
|
|
} catch (error) {
|
|
console.error('Error during logout:', error);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<nav className="fixed top-0 left-0 right-0 z-50 bg-white dark:bg-gray-900 text-gray-900 dark:text-white shadow-md h-16">
|
|
<div className="px-4 sm:px-6 lg:px-8 h-full flex items-center justify-between">
|
|
<div className="flex items-center">
|
|
<button
|
|
onClick={() => setIsSidebarOpen(!isSidebarOpen)}
|
|
className="flex items-center focus:outline-none text-gray-500 dark:text-gray-500"
|
|
aria-label={isSidebarOpen ? "Collapse Sidebar" : "Expand Sidebar"}
|
|
>
|
|
<Bars3Icon className="h-6 mt-1 w-6 mr-2" />
|
|
</button>
|
|
|
|
<Link
|
|
to="/"
|
|
className="flex items-center no-underline text-gray-900 dark:text-white"
|
|
>
|
|
<span className="text-2xl font-bold">tududi</span>
|
|
</Link>
|
|
</div>
|
|
|
|
<div className="flex items-center space-x-4">
|
|
{pomodoroEnabled && <PomodoroTimer />}
|
|
|
|
<div className="relative" ref={dropdownRef}>
|
|
<button
|
|
onClick={toggleDropdown}
|
|
className="flex items-center focus:outline-none"
|
|
aria-label="User Menu"
|
|
>
|
|
{currentUser?.avatarUrl ? (
|
|
<img
|
|
src={currentUser.avatarUrl}
|
|
alt="User Avatar"
|
|
className="h-8 w-8 rounded-full object-cover border-2 border-green-500"
|
|
/>
|
|
) : (
|
|
<div className="h-8 w-8 rounded-full border-2 border-green-500 bg-gray-200 dark:bg-gray-700 flex items-center justify-center">
|
|
<UserIcon className="h-6 w-6 text-gray-500 dark:text-gray-300" />
|
|
</div>
|
|
)}
|
|
</button>
|
|
{isDropdownOpen && (
|
|
<div
|
|
ref={dropdownRef}
|
|
className="absolute right-4 top-16 w-48 bg-white dark:bg-gray-800 rounded-md shadow-lg py-1 border border-gray-200 dark:border-gray-700"
|
|
>
|
|
<Link
|
|
to="/profile"
|
|
className="block px-4 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700"
|
|
onClick={() => setIsDropdownOpen(false)}
|
|
>
|
|
{t('navigation.profileSettings', 'Profile Settings')}
|
|
</Link>
|
|
<button
|
|
onClick={() => {
|
|
setIsDropdownOpen(false);
|
|
handleLogout();
|
|
}}
|
|
className="w-full text-left px-4 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700"
|
|
>
|
|
{t('navigation.logout', 'Logout')}
|
|
</button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
);
|
|
};
|
|
|
|
export default Navbar;
|