This is an automated email from the ASF dual-hosted git repository. dimuthuupe pushed a commit to branch cybershuttle-staging in repository https://gitbox.apache.org/repos/asf/airavata.git
commit ab35f657967129881b94e569d29ff481609fc0f0 Author: ganning127 <[email protected]> AuthorDate: Fri Apr 4 13:01:54 2025 -0400 refactoring UI and responsive changes --- modules/research-framework/portal/src/App.tsx | 21 ++- .../portal/src/components/NavBar.tsx | 90 ------------ .../src/components/datasets/DatasetDetails.tsx | 2 +- .../portal/src/components/datasets/index.tsx | 3 - .../home/NotebooksAndRepositoriesSection.tsx | 49 ------- .../portal/src/components/home/ResourceCard.tsx | 1 - .../portal/src/components/home/index.tsx | 3 - .../portal/src/components/models/ModelCard.tsx | 34 ----- .../portal/src/components/models/ModelDetails.tsx | 96 ------------- .../portal/src/components/models/index.tsx | 3 - .../src/components/notebooks/NotebookCard.tsx | 64 --------- .../portal/src/components/notebooks/index.tsx | 3 - .../repositories/RepositorySpecificDetails.tsx | 152 +++++++++++---------- .../portal/src/components/repositories/index.tsx | 3 - .../src/components/resources/ResourceDetails.tsx | 4 +- .../portal/src/layouts/NavBar.tsx | 142 +++++++++++++++++++ .../portal/src/layouts/NavBarFooterLayout.tsx | 14 ++ 17 files changed, 259 insertions(+), 425 deletions(-) diff --git a/modules/research-framework/portal/src/App.tsx b/modules/research-framework/portal/src/App.tsx index 42206ac062..ebeb14af3b 100644 --- a/modules/research-framework/portal/src/App.tsx +++ b/modules/research-framework/portal/src/App.tsx @@ -11,6 +11,7 @@ import ProtectedComponent from "./components/auth/ProtectedComponent"; import { useAuth } from "react-oidc-context"; import { useEffect } from "react"; import { setUserProvider } from "./lib/api"; +import NavBarFooterLayout from "./layouts/NavBarFooterLayout"; function App() { const colorMode = useColorMode(); if (colorMode.colorMode === "dark") { @@ -28,7 +29,7 @@ function App() { return ( <> <BrowserRouter> - <Routes> + {/* <Routes> <Route path="/" element={<Login />} /> <Route @@ -53,12 +54,28 @@ function App() { element={<ProtectedComponent Component={Models} />} /> - {/* Dynamic Route for specific resource details */} <Route path=":type/:id" element={<ProtectedComponent Component={ResourceDetails} />} /> </Route> + </Routes> */} + + <Routes> + {/* Public Route */} + <Route path="/" element={<Login />} /> + + {/* Protected Routes with Layout */} + <Route + element={<ProtectedComponent Component={NavBarFooterLayout} />} + > + <Route path="/projects" element={<Home />} /> + <Route path="/resources/notebooks" element={<Notebooks />} /> + <Route path="/resources/datasets" element={<Datasets />} /> + <Route path="/resources/repositories" element={<Repositories />} /> + <Route path="/resources/models" element={<Models />} /> + <Route path="/resources/:type/:id" element={<ResourceDetails />} /> + </Route> </Routes> </BrowserRouter> </> diff --git a/modules/research-framework/portal/src/components/NavBar.tsx b/modules/research-framework/portal/src/components/NavBar.tsx deleted file mode 100644 index 6cc402f985..0000000000 --- a/modules/research-framework/portal/src/components/NavBar.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import { - Text, - Flex, - Spacer, - Image, - HStack, - Avatar, - Box, -} from "@chakra-ui/react"; -import ApacheAiravataLogo from "../assets/airavata-logo.png"; -import { Link } from "react-router"; -import { useAuth } from "react-oidc-context"; - -const NAV_CONTENT = [ - { - title: "Projects", - url: "/projects", - }, - { - title: "Datasets", - url: "/resources/datasets", - }, - { - title: "Repositories", - url: "/resources/repositories", - }, - { - title: "Notebooks", - url: "/resources/notebooks", - }, - { - title: "Models", - url: "/resources/models", - }, -]; - -const NavBar = () => { - const auth = useAuth(); - - console.log(auth); - return ( - <Flex - as="nav" - align="center" - p={4} - boxShadow="sm" - position="sticky" - top="0" - zIndex="1000" - bg="white" // Ensure background is not transparent - > - {/* Logo */} - <Link to="/projects"> - <Image src={ApacheAiravataLogo} alt="Logo" boxSize="30px" /> - </Link> - - {/* Navigation Links */} - <Flex ml={4} gap={6}> - {NAV_CONTENT.map((item) => ( - <Link key={item.title} to={item.url} color="gray.700"> - <Text - _hover={{ - color: "blue.400", - textDecoration: "underline", - }} - > - {item.title} - </Text> - </Link> - ))} - </Flex> - - <Spacer /> - - {/* Search Bar */} - <HStack> - <Avatar.Root variant="subtle"> - <Avatar.Fallback name={auth.user?.profile.name} /> - </Avatar.Root> - <Box> - <Text>{auth.user?.profile.name}</Text> - <Text fontSize="sm" color="gray.500"> - {auth.user?.profile.email} - </Text> - </Box> - </HStack> - </Flex> - ); -}; -export default NavBar; diff --git a/modules/research-framework/portal/src/components/datasets/DatasetDetails.tsx b/modules/research-framework/portal/src/components/datasets/DatasetDetails.tsx index 79719752aa..975639fcd6 100644 --- a/modules/research-framework/portal/src/components/datasets/DatasetDetails.tsx +++ b/modules/research-framework/portal/src/components/datasets/DatasetDetails.tsx @@ -1,5 +1,5 @@ import { Metadata } from "../Metadata"; -import NavBar from "../NavBar"; +import NavBar from "../../layouts/NavBar"; // @ts-expect-error This is fine import { MOCK_DATASETS } from "../../data/MOCK_DATA"; import { DatasetType } from "@/interfaces/DatasetType"; diff --git a/modules/research-framework/portal/src/components/datasets/index.tsx b/modules/research-framework/portal/src/components/datasets/index.tsx index 690dd61152..d2b84b5b1b 100644 --- a/modules/research-framework/portal/src/components/datasets/index.tsx +++ b/modules/research-framework/portal/src/components/datasets/index.tsx @@ -1,4 +1,3 @@ -import NavBar from "../NavBar"; import { Container, HStack, Input, SimpleGrid } from "@chakra-ui/react"; import { PageHeader } from "../PageHeader"; import { LuSearch } from "react-icons/lu"; @@ -43,8 +42,6 @@ export const Datasets = () => { return ( <> - <NavBar /> - <Container maxW="container.lg" mt={8}> <HStack alignItems="flex-end" justify="space-between"> <PageHeader diff --git a/modules/research-framework/portal/src/components/home/NotebooksAndRepositoriesSection.tsx b/modules/research-framework/portal/src/components/home/NotebooksAndRepositoriesSection.tsx deleted file mode 100644 index 584d3e6b6e..0000000000 --- a/modules/research-framework/portal/src/components/home/NotebooksAndRepositoriesSection.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { Input } from "@chakra-ui/react"; -import { LuSearch } from "react-icons/lu"; -import { ResourceCard } from "./ResourceCard"; -import { InputGroup } from "../ui/input-group"; -import { GridContainer } from "../GridContainer"; -import { useEffect, useState } from "react"; -import api from "@/lib/api"; -import { Resource } from "@/interfaces/ResourceType"; - -const fetchNotebooksAndRepositories = async () => { - try { - const response = await api.get( - "/project-management/resources?type=NOTEBOOK&type=REPOSITORY" - ); - const data = response.data; - return data; - } catch (error) { - console.error("Error fetching:", error); - } -}; - -export const NotebooksAndRepositoriesSection = () => { - const [resources, setResources] = useState<Resource[]>([]); - - useEffect(() => { - async function init() { - const projects = await fetchNotebooksAndRepositories(); - setResources(projects.content); - } - - init(); - }, []); - - console.log("Resources:", resources); - - return ( - <> - <InputGroup mt={2} endElement={<LuSearch />} w="100%"> - <Input placeholder="Search" rounded="md" /> - </InputGroup> - - <GridContainer> - {resources.map((resource: Resource) => { - return <ResourceCard key={resource.id} resource={resource} />; - })} - </GridContainer> - </> - ); -}; diff --git a/modules/research-framework/portal/src/components/home/ResourceCard.tsx b/modules/research-framework/portal/src/components/home/ResourceCard.tsx index bceb4bdd92..a5da829aac 100644 --- a/modules/research-framework/portal/src/components/home/ResourceCard.tsx +++ b/modules/research-framework/portal/src/components/home/ResourceCard.tsx @@ -17,7 +17,6 @@ import { ModelCardButton } from "../models/ModelCardButton"; export const ResourceCard = ({ resource }: { resource: Resource }) => { const author = resource.authors[0]; - console.log("author" + author); const isValidImage = isValidImaage(resource.headerImage); diff --git a/modules/research-framework/portal/src/components/home/index.tsx b/modules/research-framework/portal/src/components/home/index.tsx index 3a932951f7..4d81bf39ed 100644 --- a/modules/research-framework/portal/src/components/home/index.tsx +++ b/modules/research-framework/portal/src/components/home/index.tsx @@ -1,6 +1,5 @@ import { Box, HStack, Container } from "@chakra-ui/react"; -import NavBar from "../NavBar"; import { PageHeader } from "../PageHeader"; import { AddRepositoryButton } from "./AddRepositoryButton"; import { AddZipButton } from "./AddZipButton"; @@ -12,8 +11,6 @@ import { ProjectsSection } from "./ProjectsSection"; const Home = () => { return ( <Box> - <NavBar /> - <Container maxW="container.xl" mt={8}> <HStack alignItems="flex-end" justify="space-between"> <PageHeader diff --git a/modules/research-framework/portal/src/components/models/ModelCard.tsx b/modules/research-framework/portal/src/components/models/ModelCard.tsx deleted file mode 100644 index 9fd0898e54..0000000000 --- a/modules/research-framework/portal/src/components/models/ModelCard.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { Badge, Box, Card, HStack, Heading } from "@chakra-ui/react"; -import { ModelType } from "@/interfaces/ModelType"; -import { Link } from "react-router"; -export const ModelCard = ({ model }: { model: ModelType }) => { - return ( - <Box> - <Card.Root - size="sm" - _hover={{ - bg: "gray.100", - cursor: "pointer", - }} - > - <Link to={`/models/${model.appModuleId}`}> - <Card.Header> - <HStack - justifyContent="space-between" - alignItems="flex-start" - flexWrap="wrap" - > - <Heading size="md"> {model.appModuleName}</Heading> - {model.appModuleVersion && ( - <Badge size="md" colorPalette="green"> - {model.appModuleVersion} - </Badge> - )} - </HStack> - </Card.Header> - <Card.Body color="fg.muted">{model.appModuleDescription}</Card.Body> - </Link> - </Card.Root> - </Box> - ); -}; diff --git a/modules/research-framework/portal/src/components/models/ModelDetails.tsx b/modules/research-framework/portal/src/components/models/ModelDetails.tsx deleted file mode 100644 index db3eff60ff..0000000000 --- a/modules/research-framework/portal/src/components/models/ModelDetails.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import { useParams } from "react-router"; -import NavBar from "../NavBar"; -// @ts-expect-error This is fine -import { MOCK_MODELS } from "../../data/MOCK_DATA"; -import { ModelType } from "@/interfaces/ModelType"; -import { useState, useEffect } from "react"; -import { - Badge, - Button, - Code, - Container, - HStack, - Heading, - Text, -} from "@chakra-ui/react"; - -async function getModel(id: string | undefined) { - return MOCK_MODELS.find((model: ModelType) => model.appModuleId === id); -} - -export const ModelDetails = () => { - const { id } = useParams(); - const [model, setModel] = useState<ModelType | null>(null); - const [copyClicked, setCopyClicked] = useState(false); - - useEffect(() => { - if (!id) return; - - async function getData() { - const n = await getModel(id); - setModel(n); - } - getData(); - }, [id]); - - if (!model) return null; - - const templateCode = `exp = md.AlphaFold2.initialize( - name=..., - input_seq=..., - max_template_date=..., - model_preset=..., - multimers_per_model=..., -) -exp.create_task() -exp.plan().launch()`; - - return ( - <> - <NavBar /> - - <Container maxW="breakpoint-md" p={4}> - <HStack - alignItems="center" - mt={4} - justifyContent="space-between" - flexWrap="wrap" - > - <Heading size="4xl">{model.appModuleName}</Heading> - {model.appModuleVersion && ( - <Badge size="md" colorPalette="green"> - {model.appModuleVersion} - </Badge> - )} - </HStack> - <Text color="fg.muted" mt={2}> - {model.appModuleDescription} - </Text> - - <Heading size="2xl" mt={4}> - Example - <Button - size="xs" - ml={2} - colorPalette="teal" - onClick={() => { - navigator.clipboard.writeText(templateCode); - setCopyClicked(true); - setTimeout(() => setCopyClicked(false), 2000); - }} - > - {copyClicked ? "Copied!" : "Copy"} - </Button> - </Heading> - - <Code - mt={2} - p={2} - display="block" - whiteSpace="pre" - children={templateCode} - /> - </Container> - </> - ); -}; diff --git a/modules/research-framework/portal/src/components/models/index.tsx b/modules/research-framework/portal/src/components/models/index.tsx index 2ae3cd27b2..dc1cdce827 100644 --- a/modules/research-framework/portal/src/components/models/index.tsx +++ b/modules/research-framework/portal/src/components/models/index.tsx @@ -1,4 +1,3 @@ -import NavBar from "../NavBar"; import { Container, HStack, Input, SimpleGrid } from "@chakra-ui/react"; import { PageHeader } from "../PageHeader"; import { LuSearch } from "react-icons/lu"; @@ -43,8 +42,6 @@ export const Models = () => { return ( <> - <NavBar /> - <Container maxW="container.lg" mt={8}> <HStack alignItems="flex-end" justify="space-between"> <PageHeader diff --git a/modules/research-framework/portal/src/components/notebooks/NotebookCard.tsx b/modules/research-framework/portal/src/components/notebooks/NotebookCard.tsx deleted file mode 100644 index d2bda42287..0000000000 --- a/modules/research-framework/portal/src/components/notebooks/NotebookCard.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import { - Box, - Image, - Icon, - Text, - Card, - HStack, - Avatar, - Button, -} from "@chakra-ui/react"; -import { FaPlay } from "react-icons/fa"; -import { Link } from "react-router"; - -export const NotebookCard = ({ notebook }: { notebook: any }) => { - return ( - <Box> - <Card.Root overflow="hidden"> - <Image - src={notebook.images.headerImage} - alt="Green double couch with wooden legs" - /> - <Link to={notebook.slug}> - <Card.Body - gap="2" - _hover={{ - bg: "gray.100", - }} - > - <Card.Title>{notebook.title}</Card.Title> - <HStack> - {notebook.tags.map((tag: string) => ( - <Text key={tag} bg="gray.200" p={1} rounded="md"> - {tag} - </Text> - ))} - </HStack> - <Card.Description>{notebook.description}.</Card.Description> - </Card.Body> - </Link> - - <Card.Footer justifyContent="space-between"> - <HStack mt={4}> - <Avatar.Root shape="full" size="xl"> - <Avatar.Fallback name={notebook.author.name} /> - <Avatar.Image src={notebook.author.avatar} /> - </Avatar.Root> - - <Box> - <Text fontWeight="bold">{notebook.author.name}</Text> - <Text color="gray.500">{notebook.author.role}</Text> - </Box> - </HStack> - - <Button> - Run - <Icon ml={1} size={"sm"}> - <FaPlay /> - </Icon> - </Button> - </Card.Footer> - </Card.Root> - </Box> - ); -}; diff --git a/modules/research-framework/portal/src/components/notebooks/index.tsx b/modules/research-framework/portal/src/components/notebooks/index.tsx index 7baa79aee7..ac0dd44507 100644 --- a/modules/research-framework/portal/src/components/notebooks/index.tsx +++ b/modules/research-framework/portal/src/components/notebooks/index.tsx @@ -1,5 +1,4 @@ import { Container, Input, SimpleGrid } from "@chakra-ui/react"; -import NavBar from "../NavBar"; import { PageHeader } from "../PageHeader"; import { InputGroup } from "../ui/input-group"; import { LuSearch } from "react-icons/lu"; @@ -40,8 +39,6 @@ const Notebooks = () => { return ( <> - <NavBar /> - <Container maxW="container.lg" mt={8}> <PageHeader title="Notebooks" diff --git a/modules/research-framework/portal/src/components/repositories/RepositorySpecificDetails.tsx b/modules/research-framework/portal/src/components/repositories/RepositorySpecificDetails.tsx index ba1f3f08a3..1bd94b9a53 100644 --- a/modules/research-framework/portal/src/components/repositories/RepositorySpecificDetails.tsx +++ b/modules/research-framework/portal/src/components/repositories/RepositorySpecificDetails.tsx @@ -11,6 +11,7 @@ import { Breadcrumb, } from "@chakra-ui/react"; import { Fragment, useEffect, useState } from "react"; +import { FaGithub } from "react-icons/fa"; import { FiFolder, FiFile } from "react-icons/fi"; interface FileTreeItem { @@ -22,11 +23,12 @@ interface FileTreeItem { } export const RepositorySpecificDetails = ({ - dataset, + repository, }: { - dataset: RepositoryResource; + repository: RepositoryResource; }) => { - const githubUrl = dataset.repositoryUrl; + console.log(repository); + const githubUrl = repository.repositoryUrl; const [fileTree, setFileTree] = useState<FileTreeItem[]>([]); const [fileTreeLoading, setFileTreeLoading] = useState(false); const [currentPath, setCurrentPath] = useState<string>(""); @@ -122,75 +124,85 @@ export const RepositorySpecificDetails = ({ if (error !== null) return null; return ( - <Box - bg="white" - p={4} - borderRadius="md" - shadow="md" - overflow="auto" - height="full" - > - <Button onClick={handleGoBack} mb={4}> - Back + <> + {/* @ts-expect-error This is fine */} + <Button size="sm" as="a" target="_blank" href={repository.repositoryUrl}> + <FaGithub /> + Open {repository.name} on GitHub </Button> - <Breadcrumb.Root mt={2}> - <Breadcrumb.List> - <Breadcrumb.Item> - <Breadcrumb.Link href="#">root</Breadcrumb.Link> - </Breadcrumb.Item> - {history.length > 0 && - currentPath - .split("/") - .filter(Boolean) // Remove empty strings - .map((path, index) => ( - <Fragment key={index}> - <Breadcrumb.Separator /> - <Breadcrumb.Item> - <Breadcrumb.Link href="#">{path}</Breadcrumb.Link> - </Breadcrumb.Item> - </Fragment> + <Box + mt={4} + bg="white" + p={4} + borderRadius="md" + shadow="md" + overflow="auto" + height="full" + > + {/* Open in GitHub button */} + <Button onClick={handleGoBack} mb={4}> + Back + </Button> + <Breadcrumb.Root mt={2}> + <Breadcrumb.List> + <Breadcrumb.Item> + <Breadcrumb.Link href="#">root</Breadcrumb.Link> + </Breadcrumb.Item> + + {history.length > 0 && + currentPath + .split("/") + .filter(Boolean) // Remove empty strings + .map((path, index) => ( + <Fragment key={index}> + <Breadcrumb.Separator /> + <Breadcrumb.Item> + <Breadcrumb.Link href="#">{path}</Breadcrumb.Link> + </Breadcrumb.Item> + </Fragment> + ))} + </Breadcrumb.List> + </Breadcrumb.Root>{" "} + {fileContent ? ( + <Box p={4} bg="gray.100" borderRadius="md"> + <Text whiteSpace="pre-wrap" fontSize="sm" fontFamily="monospace"> + {fileContent} + </Text> + </Box> + ) : ( + <ListRoot> + {Array.isArray(fileTree) && + fileTree.map((file) => ( + <ListItem + key={file.sha} + display="flex" + alignItems="center" + p={2} + borderRadius="md" + _hover={{ bg: "gray.100", cursor: "pointer" }} + onClick={() => + file.type === "dir" + ? handleFolderClick(file.path) + : handleFileClick(file.path) + } + > + <Icon + as={file.type === "dir" ? FiFolder : FiFile} + color={file.type === "dir" ? "blue.500" : "gray.500"} + mr={2} + /> + <p>{file.name}</p> + {file.size !== undefined && file.size > 0 && ( + <Text fontSize="xs" color="gray.500" ml={2}> + ({file.size} bytes) + </Text> + )} + </ListItem> ))} - </Breadcrumb.List> - </Breadcrumb.Root>{" "} - {fileContent ? ( - <Box p={4} bg="gray.100" borderRadius="md"> - <Text whiteSpace="pre-wrap" fontSize="sm" fontFamily="monospace"> - {fileContent} - </Text> - </Box> - ) : ( - <ListRoot> - {Array.isArray(fileTree) && - fileTree.map((file) => ( - <ListItem - key={file.sha} - display="flex" - alignItems="center" - p={2} - borderRadius="md" - _hover={{ bg: "gray.100", cursor: "pointer" }} - onClick={() => - file.type === "dir" - ? handleFolderClick(file.path) - : handleFileClick(file.path) - } - > - <Icon - as={file.type === "dir" ? FiFolder : FiFile} - color={file.type === "dir" ? "blue.500" : "gray.500"} - mr={2} - /> - <p>{file.name}</p> - {file.size !== undefined && file.size > 0 && ( - <Text fontSize="xs" color="gray.500" ml={2}> - ({file.size} bytes) - </Text> - )} - </ListItem> - ))} - </ListRoot> - )} - </Box> + </ListRoot> + )} + </Box> + </> ); }; diff --git a/modules/research-framework/portal/src/components/repositories/index.tsx b/modules/research-framework/portal/src/components/repositories/index.tsx index 665c6ab724..bf593ad0c2 100644 --- a/modules/research-framework/portal/src/components/repositories/index.tsx +++ b/modules/research-framework/portal/src/components/repositories/index.tsx @@ -1,5 +1,4 @@ import { Container, Input, SimpleGrid } from "@chakra-ui/react"; -import NavBar from "../NavBar"; import { PageHeader } from "../PageHeader"; import api from "@/lib/api"; import { useEffect, useState } from "react"; @@ -40,8 +39,6 @@ const Repositories = () => { return ( <> - <NavBar /> - <Container maxW="container.lg" mt={8}> <PageHeader title="Repositories" diff --git a/modules/research-framework/portal/src/components/resources/ResourceDetails.tsx b/modules/research-framework/portal/src/components/resources/ResourceDetails.tsx index a475cf302d..73aab50862 100644 --- a/modules/research-framework/portal/src/components/resources/ResourceDetails.tsx +++ b/modules/research-framework/portal/src/components/resources/ResourceDetails.tsx @@ -1,5 +1,4 @@ import { useNavigate, useParams } from "react-router"; -import NavBar from "../NavBar"; import { Container, Spinner, @@ -60,7 +59,6 @@ const ResourceDetails = () => { return ( <> - <NavBar /> <Container maxW="breakpoint-lg" mx="auto" p={4} mt={16}> <Box> <Button variant="plain" p={0} onClick={() => navigate(-1)}> @@ -150,7 +148,7 @@ const ResourceDetails = () => { {(resource.type as ResourceTypeEnum) === ResourceTypeEnum.REPOSITORY && ( <RepositorySpecificDetails - dataset={resource as RepositoryResource} + repository={resource as RepositoryResource} /> )} diff --git a/modules/research-framework/portal/src/layouts/NavBar.tsx b/modules/research-framework/portal/src/layouts/NavBar.tsx new file mode 100644 index 0000000000..be483f7f41 --- /dev/null +++ b/modules/research-framework/portal/src/layouts/NavBar.tsx @@ -0,0 +1,142 @@ +import { + Text, + Flex, + Spacer, + Image, + HStack, + Avatar, + Box, + IconButton, + useDisclosure, + Collapsible, + Button, + Stack, + ButtonProps, +} from "@chakra-ui/react"; +import ApacheAiravataLogo from "../assets/airavata-logo.png"; +import { Link, useNavigate } from "react-router"; +import { useAuth } from "react-oidc-context"; +import { RxHamburgerMenu } from "react-icons/rx"; +import { IoClose } from "react-icons/io5"; + +const NAV_CONTENT = [ + { + title: "Projects", + url: "/projects", + }, + { + title: "Datasets", + url: "/resources/datasets", + }, + { + title: "Repositories", + url: "/resources/repositories", + }, + { + title: "Notebooks", + url: "/resources/notebooks", + }, + { + title: "Models", + url: "/resources/models", + }, +]; + +interface NavLinkProps extends ButtonProps { + title: string; + url: string; +} + +const NavBar = () => { + const auth = useAuth(); + const { open, onToggle } = useDisclosure(); + const navigate = useNavigate(); + + const NavLink = ({ title, url, ...props }: NavLinkProps) => ( + <Button + variant="plain" + px={2} + _hover={{ bg: "gray.200" }} + onClick={() => { + navigate(url); + onToggle(); + }} + {...props} + > + <Text color="gray.700" fontSize="md" textAlign="left"> + {title} + </Text> + </Button> + ); + + return ( + <Box position="sticky" top="0" zIndex="1000" bg="white" boxShadow="sm"> + <Flex align="center" p={4}> + {/* Hamburger Menu (Mobile Only) */} + <IconButton + aria-label="Toggle Navigation" + display={{ base: "inline-flex", md: "none" }} + onClick={onToggle} + variant="ghost" + mr={2} + > + {open ? <IoClose size={24} /> : <RxHamburgerMenu size={24} />} + </IconButton> + + {/* Logo */} + <Link to="/projects"> + <Image src={ApacheAiravataLogo} alt="Logo" boxSize="30px" /> + </Link> + + {/* Desktop Nav Links */} + <HStack ml={4} display={{ base: "none", md: "flex" }}> + {NAV_CONTENT.map((item) => ( + <NavLink key={item.title} title={item.title} url={item.url} /> + ))} + </HStack> + + <Spacer /> + + {/* User Profile */} + <HStack gap={3}> + <Avatar.Root variant="subtle"> + <Avatar.Fallback name={auth.user?.profile.name} /> + </Avatar.Root> + <Box textAlign="left"> + <Text fontSize="sm">{auth.user?.profile.name}</Text> + <Text fontSize="xs" color="gray.500"> + {auth.user?.profile.email} + </Text> + </Box> + </HStack> + </Flex> + + {/* Mobile Nav Links (Collapse) */} + <Collapsible.Root open={open}> + <Collapsible.Content> + <Stack + direction="column" + bg="white" + px={4} + pb={4} + spaceY={2} + display={{ md: "none" }} + > + {NAV_CONTENT.map((item) => ( + <Box key={item.title} w="100%"> + <NavLink + key={item.title} + title={item.title} + url={item.url} + width="100%" + /> + </Box> + ))} + </Stack> + </Collapsible.Content> + </Collapsible.Root> + </Box> + ); +}; + +export default NavBar; diff --git a/modules/research-framework/portal/src/layouts/NavBarFooterLayout.tsx b/modules/research-framework/portal/src/layouts/NavBarFooterLayout.tsx new file mode 100644 index 0000000000..04d80846a6 --- /dev/null +++ b/modules/research-framework/portal/src/layouts/NavBarFooterLayout.tsx @@ -0,0 +1,14 @@ +// layout.tsx +import NavBar from "@/layouts/NavBar"; +import { Outlet } from "react-router"; + +const NavBarFooterLayout = () => { + return ( + <> + <NavBar /> + <Outlet /> + </> + ); +}; + +export default NavBarFooterLayout;
