🚚 Move new-frontend to frontend (#652)

This commit is contained in:
Alejandra
2024-03-08 19:23:54 +01:00
committed by GitHub
parent 3b44537361
commit 9d703df254
97 changed files with 8 additions and 8 deletions

View File

@@ -0,0 +1,76 @@
import React from 'react'
import {
Button,
Menu,
MenuButton,
MenuItem,
MenuList,
useDisclosure,
} from '@chakra-ui/react'
import { BsThreeDotsVertical } from 'react-icons/bs'
import { FiEdit, FiTrash } from 'react-icons/fi'
import EditUser from '../Admin/EditUser'
import EditItem from '../Items/EditItem'
import Delete from './DeleteAlert'
import { ItemOut, UserOut } from '../../client'
interface ActionsMenuProps {
type: string
value: ItemOut | UserOut
disabled?: boolean
}
const ActionsMenu: React.FC<ActionsMenuProps> = ({ type, value, disabled }) => {
const editUserModal = useDisclosure()
const deleteModal = useDisclosure()
return (
<>
<Menu>
<MenuButton
isDisabled={disabled}
as={Button}
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
user={value as UserOut}
isOpen={editUserModal.isOpen}
onClose={editUserModal.onClose}
/>
) : (
<EditItem
item={value as ItemOut}
isOpen={editUserModal.isOpen}
onClose={editUserModal.onClose}
/>
)}
<Delete
type={type}
id={value.id}
isOpen={deleteModal.isOpen}
onClose={deleteModal.onClose}
/>
</Menu>
</>
)
}
export default ActionsMenu

View File

@@ -0,0 +1,116 @@
import React from 'react'
import {
AlertDialog,
AlertDialogBody,
AlertDialogContent,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogOverlay,
Button,
} from '@chakra-ui/react'
import { useForm } from 'react-hook-form'
import { useMutation, useQueryClient } from 'react-query'
import { ItemsService, UsersService } from '../../client'
import useCustomToast from '../../hooks/useCustomToast'
interface DeleteProps {
type: string
id: number
isOpen: boolean
onClose: () => void
}
const Delete: React.FC<DeleteProps> = ({ type, id, isOpen, onClose }) => {
const queryClient = useQueryClient()
const showToast = useCustomToast()
const cancelRef = React.useRef<HTMLButtonElement | null>(null)
const {
handleSubmit,
formState: { isSubmitting },
} = useForm()
const deleteEntity = async (id: number) => {
if (type === 'Item') {
await ItemsService.deleteItem({ id: id })
} else if (type === 'User') {
await UsersService.deleteUser({ userId: id })
} else {
throw new Error(`Unexpected type: ${type}`)
}
}
const mutation = useMutation(deleteEntity, {
onSuccess: () => {
showToast(
'Success',
`The ${type.toLowerCase()} was deleted successfully.`,
'success',
)
onClose()
},
onError: () => {
showToast(
'An error occurred.',
`An error occurred while deleting the ${type.toLowerCase()}.`,
'error',
)
},
onSettled: () => {
queryClient.invalidateQueries(type === 'Item' ? 'items' : 'users')
},
})
const onSubmit = async () => {
mutation.mutate(id)
}
return (
<>
<AlertDialog
isOpen={isOpen}
onClose={onClose}
leastDestructiveRef={cancelRef}
size={{ base: 'sm', md: 'md' }}
isCentered
>
<AlertDialogOverlay>
<AlertDialogContent as="form" onSubmit={handleSubmit(onSubmit)}>
<AlertDialogHeader>Delete {type}</AlertDialogHeader>
<AlertDialogBody>
{type === 'User' && (
<span>
All items associated with this user will also be{' '}
<strong>permantly deleted. </strong>
</span>
)}
Are you sure? You will not be able to undo this action.
</AlertDialogBody>
<AlertDialogFooter gap={3}>
<Button
bg="ui.danger"
color="white"
_hover={{ opacity: 0.8 }}
type="submit"
isLoading={isSubmitting}
>
Delete
</Button>
<Button
ref={cancelRef}
onClick={onClose}
isDisabled={isSubmitting}
>
Cancel
</Button>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialogOverlay>
</AlertDialog>
</>
)
}
export default Delete

