Skip to content

Commit 8bac441

Browse files
committed
♻️ Refactor DeleteUser and DeleteItem components
1 parent b164b03 commit 8bac441

File tree

5 files changed

+153
-97
lines changed

5 files changed

+153
-97
lines changed

frontend/src/components/Admin/DeleteUser.tsx

Lines changed: 39 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,35 @@
1-
import { Button, DialogTitle, Text } from "@chakra-ui/react"
21
import { useMutation, useQueryClient } from "@tanstack/react-query"
2+
import { Trash2 } from "lucide-react"
33
import { useState } from "react"
44
import { useForm } from "react-hook-form"
5-
import { FiTrash2 } from "react-icons/fi"
65

76
import { UsersService } from "@/client"
7+
import { Button } from "@/components/ui/button"
88
import {
9-
DialogActionTrigger,
10-
DialogBody,
11-
DialogCloseTrigger,
9+
Dialog,
10+
DialogClose,
1211
DialogContent,
12+
DialogDescription,
1313
DialogFooter,
1414
DialogHeader,
15-
DialogRoot,
16-
DialogTrigger,
15+
DialogTitle,
1716
} from "@/components/ui/dialog"
17+
import { DropdownMenuItem } from "@/components/ui/dropdown-menu"
18+
import { LoadingButton } from "@/components/ui/loading-button"
1819
import useCustomToast from "@/hooks/useCustomToast"
20+
import { handleError } from "@/utils"
1921

