diff --git a/site/src/component/AppHeader/AppHeader.scss b/site/src/component/AppHeader/AppHeader.scss index 44a09687..2caee222 100644 --- a/site/src/component/AppHeader/AppHeader.scss +++ b/site/src/component/AppHeader/AppHeader.scss @@ -18,6 +18,11 @@ } } +.school-term { + margin-bottom: 0; + margin-right: 0.75em; +} + .navbar { position: sticky; z-index: 300; @@ -51,12 +56,8 @@ display: flex; justify-content: center; align-items: center; - cursor: pointer; - - .navbar-menu-icon { - width: 3vh; - height: 3vh; - } + border: none; + background: none; } .desktop-toggle { diff --git a/site/src/component/AppHeader/AppHeader.tsx b/site/src/component/AppHeader/AppHeader.tsx index 782221b3..c9a42f1b 100644 --- a/site/src/component/AppHeader/AppHeader.tsx +++ b/site/src/component/AppHeader/AppHeader.tsx @@ -11,6 +11,8 @@ import './AppHeader.scss'; import { useAppDispatch, useAppSelector } from '../../store/hooks'; import { setSidebarStatus } from '../../store/slices/uiSlice'; +import Profile from './Profile'; +import { isDesktop, isMobile } from 'react-device-detect'; const AppHeader: FC = () => { const dispatch = useAppDispatch(); @@ -43,9 +45,11 @@ const AppHeader: FC = () => {
{/* Hamburger Menu */} -
- -
+ {isMobile && ( + + )} {/* Toggle Course and Professor */} {(coursesActive || professorsActive) && ( @@ -82,7 +86,7 @@ const AppHeader: FC = () => {
{/* Week */} -
+
{

{week}

+ {isDesktop && }
diff --git a/site/src/component/AppHeader/Profile.scss b/site/src/component/AppHeader/Profile.scss new file mode 100644 index 00000000..c5dca006 --- /dev/null +++ b/site/src/component/AppHeader/Profile.scss @@ -0,0 +1,127 @@ +.navbar-profile { + height: 70%; +} + +.navbar-profile-pic { + height: 100%; + border-radius: 100%; + + &:hover { + cursor: pointer; + } +} + +#profile-popover { + font-size: 1rem; + padding: 1em; + background-color: var(--overlay1); + border-radius: var(--border-radius); + border: 1px solid var(--background); + width: 350px; + max-width: 100vw; + + .arrow::before { + border-bottom-color: var(--background); + } + + h1 { + font-size: 1.5em; + overflow: hidden; + text-overflow: ellipsis; + } + + h2 { + font-size: 1em; + color: var(--text-light); + overflow: hidden; + text-overflow: ellipsis; + } +} + +.profile-popover__header { + display: flex; + min-height: 6vh; + margin-bottom: 0.75em; + + span { + white-space: nowrap; + overflow: hidden; + } + + img { + margin-right: 0.75em; + height: 6vh; + border-radius: 100%; + } + + button { + border: none; + background: none; + font-size: 1.5em; + line-height: 1em; + padding: 0 0.5em; + border-radius: var(--border-radius); + } + + button:hover { + background-color: var(--overlay2); + } +} + +.profile-popover__links { + ul { + list-style: none; + padding: 0; + margin: 0; + } + + li div { + display: inline; + } + + a { + color: var(--text); + } + + button { + border: none; + background: none; + width: 100%; + } + + .divider-before::before { + display: block; + content: ''; + width: 100%; + height: 1px; + background-color: var(--overlay2); + margin: 0.25em 0; + } +} + +.profile-popover__link, +.theme-popover__link { + display: flex; + gap: 10px; + padding: 10px; + border-radius: var(--border-radius); + + &:hover { + background-color: var(--overlay2); + } +} + +.profile-popover__link.active, +.theme-popover__link.active { + color: var(--peterportal-primary-color-1); +} + +i.login-icon { + margin-right: 0.75em; +} + +.theme-button:hover, +.theme-popover__link:hover { + cursor: pointer; + text-decoration: underline; +} diff --git a/site/src/component/AppHeader/Profile.tsx b/site/src/component/AppHeader/Profile.tsx new file mode 100644 index 00000000..5030a7f8 --- /dev/null +++ b/site/src/component/AppHeader/Profile.tsx @@ -0,0 +1,164 @@ +import { useCookies } from 'react-cookie'; +import { useContext, useEffect, useState } from 'react'; +import ThemeContext from '../../style/theme-context'; +import { Button, OverlayTrigger, Popover } from 'react-bootstrap'; +import { Icon } from 'semantic-ui-react'; +import './Profile.scss'; +import { NavLink } from 'react-router-dom'; +import { ArrowLeftCircleFill } from 'react-bootstrap-icons'; + +type ProfileMenuTab = 'default' | 'theme'; + +const Profile = () => { + const { darkMode, setTheme, usingSystemTheme } = useContext(ThemeContext); + const [cookies] = useCookies(['user']); + const [show, setShow] = useState(false); + const [tab, setTab] = useState('default'); + + const { name, email, picture }: { name: string; email: string; picture: string } = cookies?.user ?? {}; + const isLoggedIn: boolean = cookies.user; + + useEffect(() => { + if (!show) { + setTimeout(() => setTab('default'), 150); // popover transition time is 150ms + } + }, [show]); + + const DefaultTab = ( + <> +
+ {name} + +

{name}

+

{email}

+
+
+
+
    +
  • + 'profile-popover__link' + (isActive ? ' active' : '')} + to="/reviews" + onClick={() => setShow(false)} + > +
    + +
    + Your Reviews +
    +
  • +
  • + +
  • +
  • + +
    + +
    + Log Out +
    +
  • +
+
+ + ); + + const ThemeTab = ( + <> +
+ +
+
+
    +
  • + +
  • +
  • + +
  • +
  • + +
  • +
+
+ + ); + + const ProfilePopover = ( + + {tab === 'default' && DefaultTab} + {tab === 'theme' && ThemeTab} + + ); + + return ( + <> + {isLoggedIn ? ( +
+ + {name} + +
+ ) : ( + + + + )} + + ); +}; + +export default Profile; diff --git a/site/src/component/SideBar/SideBar.tsx b/site/src/component/SideBar/SideBar.tsx index 16bcc5b5..65cf9898 100644 --- a/site/src/component/SideBar/SideBar.tsx +++ b/site/src/component/SideBar/SideBar.tsx @@ -6,7 +6,7 @@ import { useCookies } from 'react-cookie'; import './Sidebar.scss'; import defaultAvatarLight from '../../asset/default-avatar.png'; import defaultAvatarDark from '../../asset/default-avatar-dark.png'; -import { Button } from 'react-bootstrap'; +import { Button, Dropdown } from 'react-bootstrap'; import { useAppSelector, useAppDispatch } from '../..//store/hooks'; import { setSidebarStatus } from '../../store/slices/uiSlice'; @@ -20,7 +20,7 @@ const SideBar = () => { const [name, setName] = useState(''); const [picture, setPicture] = useState(''); const [isAdmin, setIsAdmin] = useState(false); - const { darkMode, setTheme } = useContext(ThemeContext); + const { darkMode, setTheme, usingSystemTheme } = useContext(ThemeContext); const defaultAvatar = darkMode ? defaultAvatarDark : defaultAvatarLight; const isLoggedIn = cookies.user !== undefined; @@ -29,20 +29,20 @@ const SideBar = () => { admin: boolean; } - const checkAdmin = async () => { - const res: AxiosResponse = await axios.get('/api/users/isAdmin'); - const admin = res.data.admin; - setIsAdmin(admin); - }; - useEffect(() => { // if the user is logged in if (isLoggedIn) { setName(cookies.user.name); setPicture(cookies.user.picture); + // useEffect's function is not allowed to be async, create async checkAdmin function within + const checkAdmin = async () => { + const res: AxiosResponse = await axios.get('/api/users/isAdmin'); + const admin = res.data.admin; + setIsAdmin(admin); + }; + checkAdmin(); } - checkAdmin(); - }); + }, [cookies.user, isLoggedIn]); const closeSidebar = () => dispatch(setSidebarStatus(false)); @@ -75,29 +75,55 @@ const SideBar = () => { Peter's Roadmap - {isLoggedIn && ( -
  • - (isActive ? 'sidebar-active' : '')} - onClick={closeSidebar} - > -
    - -
    - Reviews -
    -
  • - )} {showSidebar && ( -
  • - setTheme(darkMode ? 'light' : 'dark')}> -
    - + <> + {isLoggedIn && ( +
  • + (isActive ? 'sidebar-active' : '')} + onClick={closeSidebar} + > +
    + +
    + Reviews +
    +
  • + )} +
  • +
    +
    + +
    + + + Theme + + + setTheme('light')} + > + Light + + setTheme('dark')} + > + Dark + + setTheme('system')} + > + System + + +
    - Toggle Dark Mode -
    -
  • + + )} {isAdmin && ( <> @@ -137,9 +163,9 @@ const SideBar = () => { return (
    {/* Close Button */} -
    - -
    + {/* Profile Icon and Name */}
    diff --git a/site/src/component/SideBar/Sidebar.scss b/site/src/component/SideBar/Sidebar.scss index e2740d66..22348786 100644 --- a/site/src/component/SideBar/Sidebar.scss +++ b/site/src/component/SideBar/Sidebar.scss @@ -27,15 +27,10 @@ } .sidebar-close { - padding: 2vw; - width: 100%; - - .sidebar-close-icon { - width: 3vh; - height: 3vh; - float: right; - cursor: pointer; - } + padding: 1em; + margin-left: auto; + border: none; + background: none; } .sidebar-profile { @@ -60,10 +55,11 @@ .sidebar-links { min-width: 60%; - margin-bottom: 20vh; + margin-bottom: 2em; ul li { - a { + a, + .theme-button { color: var(--text); display: flex; grid-gap: 10px; @@ -77,7 +73,7 @@ } .sidebar-login { - margin-bottom: 5vh; + margin-bottom: 20vh; .sidebar-login-icon { margin-right: 0.4vw; @@ -85,6 +81,28 @@ } } +.theme-button .dropdown { + width: 100%; + + button { + background-color: transparent; + padding: 0; + width: 100%; + text-align: left; + border: none; + } + + .dropdown-item.active { + background-color: transparent; + color: var(--peterportal-primary-color-1); + } +} + +.theme-button .show.dropdown button { + outline: none; + background-color: transparent; +} + .sidebar.mini { position: static; width: 5vw; @@ -130,16 +148,8 @@ .sidebar { width: 100vw; z-index: 400; - display: flex; - flex-direction: column; - justify-content: space-between; - align-items: center; - - .sidebar-login { - margin-top: -20vh; - margin-bottom: 35vh; - } } + .sidebar.mini { display: none; }