🛂 Migrate to Chakra UI v3 (#1496)
Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
@@ -1,75 +0,0 @@
|
||||
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 type { ItemPublic, UserPublic } from "../../client"
|
||||
import EditUser from "../Admin/EditUser"
|
||||
import EditItem from "../Items/EditItem"
|
||||
import Delete from "./DeleteAlert"
|
||||
|
||||
interface ActionsMenuProps {
|
||||
type: string
|
||||
value: ItemPublic | UserPublic
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
const ActionsMenu = ({ type, value, disabled }: ActionsMenuProps) => {
|
||||
const editUserModal = useDisclosure()
|
||||
const deleteModal = useDisclosure()
|
||||
|
||||
return (
|
||||
<>
|
||||
<Menu>
|
||||
<MenuButton
|
||||
isDisabled={disabled}
|
||||
as={Button}
|
||||
rightIcon={<BsThreeDotsVertical />}
|
||||
variant="unstyled"
|
||||
/>
|
||||
<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 UserPublic}
|
||||
isOpen={editUserModal.isOpen}
|
||||
onClose={editUserModal.onClose}
|
||||
/>
|
||||
) : (
|
||||
<EditItem
|
||||
item={value as ItemPublic}
|
||||
isOpen={editUserModal.isOpen}
|
||||
onClose={editUserModal.onClose}
|
||||
/>
|
||||
)}
|
||||
<Delete
|
||||
type={type}
|
||||
id={value.id}
|
||||
isOpen={deleteModal.isOpen}
|
||||
onClose={deleteModal.onClose}
|
||||
/>
|
||||
</Menu>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default ActionsMenu
|
||||
@@ -1,113 +0,0 @@
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogBody,
|
||||
AlertDialogContent,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogOverlay,
|
||||
Button,
|
||||
} from "@chakra-ui/react"
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query"
|
||||
import React from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
|
||||
import { ItemsService, UsersService } from "../../client"
|
||||
import useCustomToast from "../../hooks/useCustomToast"
|
||||
|
||||
interface DeleteProps {
|
||||
type: string
|
||||
id: string
|
||||
isOpen: boolean
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
const Delete = ({ type, id, isOpen, onClose }: DeleteProps) => {
|
||||
const queryClient = useQueryClient()
|
||||
const showToast = useCustomToast()
|
||||
const cancelRef = React.useRef<HTMLButtonElement | null>(null)
|
||||
const {
|
||||
handleSubmit,
|
||||
formState: { isSubmitting },
|
||||
} = useForm()
|
||||
|
||||
const deleteEntity = async (id: string) => {
|
||||
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({
|
||||
mutationFn: 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({
|
||||
queryKey: [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>permanently deleted. </strong>
|
||||
</span>
|
||||
)}
|
||||
Are you sure? You will not be able to undo this action.
|
||||
</AlertDialogBody>
|
||||
|
||||
<AlertDialogFooter gap={3}>
|
||||
<Button variant="danger" type="submit" isLoading={isSubmitting}>
|
||||
Delete
|
||||
</Button>
|
||||
<Button
|
||||
ref={cancelRef}
|
||||
onClick={onClose}
|
||||
isDisabled={isSubmitting}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialogOverlay>
|
||||
</AlertDialog>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Delete
|
||||
27
frontend/src/components/Common/ItemActionsMenu.tsx
Normal file
27
frontend/src/components/Common/ItemActionsMenu.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import { IconButton } from "@chakra-ui/react"
|
||||
import { BsThreeDotsVertical } from "react-icons/bs"
|
||||
import { MenuContent, MenuRoot, MenuTrigger } from "../ui/menu"
|
||||
|
||||
import type { ItemPublic } from "../../client"
|
||||
import DeleteItem from "../Items/DeleteItem"
|
||||
import EditItem from "../Items/EditItem"
|
||||
|
||||
interface ItemActionsMenuProps {
|
||||
item: ItemPublic
|
||||
}
|
||||
|
||||
export const ItemActionsMenu = ({ item }: ItemActionsMenuProps) => {
|
||||
return (
|
||||
<MenuRoot>
|
||||
<MenuTrigger asChild>
|
||||
<IconButton variant="ghost" color="inherit">
|
||||
<BsThreeDotsVertical />
|
||||
</IconButton>
|
||||
</MenuTrigger>
|
||||
<MenuContent>
|
||||
<EditItem item={item} />
|
||||
<DeleteItem id={item.id} />
|
||||
</MenuContent>
|
||||
</MenuRoot>
|
||||
)
|
||||
}
|
||||
@@ -1,38 +1,30 @@
|
||||
import type { ComponentType, ElementType } from "react"
|
||||
import { Flex, Image, useBreakpointValue } from "@chakra-ui/react"
|
||||
import { Link } from "@tanstack/react-router"
|
||||
import Logo from "/assets/images/fastapi-logo.svg"
|
||||
import UserMenu from "./UserMenu"
|
||||
|
||||
import { Button, Flex, Icon, useDisclosure } from "@chakra-ui/react"
|
||||
import { FaPlus } from "react-icons/fa"
|
||||
function Navbar() {
|
||||
const display = useBreakpointValue({ base: "none", md: "flex" })
|
||||
|
||||
interface NavbarProps {
|
||||
type: string
|
||||
addModalAs: ComponentType | ElementType
|
||||
}
|
||||
|
||||
const Navbar = ({ type, addModalAs }: NavbarProps) => {
|
||||
const addModal = useDisclosure()
|
||||
|
||||
const AddModal = addModalAs
|
||||
return (
|
||||
<>
|
||||
<Flex py={8} gap={4}>
|
||||
{/* TODO: Complete search functionality */}
|
||||
{/* <InputGroup w={{ base: '100%', md: 'auto' }}>
|
||||
<InputLeftElement pointerEvents='none'>
|
||||
<Icon as={FaSearch} color='ui.dim' />
|
||||
</InputLeftElement>
|
||||
<Input type='text' placeholder='Search' fontSize={{ base: 'sm', md: 'inherit' }} borderRadius='8px' />
|
||||
</InputGroup> */}
|
||||
<Button
|
||||
variant="primary"
|
||||
gap={1}
|
||||
fontSize={{ base: "sm", md: "inherit" }}
|
||||
onClick={addModal.onOpen}
|
||||
>
|
||||
<Icon as={FaPlus} /> Add {type}
|
||||
</Button>
|
||||
<AddModal isOpen={addModal.isOpen} onClose={addModal.onClose} />
|
||||
<Flex
|
||||
display={display}
|
||||
justify="space-between"
|
||||
position="sticky"
|
||||
color="white"
|
||||
align="center"
|
||||
bg="bg.muted"
|
||||
w="100%"
|
||||
top={0}
|
||||
p={4}
|
||||
>
|
||||
<Link to="/">
|
||||
<Image src={Logo} alt="Logo" w="180px" maxW="2xs" px={2} />
|
||||
</Link>
|
||||
<Flex gap={2} alignItems="center">
|
||||
<UserMenu />
|
||||
</Flex>
|
||||
</>
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,39 +1,55 @@
|
||||
import { Button, Container, Text } from "@chakra-ui/react"
|
||||
import { Button, Center, Flex, Text } from "@chakra-ui/react"
|
||||
import { Link } from "@tanstack/react-router"
|
||||
|
||||
const NotFound = () => {
|
||||
return (
|
||||
<>
|
||||
<Container
|
||||
h="100vh"
|
||||
alignItems="stretch"
|
||||
justifyContent="center"
|
||||
textAlign="center"
|
||||
maxW="sm"
|
||||
centerContent
|
||||
<Flex
|
||||
height="100vh"
|
||||
align="center"
|
||||
justify="center"
|
||||
flexDir="column"
|
||||
data-testid="not-found"
|
||||
p={4}
|
||||
>
|
||||
<Flex alignItems="center" zIndex={1}>
|
||||
<Flex flexDir="column" ml={4} align="center" justify="center" p={4}>
|
||||
<Text
|
||||
fontSize={{ base: "6xl", md: "8xl" }}
|
||||
fontWeight="bold"
|
||||
lineHeight="1"
|
||||
mb={4}
|
||||
>
|
||||
404
|
||||
</Text>
|
||||
<Text fontSize="2xl" fontWeight="bold" mb={2}>
|
||||
Oops!
|
||||
</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<Text
|
||||
fontSize="8xl"
|
||||
color="ui.main"
|
||||
fontWeight="bold"
|
||||
lineHeight="1"
|
||||
fontSize="lg"
|
||||
color="gray.600"
|
||||
mb={4}
|
||||
textAlign="center"
|
||||
zIndex={1}
|
||||
>
|
||||
404
|
||||
The page you are looking for was not found.
|
||||
</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>
|
||||
<Center zIndex={1}>
|
||||
<Link to="/">
|
||||
<Button
|
||||
variant="solid"
|
||||
colorScheme="teal"
|
||||
mt={4}
|
||||
alignSelf="center"
|
||||
>
|
||||
Go Back
|
||||
</Button>
|
||||
</Link>
|
||||
</Center>
|
||||
</Flex>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
import { Button, Flex } from "@chakra-ui/react"
|
||||
|
||||
type PaginationFooterProps = {
|
||||
hasNextPage?: boolean
|
||||
hasPreviousPage?: boolean
|
||||
onChangePage: (newPage: number) => void
|
||||
page: number
|
||||
}
|
||||
|
||||
export function PaginationFooter({
|
||||
hasNextPage,
|
||||
hasPreviousPage,
|
||||
onChangePage,
|
||||
page,
|
||||
}: PaginationFooterProps) {
|
||||
return (
|
||||
<Flex
|
||||
gap={4}
|
||||
alignItems="center"
|
||||
mt={4}
|
||||
direction="row"
|
||||
justifyContent="flex-end"
|
||||
>
|
||||
<Button
|
||||
onClick={() => onChangePage(page - 1)}
|
||||
isDisabled={!hasPreviousPage || page <= 1}
|
||||
>
|
||||
Previous
|
||||
</Button>
|
||||
<span>Page {page}</span>
|
||||
<Button isDisabled={!hasNextPage} onClick={() => onChangePage(page + 1)}>
|
||||
Next
|
||||
</Button>
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
@@ -1,33 +1,26 @@
|
||||
import {
|
||||
Box,
|
||||
Drawer,
|
||||
DrawerBody,
|
||||
DrawerCloseButton,
|
||||
DrawerContent,
|
||||
DrawerOverlay,
|
||||
Flex,
|
||||
IconButton,
|
||||
Image,
|
||||
Text,
|
||||
useColorModeValue,
|
||||
useDisclosure,
|
||||
} from "@chakra-ui/react"
|
||||
import { Box, Flex, IconButton, Text } from "@chakra-ui/react"
|
||||
import { useQueryClient } from "@tanstack/react-query"
|
||||
import { FiLogOut, FiMenu } from "react-icons/fi"
|
||||
import { useState } from "react"
|
||||
import { FaBars } from "react-icons/fa"
|
||||
|
||||
import Logo from "/assets/images/fastapi-logo.svg"
|
||||
import { FiLogOut } from "react-icons/fi"
|
||||
import type { UserPublic } from "../../client"
|
||||
import useAuth from "../../hooks/useAuth"
|
||||
import {
|
||||
DrawerBackdrop,
|
||||
DrawerBody,
|
||||
DrawerCloseTrigger,
|
||||
DrawerContent,
|
||||
DrawerRoot,
|
||||
DrawerTrigger,
|
||||
} from "../ui/drawer"
|
||||
import SidebarItems from "./SidebarItems"
|
||||
|
||||
const Sidebar = () => {
|
||||
const queryClient = useQueryClient()
|
||||
const bgColor = useColorModeValue("ui.light", "ui.dark")
|
||||
const textColor = useColorModeValue("ui.dark", "ui.light")
|
||||
const secBgColor = useColorModeValue("ui.secondary", "ui.darkSlate")
|
||||
const currentUser = queryClient.getQueryData<UserPublic>(["currentUser"])
|
||||
const { isOpen, onOpen, onClose } = useDisclosure()
|
||||
const { logout } = useAuth()
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
const handleLogout = async () => {
|
||||
logout()
|
||||
@@ -36,78 +29,68 @@ const Sidebar = () => {
|
||||
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}>
|
||||
<DrawerRoot
|
||||
placement="start"
|
||||
open={open}
|
||||
onOpenChange={(e) => setOpen(e.open)}
|
||||
>
|
||||
<DrawerBackdrop />
|
||||
<DrawerTrigger asChild>
|
||||
<IconButton
|
||||
variant="ghost"
|
||||
color="inherit"
|
||||
display={{ base: "flex", md: "none" }}
|
||||
aria-label="Open Menu"
|
||||
position="absolute"
|
||||
zIndex="100"
|
||||
m={4}
|
||||
>
|
||||
<FaBars />
|
||||
</IconButton>
|
||||
</DrawerTrigger>
|
||||
<DrawerContent maxW="280px">
|
||||
<DrawerCloseTrigger />
|
||||
<DrawerBody>
|
||||
<Flex flexDir="column" justify="space-between">
|
||||
<Box>
|
||||
<Image src={Logo} alt="logo" p={6} />
|
||||
<SidebarItems onClose={onClose} />
|
||||
<SidebarItems />
|
||||
<Flex
|
||||
as="button"
|
||||
onClick={handleLogout}
|
||||
p={2}
|
||||
color="ui.danger"
|
||||
fontWeight="bold"
|
||||
alignItems="center"
|
||||
gap={4}
|
||||
px={4}
|
||||
py={2}
|
||||
>
|
||||
<FiLogOut />
|
||||
<Text ml={2}>Log out</Text>
|
||||
<Text>Log Out</Text>
|
||||
</Flex>
|
||||
</Box>
|
||||
{currentUser?.email && (
|
||||
<Text color={textColor} noOfLines={2} fontSize="sm" p={2}>
|
||||
<Text fontSize="sm" p={2}>
|
||||
Logged in as: {currentUser.email}
|
||||
</Text>
|
||||
)}
|
||||
</Flex>
|
||||
</DrawerBody>
|
||||
<DrawerCloseTrigger />
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
</DrawerRoot>
|
||||
|
||||
{/* Desktop */}
|
||||
|
||||
<Box
|
||||
bg={bgColor}
|
||||
p={3}
|
||||
h="100vh"
|
||||
position="sticky"
|
||||
top="0"
|
||||
display={{ base: "none", md: "flex" }}
|
||||
position="sticky"
|
||||
bg="bg.subtle"
|
||||
top={0}
|
||||
minW="280px"
|
||||
h="100vh"
|
||||
p={4}
|
||||
>
|
||||
<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 w="100%">
|
||||
<SidebarItems />
|
||||
</Box>
|
||||
</Box>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Box, Flex, Icon, Text, useColorModeValue } from "@chakra-ui/react"
|
||||
import { Box, Flex, Icon, Text } from "@chakra-ui/react"
|
||||
import { useQueryClient } from "@tanstack/react-query"
|
||||
import { Link } from "@tanstack/react-router"
|
||||
import { FiBriefcase, FiHome, FiSettings, FiUsers } from "react-icons/fi"
|
||||
import { Link as RouterLink } from "@tanstack/react-router"
|
||||
|
||||
import { FiBriefcase, FiHome, FiSettings, FiUsers } from "react-icons/fi"
|
||||
import type { IconType } from "react-icons/lib"
|
||||
import type { UserPublic } from "../../client"
|
||||
|
||||
const items = [
|
||||
@@ -15,39 +16,43 @@ interface SidebarItemsProps {
|
||||
onClose?: () => void
|
||||
}
|
||||
|
||||
interface Item {
|
||||
icon: IconType
|
||||
title: string
|
||||
path: string
|
||||
}
|
||||
|
||||
const SidebarItems = ({ onClose }: SidebarItemsProps) => {
|
||||
const queryClient = useQueryClient()
|
||||
const textColor = useColorModeValue("ui.main", "ui.light")
|
||||
const bgActive = useColorModeValue("#E2E8F0", "#4A5568")
|
||||
const currentUser = queryClient.getQueryData<UserPublic>(["currentUser"])
|
||||
|
||||
const finalItems = currentUser?.is_superuser
|
||||
const finalItems: Item[] = currentUser?.is_superuser
|
||||
? [...items, { icon: FiUsers, title: "Admin", path: "/admin" }]
|
||||
: items
|
||||
|
||||
const listItems = finalItems.map(({ icon, title, path }) => (
|
||||
<Flex
|
||||
as={Link}
|
||||
to={path}
|
||||
w="100%"
|
||||
p={2}
|
||||
key={title}
|
||||
activeProps={{
|
||||
style: {
|
||||
background: bgActive,
|
||||
borderRadius: "12px",
|
||||
},
|
||||
}}
|
||||
color={textColor}
|
||||
onClick={onClose}
|
||||
>
|
||||
<Icon as={icon} alignSelf="center" />
|
||||
<Text ml={2}>{title}</Text>
|
||||
</Flex>
|
||||
<RouterLink key={title} to={path} onClick={onClose}>
|
||||
<Flex
|
||||
gap={4}
|
||||
px={4}
|
||||
py={2}
|
||||
_hover={{
|
||||
background: "gray.subtle",
|
||||
}}
|
||||
alignItems="center"
|
||||
fontSize="sm"
|
||||
>
|
||||
<Icon as={icon} alignSelf="center" />
|
||||
<Text ml={2}>{title}</Text>
|
||||
</Flex>
|
||||
</RouterLink>
|
||||
))
|
||||
|
||||
return (
|
||||
<>
|
||||
<Text fontSize="xs" px={4} py={2} fontWeight="bold">
|
||||
Menu
|
||||
</Text>
|
||||
<Box>{listItems}</Box>
|
||||
</>
|
||||
)
|
||||
|
||||
28
frontend/src/components/Common/UserActionsMenu.tsx
Normal file
28
frontend/src/components/Common/UserActionsMenu.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import { IconButton } from "@chakra-ui/react"
|
||||
import { BsThreeDotsVertical } from "react-icons/bs"
|
||||
import { MenuContent, MenuRoot, MenuTrigger } from "../ui/menu"
|
||||
|
||||
import type { UserPublic } from "../../client"
|
||||
import DeleteUser from "../Admin/DeleteUser"
|
||||
import EditUser from "../Admin/EditUser"
|
||||
|
||||
interface UserActionsMenuProps {
|
||||
user: UserPublic
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
export const UserActionsMenu = ({ user, disabled }: UserActionsMenuProps) => {
|
||||
return (
|
||||
<MenuRoot>
|
||||
<MenuTrigger asChild>
|
||||
<IconButton variant="ghost" color="inherit" disabled={disabled}>
|
||||
<BsThreeDotsVertical />
|
||||
</IconButton>
|
||||
</MenuTrigger>
|
||||
<MenuContent>
|
||||
<EditUser user={user} />
|
||||
<DeleteUser id={user.id} />
|
||||
</MenuContent>
|
||||
</MenuRoot>
|
||||
)
|
||||
}
|
||||
@@ -1,19 +1,13 @@
|
||||
import {
|
||||
Box,
|
||||
IconButton,
|
||||
Menu,
|
||||
MenuButton,
|
||||
MenuItem,
|
||||
MenuList,
|
||||
} from "@chakra-ui/react"
|
||||
import { Box, Button, Flex, Text } from "@chakra-ui/react"
|
||||
import { Link } from "@tanstack/react-router"
|
||||
import { FaUserAstronaut } from "react-icons/fa"
|
||||
import { FiLogOut, FiUser } from "react-icons/fi"
|
||||
|
||||
import { FiLogOut, FiUser } from "react-icons/fi"
|
||||
import useAuth from "../../hooks/useAuth"
|
||||
import { MenuContent, MenuItem, MenuRoot, MenuTrigger } from "../ui/menu"
|
||||
|
||||
const UserMenu = () => {
|
||||
const { logout } = useAuth()
|
||||
const { user, logout } = useAuth()
|
||||
|
||||
const handleLogout = async () => {
|
||||
logout()
|
||||
@@ -22,36 +16,47 @@ const UserMenu = () => {
|
||||
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
|
||||
data-testid="user-menu"
|
||||
/>
|
||||
<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"
|
||||
<Flex>
|
||||
<MenuRoot>
|
||||
<MenuTrigger asChild p={2}>
|
||||
<Button
|
||||
data-testid="user-menu"
|
||||
variant="solid"
|
||||
maxW="150px"
|
||||
truncate
|
||||
>
|
||||
Log out
|
||||
<FaUserAstronaut fontSize="18" />
|
||||
<Text>{user?.full_name || "User"}</Text>
|
||||
</Button>
|
||||
</MenuTrigger>
|
||||
|
||||
<MenuContent>
|
||||
<Link to="settings">
|
||||
<MenuItem
|
||||
closeOnSelect
|
||||
value="user-settings"
|
||||
gap={2}
|
||||
py={2}
|
||||
style={{ cursor: "pointer" }}
|
||||
>
|
||||
<FiUser fontSize="18px" />
|
||||
<Box flex="1">My Profile</Box>
|
||||
</MenuItem>
|
||||
</Link>
|
||||
|
||||
<MenuItem
|
||||
value="logout"
|
||||
gap={2}
|
||||
py={2}
|
||||
onClick={handleLogout}
|
||||
style={{ cursor: "pointer" }}
|
||||
>
|
||||
<FiLogOut />
|
||||
Log Out
|
||||
</MenuItem>
|
||||
</MenuList>
|
||||
</Menu>
|
||||
</Box>
|
||||
</MenuContent>
|
||||
</MenuRoot>
|
||||
</Flex>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user