This is an automated email from the ASF dual-hosted git repository.

lahirujayathilake pushed a commit to branch cybershuttle-staging
in repository https://gitbox.apache.org/repos/asf/airavata.git


The following commit(s) were added to refs/heads/cybershuttle-staging by this 
push:
     new 214908200f add research portal
214908200f is described below

commit 214908200fb37a51061373fddb0d208adb5ed288
Author: ganning127 <[email protected]>
AuthorDate: Sun Mar 30 11:33:30 2025 -0400

    add research portal
---
 modules/research-framework/portal/.gitignore       |  24 +
 modules/research-framework/portal/README.md        |  54 ++
 modules/research-framework/portal/index.html       |  13 +
 modules/research-framework/portal/public/vite.svg  |   1 +
 modules/research-framework/portal/src/App.tsx      |  41 ++
 .../portal/src/assets/airavata-logo.png            | Bin 0 -> 14892 bytes
 .../research-framework/portal/src/assets/react.svg |   1 +
 .../portal/src/components/GridContainer.tsx        |  17 +
 .../portal/src/components/Metadata.tsx             |  66 +++
 .../portal/src/components/NavBar.tsx               |  58 +++
 .../portal/src/components/PageHeader.tsx           |  28 ++
 .../portal/src/components/datasets/DatasetCard.tsx |  70 +++
 .../src/components/datasets/DatasetDetails.tsx     |  54 ++
 .../portal/src/components/datasets/index.tsx       |  42 ++
 .../src/components/home/AddRepositoryButton.tsx    | 124 +++++
 .../portal/src/components/home/AddZipButton.tsx    | 113 +++++
 .../portal/src/components/home/ButtonWithIcon.tsx  |  22 +
 .../portal/src/components/home/ProjectCard.tsx     |  84 ++++
 .../portal/src/components/home/ProjectsSection.tsx |  28 ++
 .../portal/src/components/home/SessionCard.tsx     |  71 +++
 .../portal/src/components/home/SessionsSection.tsx |  17 +
 .../portal/src/components/home/index.tsx           |  61 +++
 .../portal/src/components/models/ModelCard.tsx     |  34 ++
 .../portal/src/components/models/ModelDetails.tsx  |  96 ++++
 .../portal/src/components/models/index.tsx         |  40 ++
 .../src/components/notebooks/NotebookCard.tsx      |  64 +++
 .../src/components/notebooks/ProjectDetails.tsx    |  98 ++++
 .../portal/src/components/notebooks/index.tsx      |  55 ++
 .../src/components/repositories/RepositoryCard.tsx |  39 ++
 .../portal/src/components/repositories/index.tsx   |  39 ++
 .../portal/src/components/ui/color-mode.tsx        | 107 ++++
 .../portal/src/components/ui/input-group.tsx       |  53 ++
 .../portal/src/components/ui/provider.tsx          |  12 +
 .../portal/src/data/MOCK_DATA.js                   | 555 +++++++++++++++++++++
 .../portal/src/interfaces/AuthorType.tsx           |   5 +
 .../portal/src/interfaces/DatasetType.tsx          |   7 +
 .../portal/src/interfaces/MetadataType.tsx         |  14 +
 .../portal/src/interfaces/ModelType.tsx            |  10 +
 .../portal/src/interfaces/ProjectType.tsx          |   7 +
 .../portal/src/interfaces/SessionType.tsx          |  11 +
 modules/research-framework/portal/src/main.tsx     |  12 +
 .../research-framework/portal/src/vite-env.d.ts    |   1 +
 42 files changed, 2248 insertions(+)

