Loading portal-gui/src/app/components/TopBar/TopBar.tsx +76 −35 Original line number Diff line number Diff line "use client"; import React, { useState } from "react"; import React, { useRef, useState } from "react"; import styles from "./topbar.module.scss"; import { Button, Nav, Toggle } from "rsuite"; import { Button, Nav } from "rsuite"; import Link from "next/link"; import { useRouter } from "next/navigation"; import buttons from "../../styles/buttons.module.scss"; import { logoIcon, userIcon } from "@/app/utils/icons"; import { logoIcon, menuCloseIcon, menuIcon, userIcon } from "@/app/utils/icons"; import { navLinks } from "@/app/utils/constants"; import { formatName } from "@/app/utils/helpers"; import { usePathname } from "next/navigation"; import useOnClickOutside from "@/app/hooks/useOnClickOutside"; const TopBar = () => { const route = useRouter(); const router = useRouter(); const [isMenuOpen, setMenuOpen] = useState(false); const pathname = usePathname(); const MobileMenuRef: any = useRef(null); const user = { name: "Chnarakis Panagiotis", role: "Developer" }; // Example user object // const user = null; // No user logged in const toggleMenu = () => setMenuOpen((prev) => !prev); useOnClickOutside(MobileMenuRef, () => { setMenuOpen(false); }); const renderNavLinks = () => navLinks.map((link) => ( <Nav.Item key={link.id} as={Link} href={link.path} eventKey={link.path} active={pathname === link.path} > {link.name} </Nav.Item> )); return ( <div className={styles.container}> <header className={styles.header}> <Link href={"/"} className={styles.logo}> {/* LOGO */} <Link href="/" className={styles.logo}> {logoIcon} </Link> <Nav defaultActiveKey="Api Cataloque" className={styles.navLinks}> {navLinks.map((link) => ( <Nav.Item key={link.id} eventKey={link.name} href={link.path}> {link.name} </Nav.Item> ))} {/* DESKTOP NAV */} <Nav activeKey={pathname} className={styles.navLinks}> {renderNavLinks()} </Nav> {/* <Button className={buttons.primary} onClick={() => route.push("/login")} > Login </Button> */} {/* MOBILE MENU BUTTON */} <div className={styles.mobileContainer}> {/* USER MENU */} {user && ( <Nav className={styles.userBtn}> <Nav.Menu icon={userIcon} title={formatName(user?.name)} title={ <span className={styles.username}> {formatName(user.name)} </span> } placement="bottomEnd" > <Nav.Item href="/profile" as="a"> <Nav.Item as={Link} href="/profile"> Profile </Nav.Item> <Nav.Item href="/settings" as="a"> <Nav.Item as={Link} href="/settings"> Settings </Nav.Item> <Nav.Item>Log Out</Nav.Item> <Nav.Item onClick={() => router.push("/logout")}> Log Out </Nav.Item> </Nav.Menu> </Nav> )} <Button ref={MobileMenuRef} className={styles.menuBtn} onClick={toggleMenu} > {isMenuOpen ? menuCloseIcon : menuIcon} </Button> </div> {/* MOBILE MENU */} {isMenuOpen && ( <div className={styles.mobileMenuContainer}> <Nav activeKey={pathname} className={styles.mobileNavLinks}> {renderNavLinks()} </Nav> </div> )} </header> </div> ); Loading portal-gui/src/app/components/TopBar/topbar.module.scss +154 −27 Original line number Diff line number Diff line Loading @@ -22,10 +22,6 @@ color: var(--text-color); gap: 0.4rem; svg { transform: scale(1.05); } } header { Loading @@ -37,7 +33,116 @@ height: 100%; } .menuBtn { border-radius: 10px; background: var(--main-gradient); display: flex; align-items: center; width: 50px; height: 50px; justify-content: center; flex-direction: column; transition: all 0.5s; svg { fill: #fff; } &:hover { background: linear-gradient(360deg, #FDB913 0%, #F15A22 100%); } } .mobileMenuContainer, .menuBtn { display: none; } .mobileMenuContainer { position: absolute; border-radius: 10px; top: 85px; right: 30px; height: auto; background: var(--background-color); backdrop-filter: blur(5px); display: flex; align-items: center; padding: 1rem 2rem; font-size: 16px; justify-content: center; flex-direction: column; box-shadow: 4px 4px 20px rgba(0, 0, 0, 0.1); border-radius: 10px; transition: all 0.5s; border: none !important; .mobileNavLinks { display: flex; flex-direction: column; width: 100%; gap: 1rem; :global(.rs-nav-item) { &::after { display: none !important; } } :global(.rs-nav-item[aria-selected="true"]:hover) { color: var(--blue-color) !important; cursor: default; } :global(.rs-nav-item[aria-selected="true"]) { color: var(--blue-color) !important; cursor: default; } } } .mobileContainer { display: flex; align-items: center; gap: 0.5rem; } .mobileUserBtn { :global(.rs-dropdown) { background: var(--background-color) !important; box-shadow: 4px 4px 20px rgba(0, 0, 0, 0.1); border-radius: 10px; &:hover { background: var(--rs-navs-bg-hover) !important; } .rs-dropdown-toggle .rs-nav-item { width: max-content; height: max-content; padding: 0.5rem 0.7rem !important; } } svg { margin: 0; } } .mobileUserBtn { display: none; } .navLinks { flex-wrap: wrap; display: flex; align-items: center; :global(.rs-nav-item) { position: relative; padding: 0 1.5rem 0 1rem; Loading Loading @@ -67,24 +172,17 @@ } :global(.rs-nav-item[aria-selected="true"]:hover) { color: var(--blue-text) !important; color: var(--blue-color) !important; cursor: default; } } .mobileMenuIcon { display: none; cursor: pointer; svg { width: 32px; height: 32px; :global(.rs-nav-item[aria-selected="true"]) { color: var(--blue-color) !important; cursor: default; } } .userBtn { :global(.rs-dropdown) { background: var(--background-color) !important; Loading @@ -109,23 +207,31 @@ } .username { @media (max-width: 600px) { display: none; } } //------------------------- Responsive -------------------------// @media screen and (max-width: 769px) { @media screen and (max-width: 1025px) { padding: 1rem 2rem; .mobileMenuIcon { display: block; .navLinks { display: none !important; } .navLinks { display: none; .mobileMenuContainer, .menuBtn { display: flex !important; } } } Loading @@ -134,13 +240,34 @@ @media screen and (max-width:425px) { @media screen and (max-width:768px) { .userBtn { svg { margin: 0; } } } @media screen and (max-width:426px) { .logo { gap: 0.3rem; svg { transform: scale(0.90) !important; } } .mobileContainer{ transform: scale(0.90) !important; } } @media screen and (max-width:380px) { .logo { width:130px; svg { transform: scale(0.95) !important; transform: scale(0.80) !important; } } Loading portal-gui/src/app/hooks/useOnClickOutside.tsx 0 → 100644 +20 −0 Original line number Diff line number Diff line import React, { useEffect } from 'react'; function useOnClickOutside(ref: any, handler: any) { useEffect(() => { const listener = (event: any) => { if (!ref.current || ref.current.contains(event.target)) { return; } handler(event); }; document.addEventListener('mousedown', listener); document.addEventListener('touchstart', listener); return () => { document.removeEventListener('mousedown', listener); document.removeEventListener('touchstart', listener); }; }, [ref, handler]); } export default useOnClickOutside; No newline at end of file portal-gui/src/app/styles/globals.css +3 −2 Original line number Diff line number Diff line Loading @@ -96,6 +96,7 @@ body { -moz-osx-font-smoothing: grayscale; &::-webkit-scrollbar { width: 10px; height: 10px; } &::-webkit-scrollbar-thumb { Loading @@ -105,8 +106,8 @@ body { &::-webkit-scrollbar-track { background: var(--background-color); box-shadow: inset -4px -4px 8px rgba(16, 22, 53, 0.8), inset 4px 4px 8px #070b2c; box-shadow: inset -4px -4px 8px rgba(19, 38, 134, 0.8), inset 4px 4px 8px #020c61; } } Loading portal-gui/src/app/utils/icons.js +104 −0 Original line number Diff line number Diff line Loading @@ -112,3 +112,107 @@ export const userIcon = </linearGradient> </defs> </svg> export const menuIcon = ( <svg xmlns="http://www.w3.org/2000/svg" width="32" height="28" viewBox="0 0 32 28" > <circle cx="2.7617" cy="2.76173" r="2.76173" transform="rotate(90 2.7617 2.76173)" /> <circle cx="15.7642" cy="2.76173" r="2.76173" transform="rotate(90 15.7642 2.76173)" /> <circle cx="28.7667" cy="2.76173" r="2.76173" transform="rotate(90 28.7667 2.76173)" /> <circle cx="2.7617" cy="14" r="2.76173" transform="rotate(90 2.7617 14)" /> <circle cx="2.7617" cy="25.2383" r="2.76173" transform="rotate(90 2.7617 25.2383)" /> <circle cx="15.7642" cy="14" r="2.76173" transform="rotate(90 15.7642 14)" /> <circle cx="15.7642" cy="25.2383" r="2.76173" transform="rotate(90 15.7642 25.2383)" /> <circle cx="28.7667" cy="14" r="2.76173" transform="rotate(90 28.7667 14)" /> <circle cx="28.7667" cy="25.2383" r="2.76173" transform="rotate(90 28.7667 25.2383)" /> </svg> ); export const menuCloseIcon = ( <svg xmlns="http://www.w3.org/2000/svg" width="31" height="31" viewBox="0 0 31 31" fill="none" > <circle cx="4.00002" cy="4" r="2.76173" transform="rotate(135 4.00002 4)" /> <circle cx="10" cy="10" r="2.76173" transform="rotate(135 10 10)" /> <circle cx="15.4621" cy="16" r="2.76173" transform="rotate(135 15.4621 16)" /> <circle cx="21" cy="21" r="2.76173" transform="rotate(135 21 21)" /> <circle cx="27" cy="27" r="2.76173" transform="rotate(135 27 27)" /> <circle cx="3.9057" cy="26.9057" r="2.76173" transform="rotate(45 3.9057 26.9057)" /> <circle cx="9.9057" cy="20.9057" r="2.76173" transform="rotate(45 9.9057 20.9057)" /> <circle cx="20.9057" cy="9.90568" r="2.76173" transform="rotate(45 20.9057 9.90568)" /> <circle cx="26.9057" cy="3.90568" r="2.76173" transform="rotate(45 26.9057 3.90568)" /> </svg> ); Loading
portal-gui/src/app/components/TopBar/TopBar.tsx +76 −35 Original line number Diff line number Diff line "use client"; import React, { useState } from "react"; import React, { useRef, useState } from "react"; import styles from "./topbar.module.scss"; import { Button, Nav, Toggle } from "rsuite"; import { Button, Nav } from "rsuite"; import Link from "next/link"; import { useRouter } from "next/navigation"; import buttons from "../../styles/buttons.module.scss"; import { logoIcon, userIcon } from "@/app/utils/icons"; import { logoIcon, menuCloseIcon, menuIcon, userIcon } from "@/app/utils/icons"; import { navLinks } from "@/app/utils/constants"; import { formatName } from "@/app/utils/helpers"; import { usePathname } from "next/navigation"; import useOnClickOutside from "@/app/hooks/useOnClickOutside"; const TopBar = () => { const route = useRouter(); const router = useRouter(); const [isMenuOpen, setMenuOpen] = useState(false); const pathname = usePathname(); const MobileMenuRef: any = useRef(null); const user = { name: "Chnarakis Panagiotis", role: "Developer" }; // Example user object // const user = null; // No user logged in const toggleMenu = () => setMenuOpen((prev) => !prev); useOnClickOutside(MobileMenuRef, () => { setMenuOpen(false); }); const renderNavLinks = () => navLinks.map((link) => ( <Nav.Item key={link.id} as={Link} href={link.path} eventKey={link.path} active={pathname === link.path} > {link.name} </Nav.Item> )); return ( <div className={styles.container}> <header className={styles.header}> <Link href={"/"} className={styles.logo}> {/* LOGO */} <Link href="/" className={styles.logo}> {logoIcon} </Link> <Nav defaultActiveKey="Api Cataloque" className={styles.navLinks}> {navLinks.map((link) => ( <Nav.Item key={link.id} eventKey={link.name} href={link.path}> {link.name} </Nav.Item> ))} {/* DESKTOP NAV */} <Nav activeKey={pathname} className={styles.navLinks}> {renderNavLinks()} </Nav> {/* <Button className={buttons.primary} onClick={() => route.push("/login")} > Login </Button> */} {/* MOBILE MENU BUTTON */} <div className={styles.mobileContainer}> {/* USER MENU */} {user && ( <Nav className={styles.userBtn}> <Nav.Menu icon={userIcon} title={formatName(user?.name)} title={ <span className={styles.username}> {formatName(user.name)} </span> } placement="bottomEnd" > <Nav.Item href="/profile" as="a"> <Nav.Item as={Link} href="/profile"> Profile </Nav.Item> <Nav.Item href="/settings" as="a"> <Nav.Item as={Link} href="/settings"> Settings </Nav.Item> <Nav.Item>Log Out</Nav.Item> <Nav.Item onClick={() => router.push("/logout")}> Log Out </Nav.Item> </Nav.Menu> </Nav> )} <Button ref={MobileMenuRef} className={styles.menuBtn} onClick={toggleMenu} > {isMenuOpen ? menuCloseIcon : menuIcon} </Button> </div> {/* MOBILE MENU */} {isMenuOpen && ( <div className={styles.mobileMenuContainer}> <Nav activeKey={pathname} className={styles.mobileNavLinks}> {renderNavLinks()} </Nav> </div> )} </header> </div> ); Loading
portal-gui/src/app/components/TopBar/topbar.module.scss +154 −27 Original line number Diff line number Diff line Loading @@ -22,10 +22,6 @@ color: var(--text-color); gap: 0.4rem; svg { transform: scale(1.05); } } header { Loading @@ -37,7 +33,116 @@ height: 100%; } .menuBtn { border-radius: 10px; background: var(--main-gradient); display: flex; align-items: center; width: 50px; height: 50px; justify-content: center; flex-direction: column; transition: all 0.5s; svg { fill: #fff; } &:hover { background: linear-gradient(360deg, #FDB913 0%, #F15A22 100%); } } .mobileMenuContainer, .menuBtn { display: none; } .mobileMenuContainer { position: absolute; border-radius: 10px; top: 85px; right: 30px; height: auto; background: var(--background-color); backdrop-filter: blur(5px); display: flex; align-items: center; padding: 1rem 2rem; font-size: 16px; justify-content: center; flex-direction: column; box-shadow: 4px 4px 20px rgba(0, 0, 0, 0.1); border-radius: 10px; transition: all 0.5s; border: none !important; .mobileNavLinks { display: flex; flex-direction: column; width: 100%; gap: 1rem; :global(.rs-nav-item) { &::after { display: none !important; } } :global(.rs-nav-item[aria-selected="true"]:hover) { color: var(--blue-color) !important; cursor: default; } :global(.rs-nav-item[aria-selected="true"]) { color: var(--blue-color) !important; cursor: default; } } } .mobileContainer { display: flex; align-items: center; gap: 0.5rem; } .mobileUserBtn { :global(.rs-dropdown) { background: var(--background-color) !important; box-shadow: 4px 4px 20px rgba(0, 0, 0, 0.1); border-radius: 10px; &:hover { background: var(--rs-navs-bg-hover) !important; } .rs-dropdown-toggle .rs-nav-item { width: max-content; height: max-content; padding: 0.5rem 0.7rem !important; } } svg { margin: 0; } } .mobileUserBtn { display: none; } .navLinks { flex-wrap: wrap; display: flex; align-items: center; :global(.rs-nav-item) { position: relative; padding: 0 1.5rem 0 1rem; Loading Loading @@ -67,24 +172,17 @@ } :global(.rs-nav-item[aria-selected="true"]:hover) { color: var(--blue-text) !important; color: var(--blue-color) !important; cursor: default; } } .mobileMenuIcon { display: none; cursor: pointer; svg { width: 32px; height: 32px; :global(.rs-nav-item[aria-selected="true"]) { color: var(--blue-color) !important; cursor: default; } } .userBtn { :global(.rs-dropdown) { background: var(--background-color) !important; Loading @@ -109,23 +207,31 @@ } .username { @media (max-width: 600px) { display: none; } } //------------------------- Responsive -------------------------// @media screen and (max-width: 769px) { @media screen and (max-width: 1025px) { padding: 1rem 2rem; .mobileMenuIcon { display: block; .navLinks { display: none !important; } .navLinks { display: none; .mobileMenuContainer, .menuBtn { display: flex !important; } } } Loading @@ -134,13 +240,34 @@ @media screen and (max-width:425px) { @media screen and (max-width:768px) { .userBtn { svg { margin: 0; } } } @media screen and (max-width:426px) { .logo { gap: 0.3rem; svg { transform: scale(0.90) !important; } } .mobileContainer{ transform: scale(0.90) !important; } } @media screen and (max-width:380px) { .logo { width:130px; svg { transform: scale(0.95) !important; transform: scale(0.80) !important; } } Loading
portal-gui/src/app/hooks/useOnClickOutside.tsx 0 → 100644 +20 −0 Original line number Diff line number Diff line import React, { useEffect } from 'react'; function useOnClickOutside(ref: any, handler: any) { useEffect(() => { const listener = (event: any) => { if (!ref.current || ref.current.contains(event.target)) { return; } handler(event); }; document.addEventListener('mousedown', listener); document.addEventListener('touchstart', listener); return () => { document.removeEventListener('mousedown', listener); document.removeEventListener('touchstart', listener); }; }, [ref, handler]); } export default useOnClickOutside; No newline at end of file
portal-gui/src/app/styles/globals.css +3 −2 Original line number Diff line number Diff line Loading @@ -96,6 +96,7 @@ body { -moz-osx-font-smoothing: grayscale; &::-webkit-scrollbar { width: 10px; height: 10px; } &::-webkit-scrollbar-thumb { Loading @@ -105,8 +106,8 @@ body { &::-webkit-scrollbar-track { background: var(--background-color); box-shadow: inset -4px -4px 8px rgba(16, 22, 53, 0.8), inset 4px 4px 8px #070b2c; box-shadow: inset -4px -4px 8px rgba(19, 38, 134, 0.8), inset 4px 4px 8px #020c61; } } Loading
portal-gui/src/app/utils/icons.js +104 −0 Original line number Diff line number Diff line Loading @@ -112,3 +112,107 @@ export const userIcon = </linearGradient> </defs> </svg> export const menuIcon = ( <svg xmlns="http://www.w3.org/2000/svg" width="32" height="28" viewBox="0 0 32 28" > <circle cx="2.7617" cy="2.76173" r="2.76173" transform="rotate(90 2.7617 2.76173)" /> <circle cx="15.7642" cy="2.76173" r="2.76173" transform="rotate(90 15.7642 2.76173)" /> <circle cx="28.7667" cy="2.76173" r="2.76173" transform="rotate(90 28.7667 2.76173)" /> <circle cx="2.7617" cy="14" r="2.76173" transform="rotate(90 2.7617 14)" /> <circle cx="2.7617" cy="25.2383" r="2.76173" transform="rotate(90 2.7617 25.2383)" /> <circle cx="15.7642" cy="14" r="2.76173" transform="rotate(90 15.7642 14)" /> <circle cx="15.7642" cy="25.2383" r="2.76173" transform="rotate(90 15.7642 25.2383)" /> <circle cx="28.7667" cy="14" r="2.76173" transform="rotate(90 28.7667 14)" /> <circle cx="28.7667" cy="25.2383" r="2.76173" transform="rotate(90 28.7667 25.2383)" /> </svg> ); export const menuCloseIcon = ( <svg xmlns="http://www.w3.org/2000/svg" width="31" height="31" viewBox="0 0 31 31" fill="none" > <circle cx="4.00002" cy="4" r="2.76173" transform="rotate(135 4.00002 4)" /> <circle cx="10" cy="10" r="2.76173" transform="rotate(135 10 10)" /> <circle cx="15.4621" cy="16" r="2.76173" transform="rotate(135 15.4621 16)" /> <circle cx="21" cy="21" r="2.76173" transform="rotate(135 21 21)" /> <circle cx="27" cy="27" r="2.76173" transform="rotate(135 27 27)" /> <circle cx="3.9057" cy="26.9057" r="2.76173" transform="rotate(45 3.9057 26.9057)" /> <circle cx="9.9057" cy="20.9057" r="2.76173" transform="rotate(45 9.9057 20.9057)" /> <circle cx="20.9057" cy="9.90568" r="2.76173" transform="rotate(45 20.9057 9.90568)" /> <circle cx="26.9057" cy="3.90568" r="2.76173" transform="rotate(45 26.9057 3.90568)" /> </svg> );