feat: Add notifications dropdown to navigation and update translations for notifications

This commit is contained in:
2025-10-06 16:01:29 +02:00
parent 5011f80fc4
commit 80a53d5d15
2 changed files with 80 additions and 4 deletions

View File

@@ -4,13 +4,29 @@ import Link from "next/link";
import { usePathname } from "next/navigation"; import { usePathname } from "next/navigation";
import { useSession, signOut } from "next-auth/react"; import { useSession, signOut } from "next-auth/react";
import { useTranslation } from "@/lib/i18n"; import { useTranslation } from "@/lib/i18n";
import { useState } from "react"; import { useState, useEffect, useRef } from "react";
const Navigation = () => { const Navigation = () => {
const pathname = usePathname(); const pathname = usePathname();
const { data: session, status } = useSession(); const { data: session, status } = useSession();
const { t } = useTranslation(); const { t } = useTranslation();
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
const [isNotificationsOpen, setIsNotificationsOpen] = useState(false);
const notificationsRef = useRef(null);
// Close notifications dropdown when clicking outside
useEffect(() => {
const handleClickOutside = (event) => {
if (notificationsRef.current && !notificationsRef.current.contains(event.target)) {
setIsNotificationsOpen(false);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, []);
const isActive = (path) => { const isActive = (path) => {
if (path === "/") return pathname === "/"; if (path === "/") return pathname === "/";
@@ -74,12 +90,43 @@ const Navigation = () => {
) : null} ) : null}
</div> </div>
{/* Right side - User/Auth section */} {/* Right side - Notifications and User/Auth section */}
<div className="flex items-center space-x-4"> <div className="flex items-center space-x-4">
{status === "loading" ? ( {status === "loading" ? (
<div className="text-blue-100">{t('navigation.loading')}</div> <div className="text-blue-100">{t('navigation.loading')}</div>
) : session ? ( ) : session ? (
<> <>
{/* Notifications */}
<div className="relative" ref={notificationsRef}>
<button
onClick={() => setIsNotificationsOpen(!isNotificationsOpen)}
className="p-2 rounded-md text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors relative"
>
<svg className="w-6 h-6" fill="currentColor" stroke="none" viewBox="0 0 640 640">
<path d="M320 64C306.7 64 296 74.7 296 88L296 97.7C214.6 109.3 152 179.4 152 264L152 278.5C152 316.2 142 353.2 123 385.8L101.1 423.2C97.8 429 96 435.5 96 442.2C96 463.1 112.9 480 133.8 480L506.2 480C527.1 480 544 463.1 544 442.2C544 435.5 542.2 428.9 538.9 423.2L517 385.7C498 353.1 488 316.1 488 278.4L488 263.9C488 179.3 425.4 109.2 344 97.6L344 87.9C344 74.6 333.3 63.9 320 63.9zM488.4 432L151.5 432L164.4 409.9C187.7 370 200 324.6 200 278.5L200 264C200 197.7 253.7 144 320 144C386.3 144 440 197.7 440 264L440 278.5C440 324.7 452.3 370 475.5 409.9L488.4 432zM252.1 528C262 556 288.7 576 320 576C351.3 576 378 556 387.9 528L252.1 528z" />
</svg>
{/* Notification badge */}
<span className="absolute -top-1 -right-1 h-4 w-4 bg-gray-400 dark:bg-gray-500 text-white text-xs rounded-full flex items-center justify-center">
0
</span>
</button>
{/* Notifications Dropdown */}
{isNotificationsOpen && (
<div className="absolute right-0 mt-2 w-80 bg-white dark:bg-gray-800 rounded-md shadow-lg border border-gray-200 dark:border-gray-700 z-50">
<div className="p-4 border-b border-gray-200 dark:border-gray-700">
<h3 className="text-sm font-medium text-gray-900 dark:text-white">{t('notifications.title')}</h3>
</div>
<div className="max-h-96 overflow-y-auto">
<div className="p-4 text-center text-gray-500 dark:text-gray-400">
<p className="text-sm">{t('notifications.noNotifications')}</p>
<p className="text-xs mt-1">{t('notifications.placeholder')}</p>
</div>
</div>
</div>
)}
</div>
{/* User Info */} {/* User Info */}
<div className="hidden md:flex items-center space-x-3"> <div className="hidden md:flex items-center space-x-3">
<Link href="/settings" className="text-right hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md px-2 py-1 transition-colors"> <Link href="/settings" className="text-right hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md px-2 py-1 transition-colors">
@@ -117,7 +164,10 @@ const Navigation = () => {
{/* Mobile menu button */} {/* Mobile menu button */}
{session && ( {session && (
<button <button
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)} onClick={() => {
setIsMobileMenuOpen(!isMobileMenuOpen);
setIsNotificationsOpen(false);
}}
className="md:hidden p-2 rounded-md text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors" className="md:hidden p-2 rounded-md text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
> >
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -140,7 +190,10 @@ const Navigation = () => {
<Link <Link
key={item.href} key={item.href}
href={item.href} href={item.href}
onClick={() => setIsMobileMenuOpen(false)} onClick={() => {
setIsMobileMenuOpen(false);
setIsNotificationsOpen(false);
}}
className={`px-4 py-3 rounded-md text-sm font-medium transition-colors ${ className={`px-4 py-3 rounded-md text-sm font-medium transition-colors ${
isActive(item.href) isActive(item.href)
? "bg-blue-50 dark:bg-blue-900/50 text-blue-700 dark:text-blue-300 border border-blue-200 dark:border-blue-700" ? "bg-blue-50 dark:bg-blue-900/50 text-blue-700 dark:text-blue-300 border border-blue-200 dark:border-blue-700"
@@ -167,6 +220,22 @@ const Navigation = () => {
</div> </div>
</Link> </Link>
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
{/* Mobile Notifications */}
<button
onClick={() => {
setIsNotificationsOpen(!isNotificationsOpen);
setIsMobileMenuOpen(false);
}}
className="p-2 rounded-md text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors relative"
>
<svg className="w-5 h-5" fill="currentColor" stroke="none" viewBox="0 0 640 640">
<path d="M320 64C306.7 64 296 74.7 296 88L296 97.7C214.6 109.3 152 179.4 152 264L152 278.5C152 316.2 142 353.2 123 385.8L101.1 423.2C97.8 429 96 435.5 96 442.2C96 463.1 112.9 480 133.8 480L506.2 480C527.1 480 544 463.1 544 442.2C544 435.5 542.2 428.9 538.9 423.2L517 385.7C498 353.1 488 316.1 488 278.4L488 263.9C488 179.3 425.4 109.2 344 97.6L344 87.9C344 74.6 333.3 63.9 320 63.9zM488.4 432L151.5 432L164.4 409.9C187.7 370 200 324.6 200 278.5L200 264C200 197.7 253.7 144 320 144C386.3 144 440 197.7 440 264L440 278.5C440 324.7 452.3 370 475.5 409.9L488.4 432zM252.1 528C262 556 288.7 576 320 576C351.3 576 378 556 387.9 528L252.1 528z" />
</svg>
{/* Notification badge */}
<span className="absolute -top-1 -right-1 h-3 w-3 bg-gray-400 dark:bg-gray-500 text-white text-xs rounded-full flex items-center justify-center">
0
</span>
</button>
<button <button
onClick={() => { onClick={() => {
setIsMobileMenuOpen(false); setIsMobileMenuOpen(false);

View File

@@ -24,6 +24,13 @@ const translations = {
signIn: "Zaloguj się" signIn: "Zaloguj się"
}, },
// Notifications
notifications: {
title: "Powiadomienia",
noNotifications: "Brak powiadomień",
placeholder: "To jest miejsce na przyszłe powiadomienia"
},
// Common UI elements // Common UI elements
common: { common: {
save: "Zapisz", save: "Zapisz",