Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Add new pages, components, panels, modals, and theme; refactor and improvements in existing components #593

Merged
merged 14 commits into from
Feb 12, 2024
56 changes: 47 additions & 9 deletions src/new-frontend/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,57 @@
import { BrowserRouter, Route, Routes } from 'react-router-dom';
import { Route, BrowserRouter as Router, Routes } from 'react-router-dom';

import Layout from './pages/Layout';
import Login from './pages/auth/Login';
import RecoverPassword from './pages/auth/RecoverPassword';
import Admin from './pages/main/Admin';
import Dashboard from './pages/main/Dashboard';
import Items from './pages/main/Items';
import Profile from './pages/main/Profile';
import { ChakraProvider, extendTheme } from '@chakra-ui/react';

// Theme
const theme = extendTheme({
colors: {
ui: {
main: "#009688",
secondary: "#EDF2F7",
success: '#48BB78',
danger: '#E53E3E',
}
},
components: {
Tabs: {
variants: {
enclosed: {
tab: {
_selected: {
color: 'ui.main',
},
},
},
},
},
},
});

function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/login" element={<Login />} />
<Route path="/recover-password" element={<RecoverPassword />} />
<Route element={<Layout />}>
</Route>
</Routes>
</BrowserRouter>
<>
<Router>
<ChakraProvider theme={theme}>
<Routes>
<Route path="/login" element={<Login />} />
<Route path="/recover-password" element={<RecoverPassword />} />
<Route element={<Layout />}>
<Route path="/" element={<Dashboard />} />
<Route path="/settings" element={<Profile />} />
<Route path="/items" element={<Items />} />
<Route path="/admin" element={<Admin />} />
</Route>
</Routes>
</ ChakraProvider>
</Router>
</>
)
}

Expand Down
39 changes: 39 additions & 0 deletions src/new-frontend/src/components/ActionsMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React from 'react';

import { Button, Menu, MenuButton, MenuItem, MenuList, useDisclosure } from '@chakra-ui/react';
import { BsThreeDotsVertical } from 'react-icons/bs';
import { FiTrash, FiEdit } from 'react-icons/fi';

import Delete from '../pages/modals/DeleteAlert';
import EditUser from '../pages/modals/EditUser';
import EditItem from '../pages/modals/EditItem';

interface ActionsMenuProps {
type: string;
id: number;
}

const ActionsMenu: React.FC<ActionsMenuProps> = ({ type, id }) => {
const editUserModal = useDisclosure();
const deleteModal = useDisclosure();

return (
<>
<Menu>
<MenuButton as={Button} bg="white" rightIcon={<BsThreeDotsVertical />} variant="unstyled">
</MenuButton>
<MenuList>
<MenuItem onClick={editUserModal.onOpen} icon={<FiEdit fontSize="16px" />}>Edit {type}</MenuItem>
<MenuItem onClick={deleteModal.onOpen} icon={<FiTrash fontSize="16px" />} color="ui.danger">Delete {type}</MenuItem>
</MenuList>
{
type === "User" ? <EditUser isOpen={editUserModal.isOpen} onClose={editUserModal.onClose} />
: <EditItem isOpen={editUserModal.isOpen} onClose={editUserModal.onClose} />
}
<Delete type={type} id={id} isOpen={deleteModal.isOpen} onClose={deleteModal.onClose} />
</Menu>
</>
);
};

export default ActionsMenu;
30 changes: 30 additions & 0 deletions src/new-frontend/src/components/Navbar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from 'react';

import { Button, Flex, Icon, useDisclosure } from '@chakra-ui/react';
import { FaPlus } from "react-icons/fa";

import CreateItem from '../pages/modals/CreateItem';
import CreateUser from '../pages/modals/CreateUser';

interface NavbarProps {
type: string;
}