diff --git a/modules/research-framework/portal/.gitignore 
b/modules/research-framework/portal/.gitignore
new file mode 100644
index 0000000000..a547bf36d8
--- /dev/null
+++ b/modules/research-framework/portal/.gitignore
@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/modules/research-framework/portal/README.md 
b/modules/research-framework/portal/README.md
new file mode 100644
index 0000000000..40ede56ea6
--- /dev/null
+++ b/modules/research-framework/portal/README.md
@@ -0,0 +1,54 @@
+# React + TypeScript + Vite
+
+This template provides a minimal setup to get React working in Vite with HMR 
and some ESLint rules.
+
+Currently, two official plugins are available:
+
+- 
[@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md)
 uses [Babel](https://babeljs.io/) for Fast Refresh
+- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) 
uses [SWC](https://swc.rs/) for Fast Refresh
+
+## Expanding the ESLint configuration
+
+If you are developing a production application, we recommend updating the 
configuration to enable type-aware lint rules:
+
+```js
+export default tseslint.config({
+  extends: [
+    // Remove ...tseslint.configs.recommended and replace with this
+    ...tseslint.configs.recommendedTypeChecked,
+    // Alternatively, use this for stricter rules
+    ...tseslint.configs.strictTypeChecked,
+    // Optionally, add this for stylistic rules
+    ...tseslint.configs.stylisticTypeChecked,
+  ],
+  languageOptions: {
+    // other options...
+    parserOptions: {
+      project: ['./tsconfig.node.json', './tsconfig.app.json'],
+      tsconfigRootDir: import.meta.dirname,
+    },
+  },
+})
+```
+
+You can also install 
[eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x)
 and 
[eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom)
 for React-specific lint rules:
+
+```js
+// eslint.config.js
+import reactX from 'eslint-plugin-react-x'
+import reactDom from 'eslint-plugin-react-dom'
+
+export default tseslint.config({
+  plugins: {
+    // Add the react-x and react-dom plugins
+    'react-x': reactX,
+    'react-dom': reactDom,
+  },
+  rules: {
+    // other rules...
+    // Enable its recommended typescript rules
+    ...reactX.configs['recommended-typescript'].rules,
+    ...reactDom.configs.recommended.rules,
+  },
+})
+```
diff --git a/modules/research-framework/portal/index.html 
b/modules/research-framework/portal/index.html
new file mode 100644
index 0000000000..e4b78eae12
--- /dev/null
+++ b/modules/research-framework/portal/index.html
@@ -0,0 +1,13 @@
+<!doctype html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>Vite + React + TS</title>
+  </head>
+  <body>
+    <div id="root"></div>
+    <script type="module" src="/src/main.tsx"></script>
+  </body>
+</html>
diff --git a/modules/research-framework/portal/public/vite.svg 
b/modules/research-framework/portal/public/vite.svg
new file mode 100644
index 0000000000..e7b8dfb1b2
--- /dev/null
+++ b/modules/research-framework/portal/public/vite.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg"; 
xmlns:xlink="http://www.w3.org/1999/xlink"; aria-hidden="true" role="img" 
class="iconify iconify--logos" width="31.88" height="32" 
preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient 
id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" 
y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" 
stop-color="#BD34FE"></stop></linearGradient><linearGradient 
id="IconifyId1813088fe1fbc01fb [...]
\ No newline at end of file
diff --git a/modules/research-framework/portal/src/App.tsx 
b/modules/research-framework/portal/src/App.tsx
new file mode 100644
index 0000000000..a03737144d
--- /dev/null
+++ b/modules/research-framework/portal/src/App.tsx
@@ -0,0 +1,41 @@
+import { useColorMode } from "./components/ui/color-mode";
+import { BrowserRouter, Route, Routes } from "react-router";
+import Home from "./components/home";
+import ProjectDetails from "./components/notebooks/ProjectDetails";
+import { Models } from "./components/models";
+import { ModelDetails } from "./components/models/ModelDetails";
+import { Datasets } from "./components/datasets";
+import { DatasetDetails } from "./components/datasets/DatasetDetails";
+
+function App() {
+  const colorMode = useColorMode();
+  if (colorMode.colorMode === "dark") {
+    colorMode.toggleColorMode();
+  }
+
+  return (
+    <>
+      <BrowserRouter>
+        <Routes>
+          <Route index element={<Home />} />
+          <Route path="/notebook">
+            <Route path=":slug" element={<ProjectDetails />} />
+          </Route>
+          <Route path="/repository">
+            <Route path=":slug" element={<ProjectDetails />} />
+          </Route>
+          <Route path="/models">
+            <Route index element={<Models />} />
+            <Route path=":id" element={<ModelDetails />} />
+          </Route>
+          <Route path="/datasets">
+            <Route index element={<Datasets />} />
+            <Route path=":slug" element={<DatasetDetails />} />
+          </Route>
+        </Routes>
+      </BrowserRouter>
+    </>
+  );
+}
+
+export default App;
diff --git a/modules/research-framework/portal/src/assets/airavata-logo.png 
b/modules/research-framework/portal/src/assets/airavata-logo.png
new file mode 100644
index 0000000000..06aea97295
Binary files /dev/null and 
b/modules/research-framework/portal/src/assets/airavata-logo.png differ
diff --git a/modules/research-framework/portal/src/assets/react.svg 
b/modules/research-framework/portal/src/assets/react.svg
new file mode 100644
index 0000000000..6c87de9bb3
--- /dev/null
+++ b/modules/research-framework/portal/src/assets/react.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg"; 
xmlns:xlink="http://www.w3.org/1999/xlink"; aria-hidden="true" role="img" 
class="iconify iconify--logos" width="35.93" height="32" 
preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" 
d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 
1.273-5.621c6.238-30.281 
2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 
0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587 [...]
\ No newline at end of file
diff --git a/modules/research-framework/portal/src/components/GridContainer.tsx 
b/modules/research-framework/portal/src/components/GridContainer.tsx
new file mode 100644
index 0000000000..834a6d3b87
--- /dev/null
+++ b/modules/research-framework/portal/src/components/GridContainer.tsx
@@ -0,0 +1,17 @@
+import { SimpleGrid } from "@chakra-ui/react";
+
+export const GridContainer = ({ children }: { children: React.ReactNode }) => {
+  return (
+    <SimpleGrid
+      columns={{ base: 1, md: 2, lg: 3 }}
+      gap={4}
+      mt={2}
+      maxH="50vh"
+      overflow="scroll"
+      p={4}
+      bg="gray.50"
+    >
+      {children}
+    </SimpleGrid>
+  );
+};
diff --git a/modules/research-framework/portal/src/components/Metadata.tsx 
b/modules/research-framework/portal/src/components/Metadata.tsx
new file mode 100644
index 0000000000..92b12dd7b8
--- /dev/null
+++ b/modules/research-framework/portal/src/components/Metadata.tsx
@@ -0,0 +1,66 @@
+import { MetadataType } from "@/interfaces/MetadataType";
+import {
+  Image,
+  HStack,
+  Box,
+  Text,
+  Heading,
+  Avatar,
+  Separator,
+  Badge,
+} from "@chakra-ui/react";
+
+export const Metadata = ({ metadata }: { metadata: MetadataType }) => {
+  return (
+    <>
+      <HStack
+        alignItems={"flex-start"}
+        mb={4}
+        gap={8}
+        justifyContent="space-between"
+      >
+        <Box>
+          <Heading as="h1" size="4xl" mb={4}>
+            {metadata.title}
+          </Heading>
+
+          <HStack>
+            <Avatar.Root shape="full" size="xl">
+              <Avatar.Fallback name={metadata.author.name} />
+              <Avatar.Image src={metadata.author.avatar} />
+            </Avatar.Root>
+
+            <Box>
+              <Text fontWeight="bold">{metadata.author.name}</Text>
+              <Text color="gray.500">{metadata.author.role}</Text>
+            </Box>
+          </HStack>
+        </Box>
+
+        <Image
+          src={metadata.images.headerImage}
+          alt="Notebook Header"
+          rounded="md"
+          maxW="300px"
+        />
+      </HStack>
+
+      <Separator my={6} />
+      <Box>
+        <Heading fontWeight="bold" size="2xl">
+          About
+        </Heading>
+
+        <HStack my={2}>
+          {metadata.tags.map((tag) => (
+            <Badge key={tag} size="lg" rounded="md">
+              {tag}
+            </Badge>
+          ))}
+        </HStack>
+
+        <Text>{metadata.description}</Text>
+      </Box>
+    </>
+  );
+};
diff --git a/modules/research-framework/portal/src/components/NavBar.tsx 
b/modules/research-framework/portal/src/components/NavBar.tsx
new file mode 100644
index 0000000000..f62d1ac85a
--- /dev/null
+++ b/modules/research-framework/portal/src/components/NavBar.tsx
@@ -0,0 +1,58 @@
+import { Text, Flex, Spacer, Image, HStack, Avatar } from "@chakra-ui/react";
+import ApacheAiravataLogo from "../assets/airavata-logo.png";
+import { Link } from "react-router";
+
+const NAV_CONTENT = [
+  {
+    title: "Home",
+    url: "/",
+  },
+  {
+    title: "Datasets",
+    url: "/datasets",
+  },
+  {
+    title: "Models",
+    url: "/models",
+  },
+];
+
+const NavBar = () => {
+  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="/">
+        <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">
+            {item.title}
+          </Link>
+        ))}
+      </Flex>
+
+      <Spacer />
+
+      {/* Search Bar */}
+      <HStack _hover={{ cursor: "pointer" }}>
+        <Text>John Doe</Text>
+        <Avatar.Root variant="subtle">
+          <Avatar.Fallback name="John Doe" />
+        </Avatar.Root>
+      </HStack>
+    </Flex>
+  );
+};
+export default NavBar;
diff --git a/modules/research-framework/portal/src/components/PageHeader.tsx 
b/modules/research-framework/portal/src/components/PageHeader.tsx
new file mode 100644
index 0000000000..f4e1e8ee85
--- /dev/null
+++ b/modules/research-framework/portal/src/components/PageHeader.tsx
@@ -0,0 +1,28 @@
+import { Box, HStack, Text, Heading, Icon } from "@chakra-ui/react";
+import { ReactNode } from "react";
+
+export const PageHeader = ({
+  title,
+  icon,
+  description,
+}: {
+  title: string;
+  icon?: ReactNode;
+  description: string;
+}) => {
+  const fontSize = "4xl";
+  return (
+    <Box>
+      <HStack alignItems="center" gap={4}>
+        {icon && <Icon as={icon} fontSize={fontSize} />}
+        <Heading as="h1" size={fontSize}>
+          {title}
+        </Heading>
+      </HStack>
+
+      <Text color="gray.600" mt={2}>
+        {description}
+      </Text>
+    </Box>
+  );
+};
diff --git 
a/modules/research-framework/portal/src/components/datasets/DatasetCard.tsx 
b/modules/research-framework/portal/src/components/datasets/DatasetCard.tsx
new file mode 100644
index 0000000000..95d3a84ddf
--- /dev/null
+++ b/modules/research-framework/portal/src/components/datasets/DatasetCard.tsx
@@ -0,0 +1,70 @@
+import { DatasetType } from "@/interfaces/DatasetType";
+import {
+  Text,
+  Box,
+  Card,
+  HStack,
+  Avatar,
+  Image,
+  Badge,
+} from "@chakra-ui/react";
+import { Link } from "react-router";
+
+export const DatasetCard = ({ dataset }: { dataset: DatasetType }) => {
+  return (
+    <Box>
+      <Card.Root overflow="hidden">
+        <Image
+          src={dataset.metadata.images.headerImage}
+          maxH={100}
+          alt="Green double couch with wooden legs"
+        />
+        <Link to={`/datasets/${dataset.metadata.slug}`}>
+          <Card.Body
+            gap="2"
+            _hover={{
+              bg: "gray.100",
+            }}
+          >
+            <HStack justifyContent="space-between" mb={1}>
+              <Card.Title>{dataset.metadata.title}</Card.Title>
+              <Badge
+                colorPalette={dataset.private ? "red" : "green"}
+                rounded="md"
+              >
+                {dataset.private ? "Private" : "Public"}
+              </Badge>
+            </HStack>
+            <HStack flexWrap={"wrap"}>
+              <Badge colorPalette={"purple"} fontWeight="bold" size="md">
+                {dataset.metadata.type}
+              </Badge>
+              {dataset.metadata.tags.map((tag: string) => (
+                <Badge size="md" key={tag}>
+                  {tag}
+                </Badge>
+              ))}
+            </HStack>
+            <Text color="fg.muted" lineClamp={2}>
+              {dataset.metadata.description}.
+            </Text>
+          </Card.Body>
+        </Link>
+
+        <Card.Footer justifyContent="space-between">
+          <HStack mt={4}>
+            <Avatar.Root shape="full" size="xl">
+              <Avatar.Fallback name={dataset.metadata.author.name} />
+              <Avatar.Image src={dataset.metadata.author.avatar} />
+            </Avatar.Root>
+
+            <Box>
+              <Text fontWeight="bold">{dataset.metadata.author.name}</Text>
+              <Text color="gray.500">{dataset.metadata.author.role}</Text>
+            </Box>
+          </HStack>
+        </Card.Footer>
+      </Card.Root>
+    </Box>
+  );
+};
diff --git 
a/modules/research-framework/portal/src/components/datasets/DatasetDetails.tsx 
b/modules/research-framework/portal/src/components/datasets/DatasetDetails.tsx
new file mode 100644
index 0000000000..79719752aa
--- /dev/null
+++ 
b/modules/research-framework/portal/src/components/datasets/DatasetDetails.tsx
@@ -0,0 +1,54 @@
+import { Metadata } from "../Metadata";
+import NavBar from "../NavBar";
+// @ts-expect-error This is fine
+import { MOCK_DATASETS } from "../../data/MOCK_DATA";
+import { DatasetType } from "@/interfaces/DatasetType";
+import { useEffect, useState } from "react";
+import { Badge, Box, Container, HStack, Icon, Spinner } from 
"@chakra-ui/react";
+import { Link, useParams } from "react-router";
+import { BiArrowBack } from "react-icons/bi";
+
+async function getDataset(slug: string | undefined) {
+  return MOCK_DATASETS.find(
+    (dataset: DatasetType) => dataset.metadata.slug === slug
+  );
+}
+
+export const DatasetDetails = () => {
+  const [dataset, setDataset] = useState<DatasetType | null>(null);
+  const { slug } = useParams();
+
+  useEffect(() => {
+    async function getData() {
+      const n = await getDataset(slug);
+      setDataset(n);
+    }
+    getData();
+  }, []);
+
+  if (!dataset) return <Spinner />;
+  return (
+    <>
+      <NavBar />
+
+      <Container maxW="breakpoint-lg" mx="auto" p={4} mt={16}>
+        <Box>
+          <HStack alignItems="center" mb={4}>
+            <Icon>
+              <BiArrowBack />
+            </Icon>
+            <Link to="/datasets">Back to Datasets</Link>
+          </HStack>
+        </Box>
+        <Badge
+          colorPalette={dataset.private ? "red" : "green"}
+          rounded="md"
+          mb={1}
+        >
+          {dataset.private ? "Private" : "Public"}
+        </Badge>
+        <Metadata metadata={dataset.metadata} />
+      </Container>
+    </>
+  );
+};
diff --git 
a/modules/research-framework/portal/src/components/datasets/index.tsx 
b/modules/research-framework/portal/src/components/datasets/index.tsx
new file mode 100644
index 0000000000..82d79d9cb5
--- /dev/null
+++ b/modules/research-framework/portal/src/components/datasets/index.tsx
@@ -0,0 +1,42 @@
+import NavBar from "../NavBar";
+// @ts-expect-error This is fine
+import { MOCK_DATASETS } from "../../data/MOCK_DATA";
+import { Container, HStack, Input, SimpleGrid } from "@chakra-ui/react";
+import { PageHeader } from "../PageHeader";
+import { LuSearch } from "react-icons/lu";
+import { InputGroup } from "../ui/input-group";
+import { ButtonWithIcon } from "../home/ButtonWithIcon";
+import { FaPlus } from "react-icons/fa";
+import { DatasetCard } from "./DatasetCard";
+import { DatasetType } from "@/interfaces/DatasetType";
+
+export const Datasets = () => {
+  return (
+    <>
+      <NavBar />
+
+      <Container maxW="container.lg" p={4}>
+        <HStack alignItems="flex-end" justify="space-between">
+          <PageHeader
+            title="Datasets"
+            description="Public and Private Scientific Datasets."
+          />
+          <ButtonWithIcon colorPalette="purple" icon={FaPlus}>
+            Dataset
+          </ButtonWithIcon>
+        </HStack>
+        <InputGroup mt={4} endElement={<LuSearch />} w="100%">
+          <Input placeholder="Search" rounded="md" />
+        </InputGroup>
+
+        <SimpleGrid columns={{ base: 1, md: 2, lg: 3 }} gap={4} mt={4}>
+          {MOCK_DATASETS.map((dataset: DatasetType) => {
+            return (
+              <DatasetCard dataset={dataset} key={dataset.metadata.slug} />
+            );
+          })}
+        </SimpleGrid>
+      </Container>
+    </>
+  );
+};
diff --git 
a/modules/research-framework/portal/src/components/home/AddRepositoryButton.tsx 
b/modules/research-framework/portal/src/components/home/AddRepositoryButton.tsx
new file mode 100644
index 0000000000..aad756a3e2
--- /dev/null
+++ 
b/modules/research-framework/portal/src/components/home/AddRepositoryButton.tsx
@@ -0,0 +1,124 @@
+import {
+  Dialog,
+  Button,
+  Field,
+  Input,
+  CloseButton,
+  Portal,
+  useDialog,
+  Code,
+  VStack,
+  SimpleGrid,
+  Textarea,
+} from "@chakra-ui/react";
+import { FaPlus } from "react-icons/fa";
+import { ButtonWithIcon } from "./ButtonWithIcon";
+
+export const AddRepositoryButton = () => {
+  const dialog = useDialog();
+  // useEffect(() => {
+  //   dialog.setOpen(true);
+  // }, []);
+  return (
+    <>
+      <Dialog.RootProvider value={dialog} size="lg">
+        <Dialog.Trigger asChild>
+          <ButtonWithIcon
+            bg="cyan.600"
+            _hover={{ bg: "cyan.700" }}
+            icon={FaPlus}
+          >
+            Repository
+          </ButtonWithIcon>
+        </Dialog.Trigger>
+        <Portal>
+          <Dialog.Backdrop />
+          <Dialog.Positioner>
+            <Dialog.Content>
+              <Dialog.Body p="4">
+                <Dialog.Title>Add Git Repository</Dialog.Title>
+                <Dialog.Description mt="2">
+                  Start by providing a GitHub URL. If the repository contains a
+                  <Code colorPalette="blue">cybershuttle.yml</Code> file, the
+                  author/tag/workspace information will be pulled from it. If
+                  not, please provide them manually.
+                </Dialog.Description>
+
+                <VStack gap={4} mt="4">
+                  <Field.Root>
+                    <Field.Label>URL</Field.Label>
+                    <Input
+                      type="url"
+                      flexGrow={1}
+                      placeholder="https://github.com/example/repo.git";
+                    />
+                  </Field.Root>
+                  <Field.Root>
+                    <Field.Label>Authors</Field.Label>
+                    <Input
+                      flexGrow={1}
+                      placeholder="John Doe <[email protected]>; Jane Doe 
<[email protected]>"
+                    />
+                  </Field.Root>
+
+                  <Field.Root>
+                    <Field.Label>Tags</Field.Label>
+                    <Input
+                      flexGrow={1}
+                      placeholder="bmtk-workshop; neuroscience; notebooks"
+                    />
+                  </Field.Root>
+                  <SimpleGrid columns={5} gap={4}>
+                    <Field.Root>
+                      <Field.Label>Dir</Field.Label>
+                      <Input flexGrow={1} placeholder="/workspace" />
+                    </Field.Root>
+                    <Field.Root>
+                      <Field.Label>Nodes</Field.Label>
+                      <Input flexGrow={1} placeholder="1" />
+                    </Field.Root>
+
+                    <Field.Root>
+                      <Field.Label>Min CPU</Field.Label>
+                      <Input flexGrow={1} placeholder="1" />
+                    </Field.Root>
+                    <Field.Root>
+                      <Field.Label>Min GPU</Field.Label>
+                      <Input flexGrow={1} placeholder="1" />
+                    </Field.Root>
+                    <Field.Root>
+                      <Field.Label>Min RAM</Field.Label>
+                      <Input flexGrow={1} placeholder="1" />
+                    </Field.Root>
+                  </SimpleGrid>
+
+                  <Field.Root>
+                    <Field.Label>Data</Field.Label>
+                    <Textarea
+                      flexGrow={1}
+                      rows={4}
+                      placeholder="cyber://321aed:/workspace/data/A
+allen://8a8b2f:/workspace/data/B
+dropbox://4a34d1:/workspace/data/C"
+                    />
+                  </Field.Root>
+                  <Button
+                    bg="cyan.600"
+                    _hover={{ bg: "cyan.700" }}
+                    width="100%"
+                    type="submit"
+                  >
+                    Add Git Repository
+                  </Button>
+                </VStack>
+              </Dialog.Body>
+              <Dialog.CloseTrigger asChild>
+                <CloseButton _hover={{ bg: "gray.100" }} size="sm" />
+              </Dialog.CloseTrigger>
+            </Dialog.Content>
+          </Dialog.Positioner>
+        </Portal>
+      </Dialog.RootProvider>
+    </>
+  );
+};
diff --git 
a/modules/research-framework/portal/src/components/home/AddZipButton.tsx 
b/modules/research-framework/portal/src/components/home/AddZipButton.tsx
new file mode 100644
index 0000000000..aae98694c0
--- /dev/null
+++ b/modules/research-framework/portal/src/components/home/AddZipButton.tsx
@@ -0,0 +1,113 @@
+import {
+  Dialog,
+  Button,
+  CloseButton,
+  Portal,
+  useDialog,
+  Code,
+  Box,
+  Icon,
+  FileUpload,
+  Spacer,
+  useFileUpload,
+} from "@chakra-ui/react";
+import { FaPlus } from "react-icons/fa";
+import { ButtonWithIcon } from "./ButtonWithIcon";
+import { LuUpload } from "react-icons/lu";
+
+export const AddZipButton = () => {
+  const dialog = useDialog();
+  // useEffect(() => {
+  //   dialog.setOpen(true);
+  // }, []);
+  const fileUpload = useFileUpload({
+    maxFiles: 1,
+    accept: ".zip",
+  });
+
+  return (
+    <>
+      <Dialog.RootProvider
+        value={dialog}
+        size="lg"
+        onExitComplete={() => {
+          fileUpload.clearFiles(); // Clear files when modal closes
+        }}
+      >
+        <Dialog.Trigger asChild>
+          <ButtonWithIcon
+            bg="pink.500"
+            _hover={{ bg: "pink.600" }}
+            icon={FaPlus}
+          >
+            Zip
+          </ButtonWithIcon>
+        </Dialog.Trigger>
+        <Portal>
+          <Dialog.Backdrop />
+          <Dialog.Positioner>
+            <Dialog.Content>
+              <Dialog.Body p="4">
+                <Dialog.Title>Add Zip File</Dialog.Title>
+                <Dialog.Description mt="2">
+                  Upload a ZIP file containing your repository. If the
+                  repository contains a{" "}
+                  <Code colorPalette="blue">cybershuttle.yml</Code> file, the
+                  author/tag/workspace information will be pulled from it. If
+                  not, please provide them manually.
+                </Dialog.Description>
+                <FileUpload.RootProvider
+                  alignItems="stretch"
+                  mt={4}
+                  value={fileUpload}
+                >
+                  <FileUpload.HiddenInput />
+                  {fileUpload.acceptedFiles.length === 0 && (
+                    <FileUpload.Dropzone>
+                      <Icon size="md" color="fg.muted">
+                        <LuUpload />
+                      </Icon>
+                      <FileUpload.DropzoneContent>
+                        <Box>Drag and drop your .zip file here</Box>
+                      </FileUpload.DropzoneContent>
+                    </FileUpload.Dropzone>
+                  )}
+
+                  <FileUpload.ItemGroup>
+                    <FileUpload.Context>
+                      {({ acceptedFiles }) =>
+                        acceptedFiles.map((file) => (
+                          <FileUpload.Item key={file.name} file={file}>
+                            <FileUpload.ItemPreview />
+                            <FileUpload.ItemName />
+                            <FileUpload.ItemSizeText />
+                            <Spacer />
+                            <FileUpload.ItemDeleteTrigger />
+                          </FileUpload.Item>
+                        ))
+                      }
+                    </FileUpload.Context>
+                  </FileUpload.ItemGroup>
+                </FileUpload.RootProvider>
+
+                <Button
+                  bg="pink.500"
+                  _hover={{ bg: "pink.600" }}
+                  width="100%"
+                  type="submit"
+                  mt={4}
+                  disabled={fileUpload.acceptedFiles.length === 0}
+                >
+                  Add Zip File
+                </Button>
+              </Dialog.Body>
+              <Dialog.CloseTrigger asChild>
+                <CloseButton _hover={{ bg: "gray.100" }} size="sm" />
+              </Dialog.CloseTrigger>
+            </Dialog.Content>
+          </Dialog.Positioner>
+        </Portal>
+      </Dialog.RootProvider>
+    </>
+  );
+};
diff --git 
a/modules/research-framework/portal/src/components/home/ButtonWithIcon.tsx 
b/modules/research-framework/portal/src/components/home/ButtonWithIcon.tsx
new file mode 100644
index 0000000000..c970dcb185
--- /dev/null
+++ b/modules/research-framework/portal/src/components/home/ButtonWithIcon.tsx
@@ -0,0 +1,22 @@
+import { Button, ButtonProps, Icon } from "@chakra-ui/react";
+import { IconType } from "react-icons";
+
+interface ButtonWithIconProps extends ButtonProps {
+  icon: IconType;
+  children: React.ReactNode;
+}
+
+export const ButtonWithIcon = ({
+  icon: UserIcon,
+  children,
+  ...rest
+}: ButtonWithIconProps) => {
+  return (
+    <Button {...rest}>
+      <Icon size="xs">
+        <UserIcon fontWeight="solid" />
+      </Icon>
+      {children}
+    </Button>
+  );
+};
diff --git 
a/modules/research-framework/portal/src/components/home/ProjectCard.tsx 
b/modules/research-framework/portal/src/components/home/ProjectCard.tsx
new file mode 100644
index 0000000000..05173203c2
--- /dev/null
+++ b/modules/research-framework/portal/src/components/home/ProjectCard.tsx
@@ -0,0 +1,84 @@
+import { ProjectType } from "@/interfaces/ProjectType";
+import {
+  Text,
+  Box,
+  Card,
+  HStack,
+  Avatar,
+  Button,
+  Icon,
+  Image,
+  Badge,
+} from "@chakra-ui/react";
+import { FaPlay } from "react-icons/fa";
+import { Link } from "react-router";
+
+export const ProjectCard = ({ project }: { project: ProjectType }) => {
+  return (
+    <Box>
+      <Card.Root overflow="hidden">
+        <Image
+          src={project.metadata.images.headerImage}
+          maxH={100}
+          alt="Green double couch with wooden legs"
+        />
+        <Link to={`/${project.metadata.type}/${project.metadata.slug}`}>
+          <Card.Body
+            gap="2"
+            _hover={{
+              bg: "gray.100",
+            }}
+          >
+            <Card.Title>{project.metadata.title}</Card.Title>
+            <HStack flexWrap={"wrap"}>
+              <Badge
+                colorPalette={
+                  project.metadata.type === "notebook" ? "cyan" : "pink"
+                }
+                fontWeight="bold"
+                size="md"
+              >
+                {project.metadata.type}
+              </Badge>
+              {project.metadata.tags.map((tag: string) => (
+                <Badge size="md" key={tag}>
+                  {tag}
+                </Badge>
+              ))}
+            </HStack>
+            <Text color="fg.muted" lineClamp={2}>
+              {project.metadata.description}.
+            </Text>
+          </Card.Body>
+        </Link>
+
+        <Card.Footer justifyContent="space-between">
+          <HStack mt={4}>
+            <Avatar.Root shape="full" size="xl">
+              <Avatar.Fallback name={project.metadata.author.name} />
+              <Avatar.Image src={project.metadata.author.avatar} />
+            </Avatar.Root>
+
+            <Box>
+              <Text fontWeight="bold">{project.metadata.author.name}</Text>
+              <Text color="gray.500">{project.metadata.author.role}</Text>
+            </Box>
+          </HStack>
+
+          <Button
+            bg={project.metadata.type === "notebook" ? "cyan.500" : "pink.500"}
+            _hover={{
+              bg:
+                project.metadata.type === "notebook" ? "cyan.600" : "pink.600",
+            }}
+          >
+            Run
+            <Icon ml={1} size={"sm"}>
+              <FaPlay />
+            </Icon>
+          </Button>
+        </Card.Footer>
+      </Card.Root>
+    </Box>
+  );
+};
diff --git 
a/modules/research-framework/portal/src/components/home/ProjectsSection.tsx 
b/modules/research-framework/portal/src/components/home/ProjectsSection.tsx
new file mode 100644
index 0000000000..836621dbd8
--- /dev/null
+++ b/modules/research-framework/portal/src/components/home/ProjectsSection.tsx
@@ -0,0 +1,28 @@
+import { ProjectType } from "@/interfaces/ProjectType";
+import { Input } from "@chakra-ui/react";
+import { LuSearch } from "react-icons/lu";
+// @ts-expect-error This is fine
+import { MOCK_PROJECTS } from "../../data/MOCK_DATA";
+import { ProjectCard } from "./ProjectCard";
+import { InputGroup } from "../ui/input-group";
+import { GridContainer } from "../GridContainer";
+
+export const ProjectsSection = () => {
+  // shuffle projects
+  // const shuffledProjects = MOCK_PROJECTS.sort(() => 0.5 - Math.random());
+
+  const projects = MOCK_PROJECTS;
+  return (
+    <>
+      <InputGroup mt={2} endElement={<LuSearch />} w="100%">
+        <Input placeholder="Search" rounded="md" />
+      </InputGroup>
+
+      <GridContainer>
+        {projects.map((project: ProjectType) => {
+          return <ProjectCard key={project.metadata.slug} project={project} />;
+        })}
+      </GridContainer>
+    </>
+  );
+};
diff --git 
a/modules/research-framework/portal/src/components/home/SessionCard.tsx 
b/modules/research-framework/portal/src/components/home/SessionCard.tsx
new file mode 100644
index 0000000000..a6bf2ee4be
--- /dev/null
+++ b/modules/research-framework/portal/src/components/home/SessionCard.tsx
@@ -0,0 +1,71 @@
+import { SessionType } from "@/interfaces/SessionType";
+import {
+  Box,
+  Text,
+  Card,
+  Heading,
+  HStack,
+  SimpleGrid,
+  Badge,
+} from "@chakra-ui/react";
+
+const getColorPalette = (status: string) => {
+  switch (status) {
+    case "running":
+      return "green";
+    case "stopped":
+      return "red";
+    case "pending":
+      return "yellow";
+    default:
+      return "gray";
+  }
+};
+export const SessionCard = ({ session }: { session: SessionType }) => {
+  return (
+    <Card.Root
+      size="sm"
+      bg={
+        session.title.toLocaleLowerCase() === "jupyter"
+          ? "green.50"
+          : "purple.50"
+      }
+    >
+      <Card.Header>
+        <HStack justify="space-between" alignItems="flex-start">
+          <Box>
+            <Heading size="lg">{session.title}</Heading>
+            <Text color="fg.muted">{session.started}</Text>
+          </Box>
+          <Badge size="md" colorPalette={getColorPalette(session.status)}>
+            {session.status}
+          </Badge>
+        </HStack>
+      </Card.Header>
+      <Card.Body>
+        <SimpleGrid columns={2}>
+          <KeyValue label="Models" value={session.models.join(", ")} />
+          <KeyValue label="Datasets" value={session.datasets.join(", ")} />
+          <KeyValue label="Nodes" value={session.nodes.toString()} />
+          <KeyValue label="RAM" value={session.ram.toString()} />
+          <KeyValue label="Storage" value={session.storage.toString()} />
+        </SimpleGrid>
+      </Card.Body>
+    </Card.Root>
+  );
+};
+
+export const KeyValue = ({
+  label,
+  value,
+}: {
+  label: string;
+  value: string;
+}) => {
+  return (
+    <HStack>
+      <Text fontWeight="bold">{label}:</Text>
+      <Text>{value}</Text>
+    </HStack>
+  );
+};
diff --git 
a/modules/research-framework/portal/src/components/home/SessionsSection.tsx 
b/modules/research-framework/portal/src/components/home/SessionsSection.tsx
new file mode 100644
index 0000000000..d3dd2eeea4
--- /dev/null
+++ b/modules/research-framework/portal/src/components/home/SessionsSection.tsx
@@ -0,0 +1,17 @@
+// @ts-expect-error This is fine
+import { MOCK_SESSIONS } from "../../data/MOCK_DATA";
+import { SessionType } from "@/interfaces/SessionType";
+import { SessionCard } from "./SessionCard";
+import { GridContainer } from "../GridContainer";
+
+export const SessionsSection = () => {
+  return (
+    <>
+      <GridContainer>
+        {MOCK_SESSIONS.map((session: SessionType) => {
+          return <SessionCard key={session.sessionId} session={session} />;
+        })}
+      </GridContainer>
+    </>
+  );
+};
diff --git a/modules/research-framework/portal/src/components/home/index.tsx 
b/modules/research-framework/portal/src/components/home/index.tsx
new file mode 100644
index 0000000000..6a96f64e5e
--- /dev/null
+++ b/modules/research-framework/portal/src/components/home/index.tsx
@@ -0,0 +1,61 @@
+import { Box, HStack, Container } from "@chakra-ui/react";
+
+import NavBar from "../NavBar";
+import { PageHeader } from "../PageHeader";
+import { AddRepositoryButton } from "./AddRepositoryButton";
+import { AddZipButton } from "./AddZipButton";
+import { ProjectsSection } from "./ProjectsSection";
+import { ButtonWithIcon } from "./ButtonWithIcon";
+import { FaPlus } from "react-icons/fa";
+import { SessionsSection } from "./SessionsSection";
+
+const Home = () => {
+  return (
+    <Box>
+      <NavBar />
+
+      <Container maxW="container.xl" p={4}>
+        <HStack alignItems="flex-end" justify="space-between">
+          <PageHeader
+            title="Notebooks"
+            description="Community-Published Scientific Notebooks and 
Repositories."
+          />
+
+          <HStack gap={4}>
+            <AddZipButton />
+            <AddRepositoryButton />
+          </HStack>
+        </HStack>
+
+        <ProjectsSection />
+
+        <HStack alignItems="flex-end" justify="space-between" mt={4}>
+          <PageHeader
+            title="Sessions"
+            description="Stop or attach to past sessions. Each session 
preserves your code and data."
+          />
+
+          <HStack gap={4}>
+            <ButtonWithIcon
+              bg="purple.500"
+              _hover={{ bg: "purple.600" }}
+              icon={FaPlus}
+            >
+              VSCode
+            </ButtonWithIcon>
+            <ButtonWithIcon
+              bg="green.600"
+              _hover={{ bg: "green.700" }}
+              icon={FaPlus}
+            >
+              Jupyter
+            </ButtonWithIcon>
+          </HStack>
+        </HStack>
+        <SessionsSection />
+      </Container>
+    </Box>
+  );
+};
+
+export default Home;
diff --git 
a/modules/research-framework/portal/src/components/models/ModelCard.tsx 
b/modules/research-framework/portal/src/components/models/ModelCard.tsx
new file mode 100644
index 0000000000..9fd0898e54
--- /dev/null
+++ b/modules/research-framework/portal/src/components/models/ModelCard.tsx
@@ -0,0 +1,34 @@
+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
new file mode 100644
index 0000000000..db3eff60ff
--- /dev/null
+++ b/modules/research-framework/portal/src/components/models/ModelDetails.tsx
@@ -0,0 +1,96 @@
+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
new file mode 100644
index 0000000000..12679290e8
--- /dev/null
+++ b/modules/research-framework/portal/src/components/models/index.tsx
@@ -0,0 +1,40 @@
+import NavBar from "../NavBar";
+// @ts-expect-error This is fine
+import { MOCK_MODELS } from "../../data/MOCK_DATA";
+import { Container, HStack, Input, SimpleGrid } from "@chakra-ui/react";
+import { PageHeader } from "../PageHeader";
+import { ModelCard } from "./ModelCard";
+import { ModelType } from "@/interfaces/ModelType";
+import { LuSearch } from "react-icons/lu";
+import { InputGroup } from "../ui/input-group";
+import { ButtonWithIcon } from "../home/ButtonWithIcon";
+import { FaPlus } from "react-icons/fa";
+
+export const Models = () => {
+  return (
+    <>
+      <NavBar />
+
+      <Container maxW="container.lg" p={4}>
+        <HStack alignItems="flex-end" justify="space-between">
+          <PageHeader
+            title="Models"
+            description="Public and Private Scientific models that can be run 
with varying inputs."
+          />
+          <ButtonWithIcon colorPalette="teal" icon={FaPlus}>
+            Model
+          </ButtonWithIcon>
+        </HStack>
+        <InputGroup mt={4} endElement={<LuSearch />} w="100%">
+          <Input placeholder="Search" rounded="md" />
+        </InputGroup>
+
+        <SimpleGrid columns={{ base: 1, md: 2, lg: 3 }} gap={4} mt={4}>
+          {MOCK_MODELS.map((model: ModelType) => {
+            return <ModelCard model={model} key={model.appModuleId} />;
+          })}
+        </SimpleGrid>
+      </Container>
+    </>
+  );
+};
diff --git 
a/modules/research-framework/portal/src/components/notebooks/NotebookCard.tsx 
b/modules/research-framework/portal/src/components/notebooks/NotebookCard.tsx
new file mode 100644
index 0000000000..d2bda42287
--- /dev/null
+++ 
b/modules/research-framework/portal/src/components/notebooks/NotebookCard.tsx
@@ -0,0 +1,64 @@
+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/ProjectDetails.tsx 
b/modules/research-framework/portal/src/components/notebooks/ProjectDetails.tsx
new file mode 100644
index 0000000000..2cf9590076
--- /dev/null
+++ 
b/modules/research-framework/portal/src/components/notebooks/ProjectDetails.tsx
@@ -0,0 +1,98 @@
+import { Link, useParams } from "react-router";
+import NavBar from "../NavBar";
+import {
+  Container,
+  Spinner,
+  HStack,
+  Box,
+  Separator,
+  Button,
+  Icon,
+  Link as ChakraLink,
+} from "@chakra-ui/react";
+// @ts-expect-error This is fine
+import { MOCK_PROJECTS } from "../../data/MOCK_DATA";
+import { useEffect, useState } from "react";
+import { BiArrowBack } from "react-icons/bi";
+import { ProjectType } from "@/interfaces/ProjectType";
+import { Metadata } from "../Metadata";
+
+async function getProject(slug: string | undefined) {
+  return MOCK_PROJECTS.find(
+    (project: ProjectType) => project.metadata.slug === slug
+  );
+}
+
+const ProjectDetails = () => {
+  const { slug } = useParams();
+  const [project, setProject] = useState<ProjectType | null>(null);
+
+  useEffect(() => {
+    if (!slug) return;
+
+    async function getData() {
+      const n = await getProject(slug);
+      setProject(n);
+    }
+    getData();
+  }, [slug]);
+
+  if (!project) return <Spinner />;
+
+  const isNotebook = project?.notebookViewer !== undefined;
+
+  return (
+    <>
+      <NavBar />
+
+      <Container maxW="breakpoint-lg" mx="auto" p={4} mt={16}>
+        <Box>
+          <HStack alignItems="center" mb={4}>
+            <Icon>
+              <BiArrowBack />
+            </Icon>
+            <Link to="/">Back to Projects</Link>
+          </HStack>
+        </Box>
+        <Metadata metadata={project.metadata} />
+
+        <Separator my={8} />
+
+        <Button colorPalette="teal" mb={4} w="full">
+          <ChakraLink
+            target="_blank"
+            href={project?.notebookViewer || project?.repositoryUrl}
+            color="white"
+            fontWeight="bold"
+            display="block"
+            width="100%"
+            textAlign="center"
+          >
+            Open
+          </ChakraLink>
+        </Button>
+
+        <Box border="2px solid black">
+          <Box height="600px" borderRadius="md" overflow="hidden">
+            {isNotebook ? (
+              <iframe
+                title="notebook"
+                src={project?.notebookViewer}
+                width="100%"
+                height="100%"
+              />
+            ) : (
+              <iframe
+                width="100%"
+                height="100%"
+                
src={`https://emgithub.com/iframe.html?target=${project.repositoryUrl}%2Fblob%2Fmaster%2FREADME.md&style=default&type=markdown&showBorder=on&showLineNumbers=on&showFileMeta=on&showFullPath=on&showCopy=on`}
+              ></iframe>
+            )}
+          </Box>
+        </Box>
+      </Container>
+    </>
+  );
+};
+
+export default ProjectDetails;
diff --git 
a/modules/research-framework/portal/src/components/notebooks/index.tsx 
b/modules/research-framework/portal/src/components/notebooks/index.tsx
new file mode 100644
index 0000000000..7fa14096d3
--- /dev/null
+++ b/modules/research-framework/portal/src/components/notebooks/index.tsx
@@ -0,0 +1,55 @@
+import { Box, Container, HStack, Input, SimpleGrid } from "@chakra-ui/react";
+import NavBar from "../NavBar";
+import { PageHeader } from "../PageHeader";
+import { FaCode } from "react-icons/fa";
+import { MOCK_NOTEBOOKS } from "../../data/MOCK_DATA";
+import { NotebookCard } from "./NotebookCard";
+import { InputGroup } from "../ui/input-group";
+import { LuSearch } from "react-icons/lu";
+import { TagsInput } from "react-tag-input-component";
+import { useState } from "react";
+
+const Notebooks = () => {
+  const [tags, setTags] = useState<string[]>([]);
+  let filteredNotebooks = MOCK_NOTEBOOKS;
+  if (tags) {
+    filteredNotebooks = MOCK_NOTEBOOKS.filter((notebook) => {
+      return tags.every((tag) => notebook.tags.includes(tag));
+    });
+  }
+  return (
+    <>
+      <NavBar />
+
+      <Container maxW="container.lg" mt={8}>
+        <PageHeader
+          title="Notebooks"
+          icon={<FaCode />}
+          description="Create and manage your notebooks. From here, you can 
create new notebooks, view existing ones, and manage them."
+        />
+        <InputGroup endElement={<LuSearch />} w="100%" mt={16}>
+          <Input placeholder="Search" rounded="md" />
+        </InputGroup>
+        <Box mt={4}>
+          <TagsInput
+            value={tags}
+            onChange={setTags}
+            placeHolder="Filter by tags"
+          />
+        </Box>
+        <SimpleGrid
+          columns={{ base: 1, md: 2, lg: 3 }}
+          mt={4}
+          gap={12}
+          justifyContent="space-around"
+        >
+          {filteredNotebooks.map((notebook: any) => {
+            return <NotebookCard key={notebook.slug} notebook={notebook} />;
+          })}
+        </SimpleGrid>
+      </Container>
+    </>
+  );
+};
+
+export default Notebooks;
diff --git 
a/modules/research-framework/portal/src/components/repositories/RepositoryCard.tsx
 