View File

@@ -0,0 +1,43 @@
import React from 'react'
import { Button, Flex, Icon, useDisclosure } from '@chakra-ui/react'
import { FaPlus } from 'react-icons/fa'
import AddUser from '../Admin/AddUser'
import AddItem from '../Items/AddItem'
interface NavbarProps {
type: string
}
const Navbar: React.FC<NavbarProps> = ({ type }) => {
const addUserModal = useDisclosure()
const addItemModal = useDisclosure()
return (
<>
<Flex py={8} gap={4}>
{/* TODO: Complete search functionality */}
{/* <InputGroup w={{ base: '100%', md: 'auto' }}>
<InputLeftElement pointerEvents='none'>
<Icon as={FaSearch} color='gray.400' />
</InputLeftElement>
<Input type='text' placeholder='Search' fontSize={{ base: 'sm', md: 'inherit' }} borderRadius='8px' />
</InputGroup> */}
<Button
bg="ui.main"
color="white"
_hover={{ opacity: 0.8 }}
gap={1}
fontSize={{ base: 'sm', md: 'inherit' }}
onClick={type === 'User' ? addUserModal.onOpen : addItemModal.onOpen}
>
<Icon as={FaPlus} /> Add {type}
</Button>
<AddUser isOpen={addUserModal.isOpen} onClose={addUserModal.onClose} />
<AddItem isOpen={addItemModal.isOpen} onClose={addItemModal.onClose} />
</Flex>
</>
)
}
export default Navbar

View File

@@ -0,0 +1,42 @@
import React from 'react'
import { Button, Container, Text } from '@chakra-ui/react'
import { Link } from '@tanstack/react-router'
const NotFound: React.FC = () => {
return (
<>
<Container
h="100vh"
alignItems="stretch"
justifyContent="center"
textAlign="center"
maxW="sm"
centerContent
>
<Text
fontSize="8xl"
color="ui.main"
fontWeight="bold"
lineHeight="1"
mb={4}
>
404
</Text>
<Text fontSize="md">Oops!</Text>
<Text fontSize="md">Page not found.</Text>
<Button
as={Link}
to="/"
color="ui.main"
borderColor="ui.main"
variant="outline"
mt={4}
>
Go back
</Button>
</Container>
</>
)
}
export default NotFound

View File

@@ -0,0 +1,117 @@
import React from 'react'
import {
Box,
Drawer,
DrawerBody,
DrawerCloseButton,
DrawerContent,
DrawerOverlay,
Flex,
IconButton,
Image,
Text,
useColorModeValue,
useDisclosure,
} from '@chakra-ui/react'
import { FiLogOut, FiMenu } from 'react-icons/fi'
import { useQueryClient } from 'react-query'
import Logo from '../../assets/images/fastapi-logo.svg'
import { UserOut } from '../../client'
import useAuth from '../../hooks/useAuth'
import SidebarItems from './SidebarItems'
const Sidebar: React.FC = () => {
const queryClient = useQueryClient()
const bgColor = useColorModeValue('white', '#1a202c')
const textColor = useColorModeValue('gray', 'white')
const secBgColor = useColorModeValue('ui.secondary', '#252d3d')
const currentUser = queryClient.getQueryData<UserOut>('currentUser')
const { isOpen, onOpen, onClose } = useDisclosure()
const { logout } = useAuth()
const handleLogout = async () => {
logout()
}
return (
<>
{/* Mobile */}
<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 maxW="250px">
<DrawerCloseButton />
<DrawerBody py={8}>
<Flex flexDir="column" justify="space-between">
<Box>
<Image src={Logo} alt="logo" p={6} />
<SidebarItems onClose={onClose} />
<Flex
as="button"
onClick={handleLogout}
p={2}
color="ui.danger"
fontWeight="bold"
alignItems="center"
>
<FiLogOut />
<Text ml={2}>Log out</Text>
</Flex>
</Box>
{currentUser?.email && (
<Text color={textColor} noOfLines={2} fontSize="sm" p={2}>
Logged in as: {currentUser.email}
</Text>
)}
</Flex>
</DrawerBody>
</DrawerContent>
</Drawer>
{/* Desktop */}
<Box
bg={bgColor}
p={3}
h="100vh"
position="sticky"
top="0"
display={{ base: 'none', md: 'flex' }}
>
<Flex
flexDir="column"
justify="space-between"
bg={secBgColor}
p={4}
borderRadius={12}
>
<Box>
<Image src={Logo} alt="Logo" w="180px" maxW="2xs" p={6} />
<SidebarItems />
</Box>
{currentUser?.email && (
<Text
color={textColor}
noOfLines={2}
fontSize="sm"
p={2}
maxW="180px"
>
Logged in as: {currentUser.email}
</Text>
)}
</Flex>
</Box>
</>
)
}
export default Sidebar

