This is an automated email from the ASF dual-hosted git repository.
lahirujayathilake pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/airavata-custos.git
The following commit(s) were added to refs/heads/master by this push:
new bfdfb05db nav bar to be collapsible and made it responsive to screen
size (#411)
bfdfb05db is described below
commit bfdfb05dbd985259d20ecb462881491526864fc5
Author: Oluwatimi Omoteso <[email protected]>
AuthorDate: Fri Nov 15 11:43:07 2024 -0500
nav bar to be collapsible and made it responsive to screen size (#411)
* edited nav bar to be collapsible as well as made it responsive to screen
size
* fixed scroll issue with navbar
* Adjusted navbar drawer behavior for mobile and desktop consistency
---
custos-portal/package.json | 5 +-
custos-portal/src/components/NavContainer.tsx | 202 ++++++++++++++++++--------
custos-portal/src/index.tsx | 13 +-
custos-portal/src/lib/constants.ts | 10 +-
4 files changed, 150 insertions(+), 80 deletions(-)
diff --git a/custos-portal/package.json b/custos-portal/package.json
index 3e789cb76..a3893e3b5 100644
--- a/custos-portal/package.json
+++ b/custos-portal/package.json
@@ -22,7 +22,8 @@
"react-icons": "^5.3.0",
"react-oidc-context": "^3.1.0",
"react-router-dom": "^6.26.1",
- "react-tag-input-component": "^2.0.2"
+ "react-tag-input-component": "^2.0.2",
+ "serve": "^14.2.4"
},
"devDependencies": {
"@eslint/js": "^9.9.0",
@@ -38,4 +39,4 @@
"typescript-eslint": "^8.0.1",
"vite": "^5.4.1"
}
-}
\ No newline at end of file
+}
diff --git a/custos-portal/src/components/NavContainer.tsx
b/custos-portal/src/components/NavContainer.tsx
index ffa051262..3b2678907 100644
--- a/custos-portal/src/components/NavContainer.tsx
+++ b/custos-portal/src/components/NavContainer.tsx
@@ -1,3 +1,4 @@
+import React, { useState, useEffect, memo } from 'react';
import {
Grid,
GridItem,
@@ -7,16 +8,22 @@ import {
Icon,
Text,
Flex,
- Button
+ Button,
+ Drawer,
+ DrawerOverlay,
+ DrawerContent,
+ DrawerCloseButton,
+ DrawerBody,
+ useDisclosure,
+ Spacer
} from '@chakra-ui/react';
import { Link } from 'react-router-dom';
-import { FiUser, FiUsers } from "react-icons/fi";
+import { FiUser, FiUsers, FiChevronLeft, FiChevronRight, FiMenu } from
"react-icons/fi";
import { AiOutlineAppstore } from "react-icons/ai";
import { IconType } from "react-icons";
import { MdLogout } from "react-icons/md";
import { useAuth } from 'react-oidc-context';
-
interface NavContainerProps {
activeTab: string;
children: React.ReactNode;
@@ -27,86 +34,161 @@ interface NavItemProps {
icon: IconType;
text: string;
activeTab: string;
+ isCollapsed: boolean;
+ onClose: () => void;
}
-const NavItem = ({ to, icon, text, activeTab }: NavItemProps) => {
+const NavItem = memo(({ to, icon, text, activeTab, isCollapsed, onClose }:
NavItemProps) => {
const isActive = activeTab.toLowerCase() === text.toLowerCase();
return (
- <Link to={to}>
+ <Link to={to} onClick={onClose}>
<Stack
- direction='row'
- align='center'
+ direction="row"
+ align="center"
color={isActive ? 'black' : 'default.secondary'}
py={2}
px={1}
- _hover={{
- bg: 'gray.100',
- }}
- fontSize='sm'
+ _hover={{ bg: 'gray.100' }}
+ fontSize="sm"
>
<Icon as={icon} />
- <Text fontWeight='semibold'>{text}</Text>
+ {!isCollapsed && <Text fontWeight="semibold">{text}</Text>}
</Stack>
</Link>
- )
-}
+ );
+});
-
-export const NavContainer = ({ activeTab, children }: NavContainerProps) => {
+export const NavContainer = memo(({ activeTab, children }: NavContainerProps)
=> {
const auth = useAuth();
- return (
- <>
- <Grid templateColumns='repeat(15, 1fr)'>
- <GridItem colSpan={3} bg='#F7F7F7'>
- <Flex h='100vh' >
- <Box position='fixed'>
- <Flex justifyContent='space-between' direction="column"
h='100vh' p={4} >
- <Box>
- <Heading size='md'>
- Custos Auth Portal
- </Heading>
+
+ const [isCollapsed, setIsCollapsed] = useState(() => {
+ const saved = localStorage.getItem('navCollapsed');
+ return saved ? JSON.parse(saved) : false;
+ });
+
+ const [isMobile, setIsMobile] = useState(window.innerWidth < 768);
+ const { isOpen, onOpen, onClose } = useDisclosure();
- <Stack direction='column' mt={4}>
- <NavItem to='/applications' icon={AiOutlineAppstore}
text="Applications" activeTab={activeTab} />
- <NavItem to='/groups' icon={FiUsers} text="Groups"
activeTab={activeTab} />
- <NavItem to='/users' icon={FiUser} text="Users"
activeTab={activeTab} />
- </Stack>
- </Box>
+ useEffect(() => {
+ const handleResize = () => setIsMobile(window.innerWidth < 768);
+ window.addEventListener('resize', handleResize);
+ return () => window.removeEventListener('resize', handleResize);
+ }, []);
+
+ useEffect(() => {
+ localStorage.setItem('navCollapsed', JSON.stringify(isCollapsed));
+ }, [isCollapsed]);
+
+ const toggleCollapse = () => {
+ setIsCollapsed((prev: boolean) => !prev);
+ };
+ if (isMobile) {
+ return (
+ <>
+ <Box position="fixed" top={4} left={4} zIndex={10}>
+ <Button onClick={onOpen} variant="ghost">
+ <Icon as={FiMenu} w={6} h={6} />
+ </Button>
+ </Box>
+ <Drawer isOpen={isOpen} placement="left" onClose={onClose}>
+ <DrawerOverlay />
+ <DrawerContent bg="#F7F7F7">
+ <DrawerCloseButton />
+ <DrawerBody p={4} display="flex" flexDirection="column">
+ <Heading size="md">Custos Auth Portal</Heading>
+ <Stack direction="column" mt={4}>
+ <NavItem to="/applications" icon={AiOutlineAppstore}
text="Applications" activeTab={activeTab} isCollapsed={false} onClose={onClose}
/>
+ <NavItem to="/groups" icon={FiUsers} text="Groups"
activeTab={activeTab} isCollapsed={false} onClose={onClose} />
+ <NavItem to="/users" icon={FiUser} text="Users"
activeTab={activeTab} isCollapsed={false} onClose={onClose} />
+ </Stack>
+ <Spacer />
<Box>
- <Text fontWeight='bold'>
- {auth.user?.profile?.name}
- </Text>
- <Text fontSize='sm' color='gray.500'>
- {auth.user?.profile?.email}
- </Text>
- <Button
- variant='unstyled'
- w='fit-content'
- size='sm'
- _hover={{
- color: 'gray.500'
- }}
+ <Text fontWeight="bold">{auth.user?.profile?.name}</Text>
+ <Text fontSize="sm"
color="gray.500">{auth.user?.profile?.email}</Text>
+ <Button
+ variant="unstyled"
+ w="fit-content"
+ size="sm"
+ _hover={{ color: "gray.500" }}
onClick={async () => {
await auth.removeUser();
+ onClose();
}}
>
- <Flex alignItems='center' gap={2} w='fit-content'>
- <Icon as={MdLogout} />
- <Text as='span' >Logout</Text>
+ <Flex alignItems="center" gap={2} w="fit-content">
+ <Icon as={MdLogout} />
+ <Text as="span">Logout</Text>
</Flex>
</Button>
</Box>
- </Flex>
- </Box>
- </Flex>
-
- </GridItem>
- <GridItem colSpan={12} p={16}>
+ </DrawerBody>
+ </DrawerContent>
+ </Drawer>
+ <Box p={5} pt={16}>
{children}
- </GridItem>
- </Grid>
- </>
- )
-}
+ </Box>
+ </>
+ );
+ }
+ return (
+ <Grid templateColumns="repeat(15, 1fr)" minHeight="100vh">
+ <GridItem
+ colSpan={isCollapsed ? 1 : 3}
+ minWidth={isCollapsed ? "60px" : "240px"}
+ maxWidth={isCollapsed ? "60px" : "240px"}
+ bg="#F7F7F7"
+ position="fixed"
+ h="100vh"
+ >
+ <Flex h="100vh" p={4} direction="column"
justifyContent="space-between">
+ <Box>
+ <Flex justifyContent="space-between" align="center">
+ {!isCollapsed && <Heading size="md">Custos Auth Portal</Heading>}
+ <Button variant="ghost" onClick={toggleCollapse} size="sm">
+ <Icon as={isCollapsed ? FiChevronRight : FiChevronLeft} />
+ </Button>
+ </Flex>
+ <Stack direction="column" mt={4}>
+ <NavItem to="/applications" icon={AiOutlineAppstore}
text="Applications" activeTab={activeTab} isCollapsed={isCollapsed}
onClose={onClose} />
+ <NavItem to="/groups" icon={FiUsers} text="Groups"
activeTab={activeTab} isCollapsed={isCollapsed} onClose={onClose} />
+ <NavItem to="/users" icon={FiUser} text="Users"
activeTab={activeTab} isCollapsed={isCollapsed} onClose={onClose} />
+ </Stack>
+ </Box>
+ <Box mt="auto">
+ {!isCollapsed && (
+ <>
+ <Text fontWeight="bold">{auth.user?.profile?.name}</Text>
+ <Text fontSize="sm"
color="gray.500">{auth.user?.profile?.email}</Text>
+ </>
+ )}
+ <Button
+ variant="unstyled"
+ w="fit-content"
+ size="sm"
+ _hover={{ color: "gray.500" }}
+ onClick={async () => {
+ await auth.removeUser();
+ }}
+ >
+ <Flex alignItems="center" gap={2} w="fit-content">
+ <Icon as={MdLogout} />
+ {!isCollapsed && <Text as="span">Logout</Text>}
+ </Flex>
+ </Button>
+ </Box>
+ </Flex>
+ </GridItem>
+ <GridItem
+ colSpan={isCollapsed ? 14 : 12}
+ p={10}
+ ml={isCollapsed ? "60px" : "240px"}
+ minWidth="0"
+ overflowY="auto"
+ >
+ {children}
+ </GridItem>
+ </Grid>
+ );
+});
diff --git a/custos-portal/src/index.tsx b/custos-portal/src/index.tsx
index c141db60e..9bf129409 100644
--- a/custos-portal/src/index.tsx
+++ b/custos-portal/src/index.tsx
@@ -4,7 +4,7 @@ import { extendTheme, ChakraProvider } from '@chakra-ui/react';
import { AuthProvider, AuthProviderProps } from 'react-oidc-context';
import { APP_REDIRECT_URI, BACKEND_URL, CLIENT_ID, TENANT_ID } from
'./lib/constants';
import { WebStorageStateStore } from 'oidc-client-ts';
-import React, { useEffect, useState } from 'react';
+import { useEffect, useState } from 'react';
import localOidcConfig from './lib/localOidcConfig.json';
const theme = extendTheme({
@@ -31,17 +31,10 @@ const Index = () => {
const fetchOidcConfig = async () => {
try {
let data;
- if (!process.env.NODE_ENV || process.env.NODE_ENV === 'development') {
- data = localOidcConfig;
- } else {
- const response = await
fetch(`${BACKEND_URL}/api/v1/identity-management/tenant/${TENANT_ID}/.well-known/openid-configuration`);
// Replace with actual API endpoint
- data = await response.json();
- }
-
- // Determine redirect_uri based on environment
+ const response = await
fetch(`${BACKEND_URL}/api/v1/identity-management/tenant/${TENANT_ID}/.well-known/openid-configuration`);
// Replace with actual API endpoint
+ data = await response.json();
const redirectUri = APP_REDIRECT_URI;
- // Create the OIDC config based on the fetched data
const theConfig: AuthProviderProps = {
authority: `${BACKEND_URL}/api/v1/identity-management/`,
client_id: CLIENT_ID,
diff --git a/custos-portal/src/lib/constants.ts
b/custos-portal/src/lib/constants.ts
index 9966e7e56..d164a3d7b 100644
--- a/custos-portal/src/lib/constants.ts
+++ b/custos-portal/src/lib/constants.ts
@@ -5,15 +5,9 @@ export let CLIENT_ID:string;
export let BACKEND_URL:string;
export let APP_URL:string;
-if (!process.env.NODE_ENV || process.env.NODE_ENV === 'development') {
- CLIENT_ID = 'veda-dafsxhsztbsczrmmbftw-10000000';
- BACKEND_URL = 'http://localhost:8081';
+ CLIENT_ID = 'custos-kgap8hu6ih4hddvlzzlb-10000000';
+ BACKEND_URL = 'https://api.playground.usecustos.org';
APP_URL = 'http://localhost:5173'
-} else {
- CLIENT_ID = 'veda-iui65nmkgaf7bihdyndc-10000000';
- BACKEND_URL = 'https://api.veda.usecustos.org';
- APP_URL = 'https://veda.usecustos.org'
-}
export const APP_REDIRECT_URI = `${APP_URL}/oauth-callback`;
export const TENANT_ID = '10000000';
\ No newline at end of file