diff --git a/frontend/src/animations/AnimatedRoutes.tsx b/frontend/src/animations/AnimatedRoutes.tsx index 86216747..52d4dfa1 100644 --- a/frontend/src/animations/AnimatedRoutes.tsx +++ b/frontend/src/animations/AnimatedRoutes.tsx @@ -5,7 +5,6 @@ import { Routes, Route, useLocation, Navigate } from "react-router-dom"; import "./transitions.css"; import { observer } from "mobx-react-lite"; -import Account from "../Account"; import { useAuthContext } from "../context/auth/authContext"; import MainPage from "../main-page/MainPage"; import Login from "../Login"; @@ -25,12 +24,12 @@ const AnimatedRoutes = observer(() => { : } + element={isAuthenticated ? : } /> : + isAuthenticated ? : } /> { } /> - : } - /> } /> + } /> diff --git a/frontend/src/custom/RingButton.tsx b/frontend/src/custom/RingButton.tsx index f162a457..04a94318 100644 --- a/frontend/src/custom/RingButton.tsx +++ b/frontend/src/custom/RingButton.tsx @@ -1,4 +1,3 @@ -import { Button } from "@chakra-ui/react"; import "../styles/button.css"; /* Maintained with button.css */ @@ -25,28 +24,28 @@ export default function RingButton({ text, color }: RingButtonProps) { return ( <> {ButtonColorOption.GREEN === color && ( - + )} {ButtonColorOption.ORANGE === color && ( - + )} {ButtonColorOption.GRAY === color && ( - + )} ); diff --git a/frontend/src/main-page/header/AccountInfo.tsx b/frontend/src/main-page/header/AccountInfo.tsx new file mode 100644 index 00000000..b35e54a0 --- /dev/null +++ b/frontend/src/main-page/header/AccountInfo.tsx @@ -0,0 +1,76 @@ +import React, { useState } from 'react'; +import './styles/AccountInfo.css'; +import { logoutUser, +} from "../../external/bcanSatchel/actions.ts"; +import { useNavigate } from 'react-router-dom'; +import { useAuthContext } from "../../context/auth/authContext"; +import { createPortal } from 'react-dom'; + +interface AccountInfoProps { + email: string; + username: string; + role: string; + setOpenModal: (modal: string | null) => void; +} + +const AccountInfo: React.FC = ({ + email, + username, + role, + setOpenModal, +}) => { + + const navigate = useNavigate(); + const {user} = useAuthContext(); + + const [isAdmin] = useState(user?.position === 'Admin'); + + const handleUserListClick = () => { + navigate('users'); + setOpenModal(null); + }; + + const handleLogoutClick = () => { + logoutUser(); + setOpenModal(null); + }; + + return createPortal( +
e.stopPropagation()}> +
+

Account Info

+ +
+
+ E-mail: + {email} +
+
+ Username: + {username} +
+
+ Role: + {role} +
+
+ {isAdmin && +
+ + +
+ } +
+
, + document.body + ); +}; + + +export default AccountInfo; diff --git a/frontend/src/main-page/header/Bell.tsx b/frontend/src/main-page/header/Bell.tsx index 759ce534..3ba3887f 100644 --- a/frontend/src/main-page/header/Bell.tsx +++ b/frontend/src/main-page/header/Bell.tsx @@ -1,24 +1,28 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faBell } from "@fortawesome/free-solid-svg-icons"; -import { useEffect, useState } from "react"; +import { useEffect } from "react"; //import { api } from "../../api"; //todo: swap out dummy data with real api fetch when backend is ready import NotificationPopup from "../notifications/NotificationPopup"; import { setNotifications as setNotificationsAction } from "../../external/bcanSatchel/actions"; import { getAppStore } from "../../external/bcanSatchel/store"; import { observer } from "mobx-react-lite"; +import { api } from "../../api"; // get current user id // const currUserID = sessionStorage.getItem('userId'); // const currUserID = "bcanuser33"; -const BellButton = observer(() => { +interface BellButtonProps { + // onClick handler to open notification popup + setOpenModal: (modal: string | null) => void; + openModal: string | null; +} + +const BellButton: React.FC = observer(({ setOpenModal, openModal }) => { // stores notifications for the current user const store = getAppStore(); const notifications = store.notifications ?? []; - // determines whether bell has been clicked - const [isClicked, setClicked] = useState(false); - // logs the notifications for the current user whenever they are fetched useEffect(() => { console.log(notifications); @@ -27,36 +31,33 @@ const BellButton = observer(() => { // function that handles when button is clicked and fetches notifications const handleClick = async () => { //temporary dummy data for now - const dummyNotifications = [ - { - id: 1, - title: "Grant Deadline", - message: "Grant A deadline approaching in 3 days", - }, - { id: 2, title: "Grant Deadline", message: "Grant B deadline tomorrow!" }, - { - id: 3, - title: "Grant Deadline", - message: "Grant C deadline passed yesterday!", - }, - { id: 4, title: "Grant Deadline", message: "Grant D deadline tomorrow!" }, - ]; - //previous api logic (for later) - //const response = await api( - //`/notifications/user/${currUserID}`, - //{ - //method: "GET", - //} - //); - //console.log(response); - //const currNotifications = await response.json(); - setNotificationsAction(dummyNotifications); - setClicked(!isClicked); + // const dummyNotifications = [ + // { + // id: 1, + // title: "Grant Deadline", + // message: "Grant A deadline approaching in 3 days", + // }, + // { id: 2, title: "Grant Deadline", message: "Grant B deadline tomorrow!" }, + // { + // id: 3, + // title: "Grant Deadline", + // message: "Grant C deadline passed yesterday!", + // }, + // { id: 4, title: "Grant Deadline", message: "Grant D deadline tomorrow!" }, + // ]; + const response = await api( + `/notifications/user/${store.user?.userId}`, + { + method: "GET", + } + ); + console.log(response); + const currNotifications = await response.json(); + setNotificationsAction(currNotifications); + setOpenModal(openModal === "bell" ? null : "bell"); return notifications; }; - const handleClose = () => setClicked(false); - return (
{ style={{ position: "relative", display: "inline-block" }} >
- {isClicked && ( + {(openModal === "bell" ? ( - )} + ) : null)}
); }); diff --git a/frontend/src/main-page/header/Header.tsx b/frontend/src/main-page/header/Header.tsx index 20e498d6..506b48e5 100644 --- a/frontend/src/main-page/header/Header.tsx +++ b/frontend/src/main-page/header/Header.tsx @@ -7,14 +7,12 @@ import { } from "../../../../middle-layer/types/Status.ts"; import { updateFilter, - logoutUser, } from "../../external/bcanSatchel/actions.ts"; import { observer } from "mobx-react-lite"; -import { Menu, Button } from "@chakra-ui/react"; -import { FaCog } from "react-icons/fa"; import BellButton from "./Bell.tsx"; import { useLocation } from 'react-router-dom'; import UserButton from "./UserButton.tsx"; +import { useState } from "react"; interface NavBarProps { name: string; @@ -33,6 +31,7 @@ const linkList: NavBarProps[] = [ * The cog displays a dropdown with "My Account" and "Logout" options. */ const Header: React.FC = observer(() => { + const [openModal, setOpenModal] = useState(null); function categoryClicked( e: React.MouseEvent, @@ -45,10 +44,7 @@ const Header: React.FC = observer(() => { } } - const handleLogout = () => { - logoutUser(); - }; - + return (
@@ -74,29 +70,12 @@ const Header: React.FC = observer(() => { ))}
-
- +
setOpenModal(openModal === "bell" ? null : "bell")}> + +
+
setOpenModal(openModal === "user" ? null : "user")}> +
- - - - - - - - My Account - - - - Logout - - - - - -
diff --git a/frontend/src/main-page/header/UserButton.tsx b/frontend/src/main-page/header/UserButton.tsx index 7ac76642..1b6ff1ca 100644 --- a/frontend/src/main-page/header/UserButton.tsx +++ b/frontend/src/main-page/header/UserButton.tsx @@ -1,17 +1,47 @@ import { faUser } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { Link } from "react-router-dom"; +import AccountInfo from "./AccountInfo"; + +import "./styles/UserButton.css"; +import { useAuthContext } from "../../context/auth/authContext"; + +interface UserButtonProps { + setOpenModal: (modal: string | null) => void; + openModal: string | null; +} + +const UserButton: React.FC = ({ setOpenModal, openModal }) => { + const { user } = useAuthContext(); + const toggleAccountInfo = () => { + setOpenModal(openModal === "user" ? null : "user"); + }; + -const UserButton = () => { return ( -
- - - + + {openModal === "user" && ( + + )} +
); }; -export default UserButton; +export default UserButton; \ No newline at end of file diff --git a/frontend/src/main-page/header/styles/AccountInfo.css b/frontend/src/main-page/header/styles/AccountInfo.css new file mode 100644 index 00000000..c73064bf --- /dev/null +++ b/frontend/src/main-page/header/styles/AccountInfo.css @@ -0,0 +1,125 @@ + +.account-modal { + position: fixed; + top: 70px; + right: 20px; + z-index: 9999; + min-width: 350px; + max-width: 480px; +} + +.account-popup { + position: absolute; + padding: 20px 40px; + background-color: white; + border: 3px solid #2d2d2d; + border-radius: 16px; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15); + z-index: 9999; +} + +.popup-header { + color: #000; + font-family: Montserrat; + font-size: 22px; + font-style: normal; + font-weight: 100; + line-height: normal; + margin-bottom: 16px; + margin-top: 0; + letter-spacing: -0.5px; + z-index: 1000; +} + +.account-info { + display: flex; + flex-direction: column; + gap: 10px; + margin-bottom: 20px; + border-left: 4px solid #ff6b35; + padding-left: 20px; + z-index: 1000; +} + +.info-row { + display: flex; + align-items: center; + font-size: 14px; + gap: 12px; + z-index: 1000; +} + +.info-label { + color: #646464; + font-family: Montserrat; + font-size: 15px; + font-style: normal; + font-weight: 700; + line-height: normal; + z-index: 1000; +} + +.info-value { + color: #000; +font-family: Montserrat; +font-size: 15px; +font-style: normal; +font-weight: 400; +line-height: normal; +z-index: 1000; +} + +.button-container { + display: flex; + justify-content: space-between; + align-items: center; + gap: 16px; + margin-top: 16px; + z-index: 1000; +} + +.user-list-btn { + display: flex; + align-items: center; + gap: 8px; + padding: 10px 20px; + background: white; + border: 2px solid #2d2d2d; + border-radius: 10px; + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: all 0.2s ease; + color: #2d2d2d; +} + +.user-list-btn:hover { + background: #f8f8f8; + transform: translateY(-1px); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); +} + +.user-icon { + width: 18px; + height: 18px; +} + +.logout-btn { + padding: 10px 26px; + background: #ffb3b3; + border: 2px solid #ff8888; + border-radius: 10px; + color: #d63031; + font-size: 14px; + font-weight: 600; + cursor: pointer; + transition: all 0.2s ease; +} + +.logout-btn:hover { + background: #ff9999; + color: white; + border-color: #ff7777; + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(255, 119, 119, 0.3); +} diff --git a/frontend/src/main-page/header/styles/UserButton.css b/frontend/src/main-page/header/styles/UserButton.css new file mode 100644 index 00000000..1273aa99 --- /dev/null +++ b/frontend/src/main-page/header/styles/UserButton.css @@ -0,0 +1,23 @@ + +.user-container { + display: inline-block; +} + +.user-container button { + background-color: transparent; + border: none; + cursor: pointer; + transition: background-color 0.3s, color 0.3s; + padding: 8px; + border-radius: 5px; +} + +.user-button.hovered { + background-color: #f39c12; + color: white; +} + +.user-button:hover { + background-color: #f39c12; + color: white; +} \ No newline at end of file diff --git a/frontend/src/main-page/notifications/NotificationPopup.tsx b/frontend/src/main-page/notifications/NotificationPopup.tsx index 26f2be59..0e0a1f9d 100644 --- a/frontend/src/main-page/notifications/NotificationPopup.tsx +++ b/frontend/src/main-page/notifications/NotificationPopup.tsx @@ -4,18 +4,18 @@ import '../../styles/notification.css'; interface NotificationPopupProps { notifications: { id: number; title: string; message: string }[]; - onClose: () => void; + setOpenModal: (value: string | null) => void; } const NotificationPopup: React.FC = ({ notifications, - onClose + setOpenModal }) => { return createPortal(

Alerts

-
diff --git a/frontend/src/styles/button.css b/frontend/src/styles/button.css index 28585e1b..b9392dd3 100644 --- a/frontend/src/styles/button.css +++ b/frontend/src/styles/button.css @@ -41,6 +41,7 @@ text-align: center; padding: 0.5rem 0; border-radius: 15px; + z-index: 0; } /* Custom button color classes */ diff --git a/package-lock.json b/package-lock.json index a643330a..18ba706c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,9 @@ "recharts": "^3.2.1", "ts-morph": "^23.0.0", "typescript": "^5.7.3" + }, + "devDependencies": { + "baseline-browser-mapping": "^2.8.32" } }, "node_modules/@nodelib/fs.scandir": { @@ -174,6 +177,16 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "license": "MIT" }, + "node_modules/baseline-browser-mapping": { + "version": "2.8.32", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.32.tgz", + "integrity": "sha512-OPz5aBThlyLFgxyhdwf/s2+8ab3OvT7AdTNvKHBwpXomIYeXqpUUuT8LrdtxZSsWJ4R4CU1un4XGh5Ez3nlTpw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, "node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", diff --git a/package.json b/package.json index aa7149c0..ad0a61ee 100644 --- a/package.json +++ b/package.json @@ -16,5 +16,8 @@ }, "keywords": [], "author": "", - "license": "ISC" + "license": "ISC", + "devDependencies": { + "baseline-browser-mapping": "^2.8.32" + } }