20-
const DeleteUser = ({ id }: { id: string }) => {
22+
interface DeleteUserProps {
23+
id: string
24+
onSuccess: () => void
25+
}
26+
27+
const DeleteUser = ({ id, onSuccess }: DeleteUserProps) => {
2128
const [isOpen, setIsOpen] = useState(false)
2229
const queryClient = useQueryClient()
2330
const { showSuccessToast, showErrorToast } = useCustomToast()
2431
const {
2532
handleSubmit,
26-
formState: { isSubmitting },
2733
} = useForm()
2834

2935
const deleteUser = async (id: string) => {
@@ -35,10 +41,9 @@ const DeleteUser = ({ id }: { id: string }) => {
3541
onSuccess: () => {
3642
showSuccessToast("The user was deleted successfully")
3743
setIsOpen(false)
44+
onSuccess()
3845
},
39-
onError: () => {
40-
showErrorToast("An error occurred while deleting the user")
41-
},
46+
onError: handleError.bind(showErrorToast),
4247
onSettled: () => {
4348
queryClient.invalidateQueries()
4449
},
@@ -49,55 +54,43 @@ const DeleteUser = ({ id }: { id: string }) => {
4954
}
5055

5156
return (
52-
<DialogRoot
53-
size={{ base: "xs", md: "md" }}
54-
placement="center"
55-
role="alertdialog"
56-
open={isOpen}
57-
onOpenChange={({ open }) => setIsOpen(open)}
58-
>
59-
<DialogTrigger asChild>
60-
<Button variant="ghost" size="sm" colorPalette="red">
61-
<FiTrash2 fontSize="16px" />
62-
Delete User
63-
</Button>
64-
</DialogTrigger>
65-
<DialogContent>
57+
<Dialog open={isOpen} onOpenChange={setIsOpen}>
58+
<DropdownMenuItem
59+
variant="destructive"
60+
onSelect={(e) => e.preventDefault()}
61+
onClick={() => setIsOpen(true)}
62+
>
63+
<Trash2 />
64+
Delete User
65+
</DropdownMenuItem>
66+
<DialogContent className="sm:max-w-md">
6667
<form onSubmit={handleSubmit(onSubmit)}>
6768
<DialogHeader>
6869
<DialogTitle>Delete User</DialogTitle>
69-
</DialogHeader>
70-
<DialogBody>
71-
<Text mb={4}>
70+
<DialogDescription>
7271
All items associated with this user will also be{" "}
7372
<strong>permanently deleted.</strong> Are you sure? You will not
7473
be able to undo this action.
75-
</Text>
76-
</DialogBody>
74+
</DialogDescription>
75+
</DialogHeader>
7776

78-
<DialogFooter gap={2}>
79-
<DialogActionTrigger asChild>
80-
<Button
81-
variant="subtle"
82-
colorPalette="gray"
83-
disabled={isSubmitting}
84-
>
77+
<DialogFooter className="mt-4">
78+
<DialogClose asChild>
79+
<Button variant="outline" disabled={mutation.isPending}>
8580
Cancel
8681
</Button>
87-
</DialogActionTrigger>
88-
<Button
89-
variant="solid"
90-
colorPalette="red"
82+
</DialogClose>
83+
<LoadingButton
84+
variant="destructive"
9185
type="submit"
92-
loading={isSubmitting}
86+
loading={mutation.isPending}
9387
>
9488
Delete
95-
</Button>
89+
</LoadingButton>
9690
</DialogFooter>
97-
<DialogCloseTrigger />
9891
</form>
9992
</DialogContent>
100-
</DialogRoot>
93+
</Dialog>
10194
)
10295
}
10396

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { EllipsisVertical } from "lucide-react"
2+
import { useState } from "react"
3+
4+
import type { UserPublic } from "@/client"
5+
import { Button } from "@/components/ui/button"
6+
import {
7+
DropdownMenu,
8+
DropdownMenuContent,
9+
DropdownMenuTrigger,
10+
} from "@/components/ui/dropdown-menu"
11+
import useAuth from "@/hooks/useAuth"
12+
import DeleteUser from "./DeleteUser"
13+
import EditUser from "./EditUser"
14+
15+
interface UserActionsMenuProps {
16+
user: UserPublic
17+
}
18+
19+
export const UserActionsMenu = ({ user }: UserActionsMenuProps) => {
20+
const [open, setOpen] = useState(false)
21+
const { user: currentUser } = useAuth()
22+
23+
if (user.id === currentUser?.id) {
24+
return null
25+
}
26+
27+
return (
28+
<DropdownMenu open={open} onOpenChange={setOpen}>
29+
<DropdownMenuTrigger asChild>
30+
<Button variant="ghost" size="icon">
31+
<EllipsisVertical />
32+
</Button>
33+
</DropdownMenuTrigger>
34+
<DropdownMenuContent align="end">
35+
<EditUser user={user} onSuccess={() => setOpen(false)} />
36+
<DeleteUser id={user.id} onSuccess={() => setOpen(false)} />
37+
</DropdownMenuContent>
38+
</DropdownMenu>
39+
)
40+
}

frontend/src/components/Admin/columns.tsx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,7 @@ export const columns: ColumnDef<UserTableData>[] = [
6464
header: () => <span className="sr-only">Actions</span>,
6565
cell: ({ row }) => (
6666
<div className="flex justify-end">
67-
<UserActionsMenu
68-
user={row.original}
69-
disabled={row.original.isCurrentUser}
70-
/>
67+
<UserActionsMenu user={row.original} />
7168
</div>
7269
),
7370
},

frontend/src/components/Items/DeleteItem.tsx

Lines changed: 39 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,35 @@
1-
import { Button, DialogTitle, Text } from "@chakra-ui/react"
21
import { useMutation, useQueryClient } from "@tanstack/react-query"
2+
import { Trash2 } from "lucide-react"
33
import { useState } from "react"
44
import { useForm } from "react-hook-form"
5-
import { FiTrash2 } from "react-icons/fi"
65

76
import { ItemsService } from "@/client"
7+
import { Button } from "@/components/ui/button"
88
import {
9-
DialogActionTrigger,
10-
DialogBody,
11-
DialogCloseTrigger,
9+
Dialog,
10+
DialogClose,
1211
DialogContent,
12+
DialogDescription,
1313
DialogFooter,
1414
DialogHeader,
15-
DialogRoot,
16-
DialogTrigger,
15+
DialogTitle,
1716
} from "@/components/ui/dialog"
17+
import { DropdownMenuItem } from "@/components/ui/dropdown-menu"
18+
import { LoadingButton } from "@/components/ui/loading-button"
1819
import useCustomToast from "@/hooks/useCustomToast"
20+
import { handleError } from "@/utils"
1921

20-
const DeleteItem = ({ id }: { id: string }) => {
22+
interface DeleteItemProps {
23+
id: string
24+
onSuccess: () => void
25+
}
26+
27+
const DeleteItem = ({ id, onSuccess }: DeleteItemProps) => {
2128
const [isOpen, setIsOpen] = useState(false)
2229
const queryClient = useQueryClient()
2330
const { showSuccessToast, showErrorToast } = useCustomToast()
2431
const {
2532
handleSubmit,
26-
formState: { isSubmitting },
2733
} = useForm()
2834

2935
const deleteItem = async (id: string) => {
@@ -35,10 +41,9 @@ const DeleteItem = ({ id }: { id: string }) => {
3541
onSuccess: () => {
3642
showSuccessToast("The item was deleted successfully")
3743
setIsOpen(false)
44+
onSuccess()
3845
},
39-
onError: () => {
40-
showErrorToast("An error occurred while deleting the item")
41-
},
46+
onError: handleError.bind(showErrorToast),
4247
onSettled: () => {
4348
queryClient.invalidateQueries()
4449
},
@@ -49,55 +54,42 @@ const DeleteItem = ({ id }: { id: string }) => {
4954
}
5055

5156
return (
52-
<DialogRoot
53-
size={{ base: "xs", md: "md" }}
54-
placement="center"
55-
role="alertdialog"
56-
open={isOpen}
57-
onOpenChange={({ open }) => setIsOpen(open)}
58-
>
59-
<DialogTrigger asChild>
60-
<Button variant="ghost" size="sm" colorPalette="red">
61-
<FiTrash2 fontSize="16px" />
62-
Delete Item
63-
</Button>
64-
</DialogTrigger>
65-
66-
<DialogContent>
57+
<Dialog open={isOpen} onOpenChange={setIsOpen}>
58+
<DropdownMenuItem
59+
variant="destructive"
60+
onSelect={(e) => e.preventDefault()}
61+
onClick={() => setIsOpen(true)}
62+
>
63+
<Trash2 />
64+
Delete Item
65+
</DropdownMenuItem>
66+
<DialogContent className="sm:max-w-md">
6767
<form onSubmit={handleSubmit(onSubmit)}>
68-
<DialogCloseTrigger />
6968
<DialogHeader>
7069
<DialogTitle>Delete Item</DialogTitle>
71-
</DialogHeader>
72-
<DialogBody>
73-
<Text mb={4}>
70+
<DialogDescription>
7471
This item will be permanently deleted. Are you sure? You will not
7572
be able to undo this action.
76-
</Text>
77-
</DialogBody>
73+
</DialogDescription>
74+
</DialogHeader>
7875

79-
<DialogFooter gap={2}>
80-
<DialogActionTrigger asChild>
81-
<Button
82-
variant="subtle"
83-
colorPalette="gray"
84-
disabled={isSubmitting}
85-
>
76+
<DialogFooter className="mt-4">
77+
<DialogClose asChild>
78+
<Button variant="outline" disabled={mutation.isPending}>
8679
Cancel
8780
</Button>
88-
</DialogActionTrigger>
89-
<Button
90-
variant="solid"
91-
colorPalette="red"
81+
</DialogClose>
82+
<LoadingButton
83+
variant="destructive"
9284
type="submit"
93-
loading={isSubmitting}
85+
loading={mutation.isPending}
9486
>
9587
Delete
96-
</Button>
88+
</LoadingButton>
9789
</DialogFooter>
9890
</form>
9991
</DialogContent>
100-
</DialogRoot>
92+
</Dialog>
10193
)
10294
}
10395

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { EllipsisVertical } from "lucide-react"
2+
import { useState } from "react"
3+
4+
import type { ItemPublic } from "@/client"
5+
import { Button } from "@/components/ui/button"
6+
import {
7+
DropdownMenu,
8+
DropdownMenuContent,
9+
DropdownMenuTrigger,
10+
} from "@/components/ui/dropdown-menu"
11+
import DeleteItem from "../Items/DeleteItem"
12+
import EditItem from "../Items/EditItem"
13+
14+
interface ItemActionsMenuProps {
15+
item: ItemPublic
16+
}
17+
18+
export const ItemActionsMenu = ({ item }: ItemActionsMenuProps) => {
19+
const [open, setOpen] = useState(false)
20+
21+
return (
22+
<DropdownMenu open={open} onOpenChange={setOpen}>
23+
<DropdownMenuTrigger asChild>
24+
<Button variant="ghost" size="icon">
25+
<EllipsisVertical />
26+
</Button>
27+
</DropdownMenuTrigger>
28+
<DropdownMenuContent align="end">
29+
<EditItem item={item} onSuccess={() => setOpen(false)} />
30+
<DeleteItem id={item.id} onSuccess={() => setOpen(false)} />
31+
</DropdownMenuContent>
32+
</DropdownMenu>
33+
)
34+
}

0 commit comments

Comments
 (0)