b/modules/research-framework/portal/src/components/repositories/RepositoryCard.tsx
new file mode 100644
index 0000000000..5d5f2ba8e4
--- /dev/null
+++ 
b/modules/research-framework/portal/src/components/repositories/RepositoryCard.tsx
@@ -0,0 +1,39 @@
+import {
+  Card,
+  Heading,
+  Box,
+  Text,
+  Link as ChakraLink,
+  HStack,
+} from "@chakra-ui/react";
+export const RepositoryCard = ({ repository }) => {
+  return (
+    <Card.Root width="100%">
+      <Card.Header>
+        <HStack justifyContent="space-between" alignItems="flex-start">
+          <Box>
+            <Heading size="md">{repository.title}</Heading>
+            <Text color="gray.500" mt={2}>
+              {repository.date}
+            </Text>
+          </Box>
+
+          <Text>
+            <ChakraLink
+              href={repository.githubUrl}
+              target="_blank"
+              bg="black"
+              color="white"
+              p={2}
+              rounded="md"
+              fontWeight="bold"
+            >
+              View on GitHub
+            </ChakraLink>
+          </Text>
+        </HStack>
+      </Card.Header>
+      <Card.Body color="fg.muted">{repository.description}</Card.Body>
+    </Card.Root>
+  );
+};
diff --git 
a/modules/research-framework/portal/src/components/repositories/index.tsx 
b/modules/research-framework/portal/src/components/repositories/index.tsx
new file mode 100644
index 0000000000..4b1e1ebf2d
--- /dev/null
+++ b/modules/research-framework/portal/src/components/repositories/index.tsx
@@ -0,0 +1,39 @@
+import { Container, VStack } from "@chakra-ui/react";
+import NavBar from "../NavBar";
+import { BsGithub } from "react-icons/bs";
+import { PageHeader } from "../PageHeader";
+import { MOCK_REPOSITORIES } from "../../data/MOCK_DATA";
+import { RepositoryCard } from "./RepositoryCard";
+
+const Repositories = () => {
+  return (
+    <>
+      <NavBar />
+
+      <Container maxW="container.lg" mt={8}>
+        <PageHeader
+          title="Repositories"
+          icon={<BsGithub />}
+          description="View the most viewed repositories on GitHub, all 
central to this page."
+        />
+
+        <VStack gap={4} mt={8}>
+          {
+            // This is the code that will be replaced by the code snippet
+            // from the instructions
+            MOCK_REPOSITORIES.map((repository) => {
+              return (
+                <RepositoryCard
+                  repository={repository}
+                  key={repository.githubUrl}
+                />
+              );
+            })
+          }
+        </VStack>
+      </Container>
+    </>
+  );
+};
+
+export default Repositories;
diff --git a/modules/research-framework/portal/src/components/ui/color-mode.tsx 
b/modules/research-framework/portal/src/components/ui/color-mode.tsx
new file mode 100644
index 0000000000..f93feabca9
--- /dev/null
+++ b/modules/research-framework/portal/src/components/ui/color-mode.tsx
@@ -0,0 +1,107 @@
+"use client"
+
+import type { IconButtonProps, SpanProps } from "@chakra-ui/react"
+import { ClientOnly, IconButton, Skeleton, Span } from "@chakra-ui/react"
+import { ThemeProvider, useTheme } from "next-themes"
+import type { ThemeProviderProps } from "next-themes"
+import * as React from "react"
+import { LuMoon, LuSun } from "react-icons/lu"
+
+export interface ColorModeProviderProps extends ThemeProviderProps {}
+
+export function ColorModeProvider(props: ColorModeProviderProps) {
+  return (
+    <ThemeProvider attribute="class" disableTransitionOnChange {...props} />
+  )
+}
+
+export type ColorMode = "light" | "dark"
+
+export interface UseColorModeReturn {
+  colorMode: ColorMode
+  setColorMode: (colorMode: ColorMode) => void
+  toggleColorMode: () => void
+}
+
+export function useColorMode(): UseColorModeReturn {
+  const { resolvedTheme, setTheme } = useTheme()
+  const toggleColorMode = () => {
+    setTheme(resolvedTheme === "dark" ? "light" : "dark")
+  }
+  return {
+    colorMode: resolvedTheme as ColorMode,
+    setColorMode: setTheme,
+    toggleColorMode,
+  }
+}
+
+export function useColorModeValue<T>(light: T, dark: T) {
+  const { colorMode } = useColorMode()
+  return colorMode === "dark" ? dark : light
+}
+
+export function ColorModeIcon() {
+  const { colorMode } = useColorMode()
+  return colorMode === "dark" ? <LuMoon /> : <LuSun />
+}
+
+interface ColorModeButtonProps extends Omit<IconButtonProps, "aria-label"> {}
+
+export const ColorModeButton = React.forwardRef<
+  HTMLButtonElement,
+  ColorModeButtonProps
+>(function ColorModeButton(props, ref) {
+  const { toggleColorMode } = useColorMode()
+  return (
+    <ClientOnly fallback={<Skeleton boxSize="8" />}>
+      <IconButton
+        onClick={toggleColorMode}
+        variant="ghost"
+        aria-label="Toggle color mode"
+        size="sm"
+        ref={ref}
+        {...props}
+        css={{
+          _icon: {
+            width: "5",
+            height: "5",
+          },
+        }}
+      >
+        <ColorModeIcon />
+      </IconButton>
+    </ClientOnly>
+  )
+})
+
+export const LightMode = React.forwardRef<HTMLSpanElement, SpanProps>(
+  function LightMode(props, ref) {
+    return (
+      <Span
+        color="fg"
+        display="contents"
+        className="chakra-theme light"
+        colorPalette="gray"
+        colorScheme="light"
+        ref={ref}
+        {...props}
+      />
+    )
+  },
+)
+
+export const DarkMode = React.forwardRef<HTMLSpanElement, SpanProps>(
+  function DarkMode(props, ref) {
+    return (
+      <Span
+        color="fg"
+        display="contents"
+        className="chakra-theme dark"
+        colorPalette="gray"
+        colorScheme="dark"
+        ref={ref}
+        {...props}
+      />
+    )
+  },
+)
diff --git 
a/modules/research-framework/portal/src/components/ui/input-group.tsx 
b/modules/research-framework/portal/src/components/ui/input-group.tsx
new file mode 100644
index 0000000000..5d8fb32ad2
--- /dev/null
+++ b/modules/research-framework/portal/src/components/ui/input-group.tsx
@@ -0,0 +1,53 @@
+import type { BoxProps, InputElementProps } from "@chakra-ui/react"
+import { Group, InputElement } from "@chakra-ui/react"
+import * as React from "react"
+
+export interface InputGroupProps extends BoxProps {
+  startElementProps?: InputElementProps
+  endElementProps?: InputElementProps
+  startElement?: React.ReactNode
+  endElement?: React.ReactNode
+  children: React.ReactElement<InputElementProps>
+  startOffset?: InputElementProps["paddingStart"]
+  endOffset?: InputElementProps["paddingEnd"]
+}
+
+export const InputGroup = React.forwardRef<HTMLDivElement, InputGroupProps>(
+  function InputGroup(props, ref) {
+    const {
+      startElement,
+      startElementProps,
+      endElement,
+      endElementProps,
+      children,
+      startOffset = "6px",
+      endOffset = "6px",
+      ...rest
+    } = props
+
+    const child =
+      React.Children.only<React.ReactElement<InputElementProps>>(children)
+
+    return (
+      <Group ref={ref} {...rest}>
+        {startElement && (
+          <InputElement pointerEvents="none" {...startElementProps}>
+            {startElement}
+          </InputElement>
+        )}
+        {React.cloneElement(child, {
+          ...(startElement && {
+            ps: `calc(var(--input-height) - ${startOffset})`,
+          }),
+          ...(endElement && { pe: `calc(var(--input-height) - ${endOffset})` 
}),
+          ...children.props,
+        })}
+        {endElement && (
+          <InputElement placement="end" {...endElementProps}>
+            {endElement}
+          </InputElement>
+        )}
+      </Group>
+    )
+  },
+)
diff --git a/modules/research-framework/portal/src/components/ui/provider.tsx 
b/modules/research-framework/portal/src/components/ui/provider.tsx
new file mode 100644
index 0000000000..c1f896c87b
--- /dev/null
+++ b/modules/research-framework/portal/src/components/ui/provider.tsx
@@ -0,0 +1,12 @@
+"use client";
+
+import { ChakraProvider, defaultSystem } from "@chakra-ui/react";
+import { ColorModeProvider, type ColorModeProviderProps } from "./color-mode";
+
+export function Provider(props: ColorModeProviderProps) {
+  return (
+    <ChakraProvider value={defaultSystem}>
+      <ColorModeProvider {...props} />
+    </ChakraProvider>
+  );
+}
diff --git a/modules/research-framework/portal/src/data/MOCK_DATA.js 
b/modules/research-framework/portal/src/data/MOCK_DATA.js
new file mode 100644
index 0000000000..774b31c38b
--- /dev/null
+++ b/modules/research-framework/portal/src/data/MOCK_DATA.js
@@ -0,0 +1,555 @@
+export const MOCK_PROJECTS = [
+  {
+    "metadata": {
+      "type": "notebook",
+      "title": "A new framework for molecular sciences",
+      "slug": "a-new-framework-for-molecular-sciences",
+      "description": "This study presents a new framework for molecular 
sciences that can be used to study the molecular structure of molecules and 
their interactions with other molecules. The framework is based on the concept 
of molecular structure and is designed to be used in conjunction with other 
molecular sciences tools.",
+      "author": {
+        "name": "Dr. John Doe",
+        "avatar": 
"https://media.istockphoto.com/id/1356638196/photo/teenage-student-using-the-microscope-in-the-laboratory.jpg?s=612x612&w=0&k=20&c=v4ZITw5Wm3QmlcNRssQjShgE5l61E9TW62kK3k-G3jY=";,
+        "role": "contributor"
+      },
+      "tags": ["science", "molecular", "framework"],
+      "date": "2021-01-01",
+      "images": {
+        "headerImage": 
"https://scitechdaily.com/images/Artistic-Molecular-Structure.jpg";
+      },
+    },
+    "notebookViewer": "https://jupyter.org/try-jupyter/lab/";
+  },
+  {
+    "metadata": {
+      "type": "notebook",
+      "title": "The role of machine learning in molecular sciences",
+      "slug": "the-role-of-machine-learning-in-molecular-sciences",
+      "description": "This study presents the role of machine learning in 
molecular sciences and how it can be used to study the molecular structure of 
molecules and their interactions with other molecules. The study is based on 
the concept of molecular structure and is designed to be used in conjunction 
with other molecular sciences tools.",
+      "author": {
+        "name": "Dr. Jane Doe",
+        "avatar": 
"https://media.istockphoto.com/id/1356638196/photo/teenage-student-using-the-microscope-in-the-laboratory.jpg?s=612x612&w=0&k=20&c=v4ZITw5Wm3QmlcNRssQjShgE5l61E9TW62kK3k-G3jY=";,
+        "role": "contributor"
+      },
+      "tags": ["science", "molecular", "machine learning"],
+      "date": "2021-01-02",
+      "images": {
+        "headerImage": 
"https://scitechdaily.com/images/Artistic-Molecular-Structure.jpg";
+      },
+    },
+    "notebookViewer": "https://jupyter.org/try-jupyter/lab/";
+  },
+  {
+    "metadata": {
+      "type": "notebook",
+      "title": "The future of molecular sciences",
+      "slug": "the-future-of-molecular-sciences",
+      "description": "This study presents the future of molecular sciences and 
how it can be used to study the molecular structure of molecules and their 
interactions with other molecules. The study is based on the concept of 
molecular structure and is designed to be used in conjunction with other 
molecular sciences tools.",
+      "author": {
+        "name": "Dr. James Doe",
+        "avatar": 
"https://media.istockphoto.com/id/1356638196/photo/teenage-student-using-the-microscope-in-the-laboratory.jpg?s=612x612&w=0&k=20&c=v4ZITw5Wm3QmlcNRssQjShgE5l61E9TW62kK3k-G3jY=";,
+        "role": "contributor"
+      },
+      "tags": ["science", "molecular", "future"],
+      "date": "2021-01-03",
+      "images": {
+        "headerImage": 
"https://scitechdaily.com/images/Artistic-Molecular-Structure.jpg";
+      },
+    },
+    "notebookViewer": "https://jupyter.org/try-jupyter/lab/";
+  },
+  {
+    "metadata": {
+      "type": "repository",
+      "title": "Apache Airavata Repository",
+      "slug": "apache-airavata-repository",
+      "description": "Apache Airavata is a software framework for executing 
and managing computational workflows on distributed computing resources 
including local clusters, supercomputers, national grids, academic and 
commercial clouds.",
+      "author": {
+        "name": "Apache Software Foundation",
+        "avatar": "https://avatars.githubusercontent.com/u/47359?v=4";,
+        "role": "contributor"
+      },
+      "tags": ["apache", "airavata", "repository"],
+      "date": "2021-01-01",
+      "images": {
+        "headerImage": 
"https://www.gatech.edu/sites/default/files/styles/hero_16_9_extra_extra_large_1800x1013_/public/2022-04/Tech-tower-wreck.jpg?h=fb7e085a&itok=MGAERbjU";
+      },
+    },
+    "repositoryUrl": "https://github.com/apache/airavata";
+  },
+  {
+    "metadata": {
+      "type": "repository",
+      "title": "TensorFlow Repository",
+      "slug": "tensorflow-repository",
+      "description": "TensorFlow is an end-to-end open-source platform for 
machine learning, providing a comprehensive, flexible ecosystem of tools, 
libraries, and community resources.",
+      "author": {
+        "name": "Google",
+        "avatar": "https://avatars.githubusercontent.com/u/1342004?v=4";,
+        "role": "contributor"
+      },
+      "tags": ["tensorflow", "machine learning", "repository"],
+      "date": "2022-05-15",
+      "images": {
+        "headerImage": 
"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhKis9ECId8eIwn_p0SVMBt3a1vfvKOcOZXy6zK0fWoyzXnzQTguKc2CV__6oI1Pwg22NjWsErpDKqjwQdzjilvmqwWkXPj2ncglphh6mAhpoZ_QXQiDwxnwo-GjKEP0fEOb3uBlNlh9sc/s1600/tensorflow2objectdetection.png";
+      },
+    },
+    "repositoryUrl": "https://github.com/tensorflow/tensorflow";,
+  },
+  {
+    "metadata": {
+      "type": "repository",
+      "title": "PyTorch Repository",
+      "slug": "pytorch-repository",
+      "description": "PyTorch is an open-source machine learning framework 
that accelerates the path from research prototyping to production deployment.",
+      "author": {
+        "name": "Facebook",
+        "avatar": "https://avatars.githubusercontent.com/u/69631?v=4";,
+        "role": "contributor"
+      },
+      "tags": ["pytorch", "machine learning", "repository"],
+      "date": "2023-03-10",
+      "images": {
+        "headerImage": 
"https://pytorch.org/assets/images/community-events-recap/fg1.jpg";
+      },
+    },
+    "repositoryUrl": "https://github.com/pytorch/pytorch";
+  }
+];
+
+export const MOCK_DATASETS = [
+  {
+    "metadata": {
+      "type": "dataset",
+      "title": "Dataset A",
+      "slug": "dataset-a",
+      "description": "Dataset A is the best dataset for machine learning 
projects. It contains a wide variety of data points and features that can be 
used to train and test machine learning models.",
+      "author": {
+        "name": "Facebook",
+        "avatar": "https://avatars.githubusercontent.com/u/69631?v=4";,
+        "role": "contributor"
+      },
+      "tags": ["pytorch", "machine learning", "repository"],
+      "date": "2023-03-10",
+      "images": {
+        "headerImage": 
"https://pytorch.org/assets/images/community-events-recap/fg1.jpg";
+      },
+    },
+    "datasetUrl": 
"https://www.kaggle.com/datasets/muhammadtahir194/movies-dataset-tmdb-top-rated";,
+    "private": true,
+  },
+  {
+    "metadata": {
+      "type": "dataset",
+      "title": "Dataset B",
+      "slug": "dataset-b",
+      "description": "Dataset B is the best dataset for machine learning 
projects. It contains a wide variety of data points and features that can be 
used to train and test machine learning models.",
+      "author": {
+        "name": "Facebook",
+        "avatar": "https://avatars.githubusercontent.com/u/69631?v=4";,
+        "role": "contributor"
+      },
+      "tags": ["pytorch", "machine learning", "repository"],
+      "date": "2023-03-10",
+      "images": {
+        "headerImage": 
"https://pytorch.org/assets/images/community-events-recap/fg1.jpg";
+      },
+    },
+    "datasetUrl": 
"https://www.kaggle.com/datasets/muhammadtahir194/movies-dataset-tmdb-top-rated";,
+    "private": false,
+  },
+  {
+    "metadata": {
+      "type": "dataset",
+      "title": "Dataset C",
+      "slug": "dataset-c",
+      "description": "Dataset C is the best dataset for machine learning 
projects. It contains a wide variety of data points and features that can be 
used to train and test machine learning models.",
+      "author": {
+        "name": "Facebook",
+        "avatar": "https://avatars.githubusercontent.com/u/69631?v=4";,
+        "role": "contributor"
+      },
+      "tags": ["pytorch", "machine learning", "repository"],
+      "date": "2023-03-10",
+      "images": {
+        "headerImage": 
"https://pytorch.org/assets/images/community-events-recap/fg1.jpg";
+      },
+    },
+    "datasetUrl": 
"https://www.kaggle.com/datasets/muhammadtahir194/movies-dataset-tmdb-top-rated";,
+    "private": true,
+  },
+];
+
+export const MOCK_SESSIONS = [
+  {
+    "sessionId": "1",
+    "title": "Jupyter",
+    "started": "4 hours ago",
+    "models": ["v1", "v2"],
+    "datasets": ["A", "B", "C"],
+    "nodes": 1,
+    "ram": "8GB",
+    "storage": "40GB",
+    "status": "running"
+  },
+  {
+    "sessionId": "2",
+    "title": "Jupyter",
+    "started": "2 hours ago",
+    "models": ["v1", "v2"],
+    "datasets": ["A", "B", "C"],
+    "nodes": 1,
+    "ram": "8GB",
+    "storage": "40GB",
+    "status": "running"
+  },
+  {
+    "sessionId": "3",
+    "title": "VSCode",
+    "started": "1 hours ago",
+    "models": ["v1", "v2"],
+    "datasets": ["A", "B", "C"],
+    "nodes": 1,
+    "ram": "8GB",
+    "storage": "40GB",
+    "status": "stopped"
+  },
+  {
+    "sessionId": "4",
+    "title": "Jupyter",
+    "started": "1 hours ago",
+    "models": ["v1", "v2"],
+    "datasets": ["A", "B", "C"],
+    "nodes": 1,
+    "ram": "8GB",
+    "storage": "40GB",
+    "status": "stopped"
+  },
+  {
+    "sessionId": "5",
+    "title": "VSCode",
+    "started": "1 hours ago",
+    "models": ["v1", "v2"],
+    "datasets": ["A", "B", "C"],
+    "nodes": 1,
+    "ram": "8GB",
+    "storage": "40GB",
+    "status": "stopped"
+  },
+  {
+    "sessionId": "6",
+    "title": "VSCode",
+    "started": "1 hours ago",
+    "models": ["v1", "v2"],
+    "datasets": ["A", "B", "C"],
+    "nodes": 1,
+    "ram": "8GB",
+    "storage": "40GB",
+    "status": "stopped"
+  },
+  {
+    "sessionId": "7",
+    "title": "Jupyter",
+    "started": "1 hours ago",
+    "models": ["v1", "v2"],
+    "datasets": ["A", "B", "C"],
+    "nodes": 1,
+    "ram": "8GB",
+    "storage": "40GB",
+    "status": "running"
+  },
+];
+
+
+
+
+export const MOCK_NOTEBOOKS = [
+  {
+    "type": "notebook",
+    "title": "A new framework for molecular sciences",
+    "slug": "a-new-framework-for-molecular-sciences",
+    "description": "This study presents a new framework for molecular sciences 
that can be used to study the molecular structure of molecules and their 
interactions with other molecules. The framework is based on the concept of 
molecular structure and is designed to be used in conjunction with other 
molecular sciences tools.",
+    "author": {
+      "name": "Dr. John Doe",
+      "avatar": 
"https://media.istockphoto.com/id/1356638196/photo/teenage-student-using-the-microscope-in-the-laboratory.jpg?s=612x612&w=0&k=20&c=v4ZITw5Wm3QmlcNRssQjShgE5l61E9TW62kK3k-G3jY=";,
+      "role": "contributor"
+    },
+    "tags": ["science", "molecular", "framework"],
+    "date": "2021-01-01",
+    "images": {
+      "headerImage": 
"https://scitechdaily.com/images/Artistic-Molecular-Structure.jpg";
+    },
+    "notebookViewer": "https://jupyter.org/try-jupyter/lab/";
+  },
+  {
+    "type": "notebook",
+    "title": "The role of machine learning in molecular sciences",
+    "slug": "the-role-of-machine-learning-in-molecular-sciences",
+    "description": "This study presents the role of machine learning in 
molecular sciences and how it can be used to study the molecular structure of 
molecules and their interactions with other molecules. The study is based on 
the concept of molecular structure and is designed to be used in conjunction 
with other molecular sciences tools.",
+    "author": {
+      "name": "Dr. Jane Doe",
+      "avatar": 
"https://media.istockphoto.com/id/1356638196/photo/teenage-student-using-the-microscope-in-the-laboratory.jpg?s=612x612&w=0&k=20&c=v4ZITw5Wm3QmlcNRssQjShgE5l61E9TW62kK3k-G3jY=";,
+      "role": "contributor"
+    },
+    "tags": ["science", "molecular", "machine learning"],
+    "date": "2021-01-02",
+    "images": {
+      "headerImage": 
"https://scitechdaily.com/images/Artistic-Molecular-Structure.jpg";
+    },
+    "notebookViewer": "https://jupyter.org/try-jupyter/lab/";
+  },
+  {
+    "type": "notebook",
+    "title": "The future of molecular sciences",
+    "slug": "the-future-of-molecular-sciences",
+    "description": "This study presents the future of molecular sciences and 
how it can be used to study the molecular structure of molecules and their 
interactions with other molecules. The study is based on the concept of 
molecular structure and is designed to be used in conjunction with other 
molecular sciences tools.",
+    "author": {
+      "name": "Dr. James Doe",
+      "avatar": 
"https://media.istockphoto.com/id/1356638196/photo/teenage-student-using-the-microscope-in-the-laboratory.jpg?s=612x612&w=0&k=20&c=v4ZITw5Wm3QmlcNRssQjShgE5l61E9TW62kK3k-G3jY=";,
+      "role": "contributor"
+    },
+    "tags": ["science", "molecular", "future"],
+    "date": "2021-01-03",
+    "images": {
+      "headerImage": 
"https://scitechdaily.com/images/Artistic-Molecular-Structure.jpg";
+    },
+    "notebookViewer": "https://jupyter.org/try-jupyter/lab/";
+  },
+  {
+    "type": "repository",
+    "title": "Apache Airavata Repository",
+    "slug": "apache-airavata-repository",
+    "description": "Apache Airavata is a software framework for executing and 
managing computational workflows on distributed computing resources including 
local clusters, supercomputers, national grids, academic and commercial 
clouds.",
+    "author": {
+      "name": "Apache Software Foundation",
+      "avatar": "https://avatars.githubusercontent.com/u/47359?v=4";,
+      "role": "contributor"
+    },
+    "tags": ["apache", "airavata", "repository"],
+    "date": "2021-01-01",
+    "images": {
+      "headerImage": 
"https://www.gatech.edu/sites/default/files/styles/hero_16_9_extra_extra_large_1800x1013_/public/2022-04/Tech-tower-wreck.jpg?h=fb7e085a&itok=MGAERbjU";
+    },
+    "repositoryUrl": "https://github.com/apache/airavata";
+  },
+  {
+    "type": "repository",
+    "title": "TensorFlow Repository",
+    "slug": "tensorflow-repository",
+    "description": "TensorFlow is an end-to-end open-source platform for 
machine learning, providing a comprehensive, flexible ecosystem of tools, 
libraries, and community resources.",
+    "author": {
+      "name": "Google",
+      "avatar": "https://avatars.githubusercontent.com/u/1342004?v=4";,
+      "role": "contributor"
+    },
+    "tags": ["tensorflow", "machine learning", "repository"],
+    "date": "2022-05-15",
+    "images": {
+      "headerImage": 
"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhKis9ECId8eIwn_p0SVMBt3a1vfvKOcOZXy6zK0fWoyzXnzQTguKc2CV__6oI1Pwg22NjWsErpDKqjwQdzjilvmqwWkXPj2ncglphh6mAhpoZ_QXQiDwxnwo-GjKEP0fEOb3uBlNlh9sc/s1600/tensorflow2objectdetection.png";
+    },
+    "repositoryUrl": "https://github.com/tensorflow/tensorflow";,
+  },
+  {
+    "type": "repository",
+    "title": "PyTorch Repository",
+    "slug": "pytorch-repository",
+    "description": "PyTorch is an open-source machine learning framework that 
accelerates the path from research prototyping to production deployment.",
+    "author": {
+      "name": "Facebook",
+      "avatar": "https://avatars.githubusercontent.com/u/69631?v=4";,
+      "role": "contributor"
+    },
+    "tags": ["pytorch", "machine learning", "repository"],
+    "date": "2023-03-10",
+    "images": {
+      "headerImage": 
"https://pytorch.org/assets/images/community-events-recap/fg1.jpg";
+    },
+    "repositoryUrl": "https://github.com/pytorch/pytorch";
+  }
+];
+
+
+export const MOCK_REPOSITORIES = [
+  {
+    "githubUrl": "https://github.com/apache/airavata";,
+    "title": "Apache Airavata Dataset",
+    "description": "Apache Airavata is a software framework for executing and 
managing computational workflows on distributed computing resources including 
local clusters, supercomputers, national grids, academic and commercial 
clouds.",
+    "date": "2021-01-01",
+  },
+  {
+    "githubUrl": "https://github.com/tensorflow/tensorflow";,
+    "title": "TensorFlow",
+    "description": "TensorFlow is an end-to-end open-source platform for 
machine learning, providing a comprehensive, flexible ecosystem of tools, 
libraries, and community resources.",
+    "date": "2022-05-15",
+  },
+  {
+    "githubUrl": "https://github.com/pytorch/pytorch";,
+    "title": "PyTorch",
+    "description": "PyTorch is an open-source machine learning framework that 
accelerates the path from research prototyping to production deployment.",
+    "date": "2023-03-10",
+  },
+  {
+    "githubUrl": "https://github.com/apache/spark";,
+    "title": "Apache Spark",
+    "description": "Apache Spark is a unified analytics engine for large-scale 
data processing, with built-in modules for SQL, streaming, machine learning, 
and graph processing.",
+    "date": "2020-11-20",
+  },
+  {
+    "githubUrl": "https://github.com/microsoft/vscode";,
+    "title": "Visual Studio Code",
+    "description": "Visual Studio Code is a lightweight but powerful source 
code editor that runs on your desktop and is available for Windows, macOS, and 
Linux.",
+    "date": "2021-09-30",
+  },
+  {
+    "githubUrl": "https://github.com/nodejs/node";,
+    "title": "Node.js",
+    "description": "Node.js is a JavaScript runtime built on Chrome's V8 
JavaScript engine, designed for building scalable network applications.",
+    "date": "2022-07-18",
+  },
+  {
+    "githubUrl": "https://github.com/facebook/react";,
+    "title": "React",
+    "description": "React is a declarative, efficient, and flexible JavaScript 
library for building user interfaces, maintained by Facebook.",
+    "date": "2023-01-05",
+  },
+  {
+    "githubUrl": "https://github.com/django/django";,
+    "title": "Django",
+    "description": "Django is a high-level Python web framework that 
encourages rapid development and clean, pragmatic design.",
+    "date": "2021-12-10",
+  },
+  {
+    "githubUrl": "https://github.com/rust-lang/rust";,
+    "title": "Rust",
+    "description": "Rust is a multi-paradigm programming language designed for 
performance and safety, especially safe concurrency.",
+    "date": "2020-06-25",
+  },
+  {
+    "githubUrl": "https://github.com/kubernetes/kubernetes";,
+    "title": "Kubernetes",
+    "description": "Kubernetes is an open-source system for automating 
deployment, scaling, and management of containerized applications.",
+    "date": "2022-10-14",
+  }
+];
+
+
+export const MOCK_MODELS = [
+  {
+    "appModuleId": "AiravataAgent_6a584041-c8ee-4f89-b1cc-6905093b504c",
+    "appModuleName": "AiravataAgent",
+    "appModuleVersion": "1.0.0",
+    "appModuleDescription": "Airavata Agent Application",
+    "url": 
"https://cybershuttle.org/api/applications/AiravataAgent_6a584041-c8ee-4f89-b1cc-6905093b504c/";,
+    "applicationInterface": 
"https://cybershuttle.org/api/applications/AiravataAgent_6a584041-c8ee-4f89-b1cc-6905093b504c/application_interface/";,
+    "applicationDeployments": 
"https://cybershuttle.org/api/applications/AiravataAgent_6a584041-c8ee-4f89-b1cc-6905093b504c/application_deployments/";,
+    "userHasWriteAccess": false
+  },
+  {
+    "appModuleId": "AlphaFold2_c177803a-f1d2-4559-a306-2e1b9cef6496",
+    "appModuleName": "AlphaFold2",
+    "appModuleVersion": "2.2.0",
+    "appModuleDescription": "AlphaFold is an AI system developed by DeepMind 
that makes state-of-the-art accurate predictions of a protein’s structure from 
its amino-acid sequence.",
+    "url": 
"https://cybershuttle.org/api/applications/AlphaFold2_c177803a-f1d2-4559-a306-2e1b9cef6496/";,
+    "applicationInterface": 
"https://cybershuttle.org/api/applications/AlphaFold2_c177803a-f1d2-4559-a306-2e1b9cef6496/application_interface/";,
+    "applicationDeployments": 
"https://cybershuttle.org/api/applications/AlphaFold2_c177803a-f1d2-4559-a306-2e1b9cef6496/application_deployments/";,
+    "userHasWriteAccess": false
+  },
+  {
+    "appModuleId": "Amber_pmemd_7fe7fb4b-38a7-45c4-a4e5-2ca9f4f49f3b",
+    "appModuleName": "Amber_pmemd",
+    "appModuleVersion": null,
+    "appModuleDescription": "Assisted Model Building with Energy Refinement MD 
Package with Particle Mesh Ewald summation for electrostatic interactions",
+    "url": 
"https://cybershuttle.org/api/applications/Amber_pmemd_7fe7fb4b-38a7-45c4-a4e5-2ca9f4f49f3b/";,
+    "applicationInterface": 
"https://cybershuttle.org/api/applications/Amber_pmemd_7fe7fb4b-38a7-45c4-a4e5-2ca9f4f49f3b/application_interface/";,
+    "applicationDeployments": 
"https://cybershuttle.org/api/applications/Amber_pmemd_7fe7fb4b-38a7-45c4-a4e5-2ca9f4f49f3b/application_deployments/";,
+    "userHasWriteAccess": false
+  },
+  {
+    "appModuleId": "Amber_pmemd_CUDA_25c95122-ac00-4ba6-a834-33f129ad6eb2",
+    "appModuleName": "Amber_pmemd_CUDA",
+    "appModuleVersion": "Amber.22-CUDA",
+    "appModuleDescription": "GPU openmpi version of AMBER pmemd",
+    "url": 
"https://cybershuttle.org/api/applications/Amber_pmemd_CUDA_25c95122-ac00-4ba6-a834-33f129ad6eb2/";,
+    "applicationInterface": 
"https://cybershuttle.org/api/applications/Amber_pmemd_CUDA_25c95122-ac00-4ba6-a834-33f129ad6eb2/application_interface/";,
+    "applicationDeployments": 
"https://cybershuttle.org/api/applications/Amber_pmemd_CUDA_25c95122-ac00-4ba6-a834-33f129ad6eb2/application_deployments/";,
+    "userHasWriteAccess": false
+  },
+  {
+    "appModuleId": "CyberFaCESAgent_74734794-25db-413b-972b-dffbfab346ca",
+    "appModuleName": "CyberFaCESAgent",
+    "appModuleVersion": "1.0.0",
+    "appModuleDescription": "CyberFaCES Agent",
+    "url": 
"https://cybershuttle.org/api/applications/CyberFaCESAgent_74734794-25db-413b-972b-dffbfab346ca/";,
+    "applicationInterface": 
"https://cybershuttle.org/api/applications/CyberFaCESAgent_74734794-25db-413b-972b-dffbfab346ca/application_interface/";,
+    "applicationDeployments": 
"https://cybershuttle.org/api/applications/CyberFaCESAgent_74734794-25db-413b-972b-dffbfab346ca/application_deployments/";,
+    "userHasWriteAccess": false
+  },
+  {
+    "appModuleId": "Echo_88202f35-beef-414a-84a4-6ee9dd4f5a84",
+    "appModuleName": "Echo",
+    "appModuleVersion": "",
+    "appModuleDescription": "",
+    "url": 
"https://cybershuttle.org/api/applications/Echo_88202f35-beef-414a-84a4-6ee9dd4f5a84/";,
+    "applicationInterface": 
"https://cybershuttle.org/api/applications/Echo_88202f35-beef-414a-84a4-6ee9dd4f5a84/application_interface/";,
+    "applicationDeployments": 
"https://cybershuttle.org/api/applications/Echo_88202f35-beef-414a-84a4-6ee9dd4f5a84/application_deployments/";,
+    "userHasWriteAccess": false
+  },
+  {
+    "appModuleId": "Gaussian16_43c4b549-f8c2-4024-accc-d5e965a0025a",
+    "appModuleName": "Gaussian16",
+    "appModuleVersion": "Gaussian16/c.02",
+    "appModuleDescription": "Quantum chemistry application",
+    "url": 
"https://cybershuttle.org/api/applications/Gaussian16_43c4b549-f8c2-4024-accc-d5e965a0025a/";,
+    "applicationInterface": 
"https://cybershuttle.org/api/applications/Gaussian16_43c4b549-f8c2-4024-accc-d5e965a0025a/application_interface/";,
+    "applicationDeployments": 
"https://cybershuttle.org/api/applications/Gaussian16_43c4b549-f8c2-4024-accc-d5e965a0025a/application_deployments/";,
+    "userHasWriteAccess": false
+  },
+  {
+    "appModuleId": 
"GEM_Magnetic_Reconnection_Challenge_Problem_43026dd1-5229-4d80-8be3-32c7b876e157",
+    "appModuleName": "GEM_Magnetic_Reconnection_Challenge_Problem",
+    "appModuleVersion": "1.0",
+    "appModuleDescription": "\"Magnetic reconnection is studied in a simple 
Harris sheet configuration with a specified set of initial conditions, 
including a finite amplitude, magnetic island perturbation to trigger the 
dynamics.\" 
[https://agupubs.onlinelibrary.wiley.com/doi/abs/10.1029/1999JA900449]";,
+    "url": 
"https://cybershuttle.org/api/applications/GEM_Magnetic_Reconnection_Challenge_Problem_43026dd1-5229-4d80-8be3-32c7b876e157/";,
+    "applicationInterface": 
"https://cybershuttle.org/api/applications/GEM_Magnetic_Reconnection_Challenge_Problem_43026dd1-5229-4d80-8be3-32c7b876e157/application_interface/";,
+    "applicationDeployments": 
"https://cybershuttle.org/api/applications/GEM_Magnetic_Reconnection_Challenge_Problem_43026dd1-5229-4d80-8be3-32c7b876e157/application_deployments/";,
+    "userHasWriteAccess": false
+  },
+  {
+    "appModuleId": "GkeyllAgent_c884a86e-5c5e-416d-93e9-59361ac87898",
+    "appModuleName": "GkeyllAgent",
+    "appModuleVersion": "1.0.0",
+    "appModuleDescription": "Gkeyll  Agent",
+    "url": 
"https://cybershuttle.org/api/applications/GkeyllAgent_c884a86e-5c5e-416d-93e9-59361ac87898/";,
+    "applicationInterface": 
"https://cybershuttle.org/api/applications/GkeyllAgent_c884a86e-5c5e-416d-93e9-59361ac87898/application_interface/";,
+    "applicationDeployments": 
"https://cybershuttle.org/api/applications/GkeyllAgent_c884a86e-5c5e-416d-93e9-59361ac87898/application_deployments/";,
+    "userHasWriteAccess": false
+  },
+  {
+    "appModuleId": 
"Groamcs_with_OptionalRestart_cd3e614c-c66c-4b67-b944-42f0adbaba16",
+    "appModuleName": "Gromacs_with_OptionalRestart",
+    "appModuleVersion": "2016.3",
+    "appModuleDescription": "Gromacs with optional restart from cpt file 
staged from a previous job ID provided in a restart text file",
+    "url": 
"https://cybershuttle.org/api/applications/Groamcs_with_OptionalRestart_cd3e614c-c66c-4b67-b944-42f0adbaba16/";,
+    "applicationInterface": 
"https://cybershuttle.org/api/applications/Groamcs_with_OptionalRestart_cd3e614c-c66c-4b67-b944-42f0adbaba16/application_interface/";,
+    "applicationDeployments": 
"https://cybershuttle.org/api/applications/Groamcs_with_OptionalRestart_cd3e614c-c66c-4b67-b944-42f0adbaba16/application_deployments/";,
+    "userHasWriteAccess": false
+  },
+  {
+    "appModuleId": "NAMD_76b9b262-de09-4ab7-b4d4-e89502405af7",
+    "appModuleName": "NAMD",
+    "appModuleVersion": "2.14/3.2022.07",
+    "appModuleDescription": "NAMD is a parallel, object-oriented molecular 
dynamics code designed for high-performance simulation of large biomolecular 
systems. Simulation preparation and analysis is integrated into the 
visualization package VMD. Visit the NAMD website for complete information.",
+    "url": 
"https://cybershuttle.org/api/applications/NAMD_76b9b262-de09-4ab7-b4d4-e89502405af7/";,
+    "applicationInterface": 
"https://cybershuttle.org/api/applications/NAMD_76b9b262-de09-4ab7-b4d4-e89502405af7/application_interface/";,
+    "applicationDeployments": 
"https://cybershuttle.org/api/applications/NAMD_76b9b262-de09-4ab7-b4d4-e89502405af7/application_deployments/";,
+    "userHasWriteAccess": false
+  },
+  {
+    "appModuleId": "VsCode_0545a9d1-2ab1-4312-b0e9-dfa8f470cb15",
+    "appModuleName": "VsCode",
+    "appModuleVersion": "V1.0",
+    "appModuleDescription": null,
+    "url": 
"https://cybershuttle.org/api/applications/VsCode_0545a9d1-2ab1-4312-b0e9-dfa8f470cb15/";,
+    "applicationInterface": 
"https://cybershuttle.org/api/applications/VsCode_0545a9d1-2ab1-4312-b0e9-dfa8f470cb15/application_interface/";,
+    "applicationDeployments": 
"https://cybershuttle.org/api/applications/VsCode_0545a9d1-2ab1-4312-b0e9-dfa8f470cb15/application_deployments/";,
+    "userHasWriteAccess": false
+  }
+];
\ No newline at end of file
diff --git a/modules/research-framework/portal/src/interfaces/AuthorType.tsx 
b/modules/research-framework/portal/src/interfaces/AuthorType.tsx
new file mode 100644
index 0000000000..7beaed29a3
--- /dev/null
+++ b/modules/research-framework/portal/src/interfaces/AuthorType.tsx
@@ -0,0 +1,5 @@
+export interface AuthorType {
+  name: string;
+  avatar: string;
+  role: string;
+}
diff --git a/modules/research-framework/portal/src/interfaces/DatasetType.tsx 
b/modules/research-framework/portal/src/interfaces/DatasetType.tsx
new file mode 100644
index 0000000000..c331b864d8
--- /dev/null
+++ b/modules/research-framework/portal/src/interfaces/DatasetType.tsx
@@ -0,0 +1,7 @@
+import { MetadataType } from "./MetadataType";
+
+export interface DatasetType {
+  metadata: MetadataType;
+  datasetUrl: string;
+  private: true | false;
+}
diff --git a/modules/research-framework/portal/src/interfaces/MetadataType.tsx 
b/modules/research-framework/portal/src/interfaces/MetadataType.tsx
new file mode 100644
index 0000000000..ba183d2185
--- /dev/null
+++ b/modules/research-framework/portal/src/interfaces/MetadataType.tsx
@@ -0,0 +1,14 @@
+import { AuthorType } from "./AuthorType";
+
+export interface MetadataType {
+  type: string;
+  title: string;
+  slug: string;
+  description: string;
+  author: AuthorType;
+  tags: string[];
+  date: string;
+  images: {
+    headerImage: string;
+  };
+}
diff --git a/modules/research-framework/portal/src/interfaces/ModelType.tsx 
b/modules/research-framework/portal/src/interfaces/ModelType.tsx
new file mode 100644
index 0000000000..7d1f933a11
--- /dev/null
+++ b/modules/research-framework/portal/src/interfaces/ModelType.tsx
@@ -0,0 +1,10 @@
+export interface ModelType {
+  appModuleId: string;
+  appModuleName: string;
+  appModuleVersion: string;
+  appModuleDescription: string;
+  url: string;
+  applicationInterface: string;
+  applicationDeployments: string;
+  userHasWriteAccess: boolean;
+}
diff --git a/modules/research-framework/portal/src/interfaces/ProjectType.tsx 
b/modules/research-framework/portal/src/interfaces/ProjectType.tsx
new file mode 100644
index 0000000000..b09bb66ced
--- /dev/null
+++ b/modules/research-framework/portal/src/interfaces/ProjectType.tsx
@@ -0,0 +1,7 @@
+import { MetadataType } from "./MetadataType";
+
+export interface ProjectType {
+  metadata: MetadataType;
+  notebookViewer?: string;
+  repositoryUrl?: string;
+}
diff --git a/modules/research-framework/portal/src/interfaces/SessionType.tsx 
b/modules/research-framework/portal/src/interfaces/SessionType.tsx
new file mode 100644
index 0000000000..fb483cc421
--- /dev/null
+++ b/modules/research-framework/portal/src/interfaces/SessionType.tsx
@@ -0,0 +1,11 @@
+export interface SessionType {
+  sessionId: string;
+  title: string;
+  started: string;
+  models: string[];
+  datasets: string[];
+  nodes: number;
+  ram: number;
+  storage: number;
+  status: "running" | "stopped";
+}
diff --git a/modules/research-framework/portal/src/main.tsx 
b/modules/research-framework/portal/src/main.tsx
new file mode 100644
index 0000000000..f12fb02d01
--- /dev/null
+++ b/modules/research-framework/portal/src/main.tsx
@@ -0,0 +1,12 @@
+import { Provider } from "@/components/ui/provider";
+import React from "react";
+import ReactDOM from "react-dom/client";
+import App from "./App";
+
+ReactDOM.createRoot(document.getElementById("root")!).render(
+  <React.StrictMode>
+    <Provider>
+      <App />
+    </Provider>
+  </React.StrictMode>
+);
diff --git a/modules/research-framework/portal/src/vite-env.d.ts 
b/modules/research-framework/portal/src/vite-env.d.ts
new file mode 100644
index 0000000000..11f02fe2a0
--- /dev/null
+++ b/modules/research-framework/portal/src/vite-env.d.ts
@@ -0,0 +1 @@
+/// <reference types="vite/client" />


Reply via email to