diff --git a/src/components/Topbar.js b/src/components/Topbar.js
index de559b8f..5c40afa0 100644
--- a/src/components/Topbar.js
+++ b/src/components/Topbar.js
@@ -1,16 +1,57 @@
-import React from "react";
+import React, { useState, useEffect, useRef } from "react";
+import ReactDOM from "react-dom";
import { useNavigate } from "react-router-dom";
import { logout, getCurrentUser } from "../services/authService";
-export default function Topbar({ onToggleNav }) {
- const navigate = useNavigate();
- const currentUser = getCurrentUser();
+function NotificationDropdown() {
+ const [open, setOpen] = useState(false);
+ const btnRef = useRef(null);
+ const menuRef = useRef(null);
+ const [menuPos, setMenuPos] = useState({ top: 0, right: 0 });
- const handleLogout = () => {
- logout();
- navigate("/login");
+ useEffect(() => {
+ const handleClick = (e) => {
+ if (btnRef.current?.contains(e.target)) return;
+ if (menuRef.current?.contains(e.target)) return;
+ setOpen(false);
+ };
+ document.addEventListener("mousedown", handleClick);
+ return () => document.removeEventListener("mousedown", handleClick);
+ }, []);
+
+ const handleOpen = () => {
+ if (btnRef.current) {
+ const rect = btnRef.current.getBoundingClientRect();
+ setMenuPos({ top: rect.bottom + 8, right: window.innerWidth - rect.right });
+ }
+ setOpen(!open);
};
+ return (
+ <>
+
+ {open && ReactDOM.createPortal(
+
+
Notifications
+
+
+ No new notifications
+
+
,
+ document.body
+ )}
+ >
+ );
+}
+
+function UserDropdown({ currentUser, onLogout, onNavigate }) {
+ const [open, setOpen] = useState(false);
+ const btnRef = useRef(null);
+ const menuRef = useRef(null);
+ const [menuPos, setMenuPos] = useState({ top: 0, right: 0 });
+
const getInitials = (fullname) => {
if (!fullname) return "U";
const parts = fullname.split(" ");
@@ -20,6 +61,78 @@ export default function Topbar({ onToggleNav }) {
return fullname.substring(0, 2).toUpperCase();
};
+ useEffect(() => {
+ const handleClick = (e) => {
+ if (btnRef.current?.contains(e.target)) return;
+ if (menuRef.current?.contains(e.target)) return;
+ setOpen(false);
+ };
+ document.addEventListener("mousedown", handleClick);
+ return () => document.removeEventListener("mousedown", handleClick);
+ }, []);
+
+ const handleOpen = () => {
+ if (btnRef.current) {
+ const rect = btnRef.current.getBoundingClientRect();
+ setMenuPos({ top: rect.bottom + 8, right: window.innerWidth - rect.right });
+ }
+ setOpen(!open);
+ };
+
+ const handleNav = (path) => {
+ setOpen(false);
+ onNavigate(path);
+ };
+
+ return (
+ <>
+
+ {open && ReactDOM.createPortal(
+
+
+
{getInitials(currentUser?.fullname)}
+
+
{currentUser?.fullname || "User"}
+
{currentUser?.email || ""}
+
+
+
+
+
+
+
+
+
,
+ document.body
+ )}
+ >
+ );
+}
+
+export default function Topbar({ onToggleNav }) {
+ const navigate = useNavigate();
+ const currentUser = getCurrentUser();
+
+ const handleLogout = () => {
+ logout();
+ navigate("/login");
+ };
+
return (
);
diff --git a/src/index.css b/src/index.css
index 2ad15eb0..5af3533b 100644
--- a/src/index.css
+++ b/src/index.css
@@ -263,6 +263,145 @@ body {
background: rgba(255,100,100,0.1);
}
+.notifDropdown {
+ position: fixed;
+ background: #fff;
+ border: 1px solid #ddd;
+ border-radius: 8px;
+ box-shadow: 0 8px 24px rgba(0,0,0,0.15);
+ z-index: 99999;
+ width: 300px;
+ overflow: hidden;
+}
+
+.notifHeader {
+ padding: 12px 16px;
+ font-size: 14px;
+ font-weight: 600;
+ color: #333;
+ border-bottom: 1px solid #eee;
+}
+
+.notifEmpty {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 8px;
+ padding: 30px 16px;
+ color: #999;
+ font-size: 13px;
+}
+
+.notifEmptyIcon {
+ font-size: 24px;
+ color: #ccc;
+}
+
+.topbarUserBtn {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ background: none;
+ border: none;
+ cursor: pointer;
+ padding: 4px 8px;
+ border-radius: 4px;
+}
+
+.topbarUserBtn:hover {
+ background: rgba(255,255,255,0.1);
+}
+
+.topbarUserCaret {
+ color: rgba(255,255,255,0.5);
+ font-size: 12px;
+ margin-left: 2px;
+}
+
+.userDropdown {
+ position: fixed;
+ background: #fff;
+ border: 1px solid #ddd;
+ border-radius: 8px;
+ box-shadow: 0 8px 24px rgba(0,0,0,0.15);
+ z-index: 99999;
+ width: 260px;
+ overflow: hidden;
+}
+
+.userDropdownHeader {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 16px;
+}
+
+.userDropdownAvatar {
+ width: 40px;
+ height: 40px;
+ border-radius: 50%;
+ background: #1fa8de;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-weight: 600;
+ font-size: 14px;
+ color: #fff;
+ flex-shrink: 0;
+}
+
+.userDropdownName {
+ font-size: 14px;
+ font-weight: 600;
+ color: #333;
+}
+
+.userDropdownEmail {
+ font-size: 12px;
+ color: #999;
+}
+
+.userDropdownDivider {
+ height: 1px;
+ background: #eee;
+}
+
+.userDropdownItem {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ width: 100%;
+ padding: 10px 16px;
+ border: none;
+ background: #fff;
+ font-size: 13px;
+ color: #333;
+ cursor: pointer;
+ text-align: left;
+}
+
+.userDropdownItem:hover {
+ background: #f5f5f5;
+}
+
+.userDropdownItem i {
+ width: 16px;
+ text-align: center;
+ color: #999;
+}
+
+.userDropdownLogout {
+ color: #dc3545;
+}
+
+.userDropdownLogout:hover {
+ background: #fff5f5;
+}
+
+.userDropdownLogout i {
+ color: #dc3545;
+}
+
.topbarDivider {
width: 1px;
height: 24px;