const Navbar: React.FC<NavbarProps> = ({ type }) => {
const createUserModal = useDisclosure();
const createItemModal = useDisclosure();

return (
<>
<Flex gap={4} py={{ base: "8", md: "4" }} justify={{ base: "center", md: "end" }}>
<Button bg="ui.main" color="white" gap={1} fontSize={{ base: "sm", md: "inherit" }} onClick={type === "User" ? createUserModal.onOpen : createItemModal.onOpen}>
<Icon as={FaPlus} /> Create {type}
</Button>
<CreateUser isOpen={createUserModal.isOpen} onClose={createUserModal.onClose} />
<CreateItem isOpen={createItemModal.isOpen} onClose={createItemModal.onClose} />
</Flex>
</>
);
};

export default Navbar;
4 changes: 2 additions & 2 deletions src/new-frontend/src/components/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const Sidebar: React.FC = () => {
<IconButton onClick={onOpen} display={{ base: 'flex', md: 'none' }} aria-label="Open Menu" position="absolute" fontSize='20px' m={4} icon={<FiMenu />} />
<Drawer isOpen={isOpen} placement="left" onClose={onClose}>
<DrawerOverlay />
<DrawerContent bg="gray.100" maxW="250px">
<DrawerContent bg="ui.secondary" maxW="250px">
<DrawerCloseButton />
<DrawerBody py={8}>
<Flex flexDir="column" justify="space-between" h="100%">
Expand All @@ -33,7 +33,7 @@ const Sidebar: React.FC = () => {

{/* Desktop */}
<Box bg="white" p={3} h="100vh" position="sticky" top="0" display={{ base: 'none', md: 'flex' }}>
<Flex flexDir="column" justify="space-between" bg="gray.100" p={6} borderRadius={12}>
<Flex flexDir="column" justify="space-between" bg="ui.secondary" p={6} borderRadius={12}>
<Box>
<Image src={Logo} alt="Logo" w="180px" maxW="2xs" />
<SidebarItems />
Expand Down
22 changes: 16 additions & 6 deletions src/new-frontend/src/components/SidebarItems.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import React from 'react';

import { Flex, Icon, Text } from '@chakra-ui/react';
import { FiBriefcase, FiHome, FiLogOut, FiUser, FiUsers } from 'react-icons/fi';
import { Link } from 'react-router-dom';
import { FiBriefcase, FiHome, FiLogOut, FiSettings, FiUsers } from 'react-icons/fi';
import { Link, useNavigate } from 'react-router-dom';


const items = [
{ icon: FiHome, title: 'Dashboard', path: "/" },
{ icon: FiUser, title: 'Profile', path: "/profile" },
{ icon: FiBriefcase, title: 'Items', path: "/items" },
{ icon: FiUsers, title: 'Admin', path: "/admin" },
{ icon: FiSettings, title: 'User Settings', path: "/settings" },
{ icon: FiLogOut, title: 'Log out' }
];

Expand All @@ -17,15 +18,24 @@ interface SidebarItemsProps {
}

const SidebarItems: React.FC<SidebarItemsProps> = ({ onClose }) => {
const navigate = useNavigate();

const handleLogout = async () => {
localStorage.removeItem("access_token");
navigate("/login");
// TODO: reset all Zustand states
};
alejsdev marked this conversation as resolved.
Show resolved Hide resolved

const listItems = items.map((item) => (
<Flex w="100%" p={2} key={item.title} _hover={{
background: "gray.200",
borderRadius: "12px",
}} onClick={onClose}>
}} onClick={item.title === 'Log out' ? handleLogout : onClose}>
<Link to={item.path || "/"}>
<Flex color="teal.500" gap={4}>
<Icon as={item.icon} alignSelf="center" />
<Flex gap={4}>
<Icon color="ui.main" as={item.icon} alignSelf="center" />
<Text>{item.title}</Text>

</Flex>
</Link>
</Flex>
Expand Down
27 changes: 8 additions & 19 deletions src/new-frontend/src/components/UserInfo.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,22 @@
import React, { useEffect, useState } from 'react';
import React from 'react';

import { Avatar, Flex, Skeleton, Text } from '@chakra-ui/react';
import { FaUserAstronaut } from 'react-icons/fa';

import { UserOut, UsersService } from '../client';
import { useUserStore } from '../store/user-store';

const UserInfo: React.FC = () => {
const [userData, setUserData] = useState<UserOut>();

useEffect(() => {
const fetchUserData = async () => {
try {
const userResponse = await UsersService.readUserMe();
setUserData(userResponse);
} catch (error) {
// TODO: Handle error to give feedback to the user
console.error(error);
}
};
fetchUserData();
}, []);
const UserInfo: React.FC = () => {
const { user } = useUserStore();


return (
<>
{userData ? (
{user ? (
<Flex gap={2} maxW="180px">
<Avatar icon={<FaUserAstronaut fontSize="18px" />} size='sm' alignSelf="center" />
<Avatar bg="ui.main" icon={<FaUserAstronaut fontSize="18px" />} size='sm' alignSelf="center" />
{/* TODO: Conditional tooltip based on email length */}
<Text color='gray' alignSelf={"center"} noOfLines={1} fontSize="14px">{userData.email}</Text>
<Text color='gray' alignSelf={"center"} noOfLines={1} fontSize="14px">{user.email}</Text>
</Flex>
) :
<Skeleton height='20px' />
Expand Down
91 changes: 46 additions & 45 deletions src/new-frontend/src/pages/auth/Login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,9 @@ import { LoginService } from "../../client";
import { Body_login_login_access_token as AccessToken } from "../../client/models/Body_login_login_access_token";

const Login: React.FC = () => {
const [show, setShow] = useBoolean();
const [show, setShow] = useBoolean();
const navigate = useNavigate();
const { register, handleSubmit } = useForm<AccessToken>();

const onSubmit: SubmitHandler<AccessToken> = async (data) => {
const response = await LoginService.loginAccessToken({
formData: data,
Expand All @@ -23,49 +22,51 @@ const Login: React.FC = () => {
};

return (
<Container
as="form"
onSubmit={handleSubmit(onSubmit)}
h="100vh"
maxW="sm"
alignItems="stretch"
justifyContent="center"
gap={4}
centerContent
>
<Image src={Logo} alt="FastAPI logo" height="auto" maxW="2xs" alignSelf="center" />
<FormControl id="email">
<Input {...register("username")} focusBorderColor="blue.200" placeholder="Email" type="text" />
</FormControl>
<FormControl id="password">
<InputGroup>
<Input
{...register("password")}
type={show ? "text" : "password"}
focusBorderColor="blue.200"
placeholder="Password"
/>
<InputRightElement
color="gray.500"
_hover={{
cursor: "pointer",
}}
>
<Icon onClick={setShow.toggle} aria-label={show ? "Hide password" : "Show password"}>
{show ? <ViewOffIcon /> : <ViewIcon />}
</Icon>
</InputRightElement>
</InputGroup>
<Center>
<Link as={ReactRouterLink} to="/recover-password" color="blue.500" mt={2}>
Forgot password?
</Link>
</Center>
</FormControl>
<Button colorScheme="teal" type="submit">
Log In
</Button>
</Container>
<>
<Container
as="form"
onSubmit={handleSubmit(onSubmit)}
h="100vh"
maxW="sm"
alignItems="stretch"
justifyContent="center"
gap={4}
centerContent
>
<Image src={Logo} alt="FastAPI logo" height="auto" maxW="2xs" alignSelf="center" />
<FormControl id="email">
<Input {...register("username")} placeholder="Email" type="text" />
</FormControl>
<FormControl id="password">
<InputGroup>
<Input
{...register("password")}
type={show ? "text" : "password"}

placeholder="Password"
/>
<InputRightElement
color="gray.400"
_hover={{
cursor: "pointer",
}}
>
<Icon onClick={setShow.toggle} aria-label={show ? "Hide password" : "Show password"}>
{show ? <ViewOffIcon /> : <ViewIcon />}
</Icon>
</InputRightElement>
</InputGroup>
<Center>
<Link as={ReactRouterLink} to="/recover-password" color="blue.500" mt={2}>
Forgot password?
</Link>
</Center>
</FormControl>
<Button bg="ui.main" color="white" type="submit">
Log In
</Button>
</Container>
</>
);
};

Expand Down
Loading