feat: Add notifications dropdown to navigation and update translations for notifications
This commit is contained in:
@@ -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);
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
Reference in New Issue
Block a user