View File

@@ -0,0 +1,57 @@
import React from 'react'
import { Box, Flex, Icon, Text, useColorModeValue } from '@chakra-ui/react'
import { FiBriefcase, FiHome, FiSettings, FiUsers } from 'react-icons/fi'
import { Link } from '@tanstack/react-router'
import { useQueryClient } from 'react-query'
import { UserOut } from '../../client'
const items = [
{ icon: FiHome, title: 'Dashboard', path: '/' },
{ icon: FiBriefcase, title: 'Items', path: '/items' },
{ icon: FiSettings, title: 'User Settings', path: '/settings' },
]
interface SidebarItemsProps {
onClose?: () => void
}
const SidebarItems: React.FC<SidebarItemsProps> = ({ onClose }) => {
const queryClient = useQueryClient()
const textColor = useColorModeValue('ui.main', '#E2E8F0')
const bgActive = useColorModeValue('#E2E8F0', '#4A5568')
const currentUser = queryClient.getQueryData<UserOut>('currentUser')
const finalItems = currentUser?.is_superuser
? [...items, { icon: FiUsers, title: 'Admin', path: '/admin' }]
: items
const listItems = finalItems.map((item) => (
<Flex
as={Link}
to={item.path}
w="100%"
p={2}
key={item.title}
activeProps={{
style: {
background: bgActive,
borderRadius: '12px',
},
}}
color={textColor}
onClick={onClose}
>
<Icon as={item.icon} alignSelf="center" />
<Text ml={2}>{item.title}</Text>
</Flex>
))
return (
<>
<Box>{listItems}</Box>
</>
)
}
export default SidebarItems

View File

@@ -0,0 +1,59 @@
import React from 'react'
import {
Box,
IconButton,
Menu,
MenuButton,
MenuItem,
MenuList,
} from '@chakra-ui/react'
import { FaUserAstronaut } from 'react-icons/fa'
import { FiLogOut, FiUser } from 'react-icons/fi'
import useAuth from '../../hooks/useAuth'
import { Link } from '@tanstack/react-router'
const UserMenu: React.FC = () => {
const { logout } = useAuth()
const handleLogout = async () => {
logout()
}
return (
<>
{/* Desktop */}
<Box
display={{ base: 'none', md: 'block' }}
position="fixed"
top={4}
right={4}
>
<Menu>
<MenuButton
as={IconButton}
aria-label="Options"
icon={<FaUserAstronaut color="white" fontSize="18px" />}
bg="ui.main"
isRound
/>
<MenuList>
<MenuItem icon={<FiUser fontSize="18px" />} as={Link} to="settings">
My profile
</MenuItem>
<MenuItem
icon={<FiLogOut fontSize="18px" />}
onClick={handleLogout}
color="ui.danger"
fontWeight="bold"
>
Log out
</MenuItem>
</MenuList>
</Menu>
</Box>
</>
)
}
export default UserMenu