pythonasyncioapiasyncfastapiframeworkjsonjson-schemaopenapiopenapi3pydanticpython-typespython3redocreststarletteswaggerswagger-uiuvicornweb
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
220 lines
6.2 KiB
220 lines
6.2 KiB
import { useMutation, useQueryClient } from "@tanstack/react-query"
|
|
import { Controller, type SubmitHandler, useForm } from "react-hook-form"
|
|
|
|
import {
|
|
Button,
|
|
DialogActionTrigger,
|
|
DialogRoot,
|
|
DialogTrigger,
|
|
Flex,
|
|
Input,
|
|
Text,
|
|
VStack,
|
|
} from "@chakra-ui/react"
|
|
import { useState } from "react"
|
|
import { FaExchangeAlt } from "react-icons/fa"
|
|
|
|
import { type UserPublic, type UserUpdate, UsersService } from "@/client"
|
|
import type { ApiError } from "@/client/core/ApiError"
|
|
import useCustomToast from "@/hooks/useCustomToast"
|
|
import { emailPattern, handleError } from "@/utils"
|
|
import { Checkbox } from "../ui/checkbox"
|
|
import {
|
|
DialogBody,
|
|
DialogCloseTrigger,
|
|
DialogContent,
|
|
DialogFooter,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
} from "../ui/dialog"
|
|
import { Field } from "../ui/field"
|
|
|
|
interface EditUserProps {
|
|
user: UserPublic
|
|
}
|
|
|
|
interface UserUpdateForm extends UserUpdate {
|
|
confirm_password?: string
|
|
}
|
|
|
|
const EditUser = ({ user }: EditUserProps) => {
|
|
const [isOpen, setIsOpen] = useState(false)
|
|
const queryClient = useQueryClient()
|
|
const { showSuccessToast } = useCustomToast()
|
|
const {
|
|
control,
|
|
register,
|
|
handleSubmit,
|
|
reset,
|
|
getValues,
|
|
formState: { errors, isSubmitting },
|
|
} = useForm<UserUpdateForm>({
|
|
mode: "onBlur",
|
|
criteriaMode: "all",
|
|
defaultValues: user,
|
|
})
|
|
|
|
const mutation = useMutation({
|
|
mutationFn: (data: UserUpdateForm) =>
|
|
UsersService.updateUser({ userId: user.id, requestBody: data }),
|
|
onSuccess: () => {
|
|
showSuccessToast("User updated successfully.")
|
|
reset()
|
|
setIsOpen(false)
|
|
},
|
|
onError: (err: ApiError) => {
|
|
handleError(err)
|
|
},
|
|
onSettled: () => {
|
|
queryClient.invalidateQueries({ queryKey: ["users"] })
|
|
},
|
|
})
|
|
|
|
const onSubmit: SubmitHandler<UserUpdateForm> = async (data) => {
|
|
if (data.password === "") {
|
|
data.password = undefined
|
|
}
|
|
mutation.mutate(data)
|
|
}
|
|
|
|
return (
|
|
<DialogRoot
|
|
size={{ base: "xs", md: "md" }}
|
|
placement="center"
|
|
open={isOpen}
|
|
onOpenChange={({ open }) => setIsOpen(open)}
|
|
>
|
|
<DialogTrigger asChild>
|
|
<Button variant="ghost" size="sm">
|
|
<FaExchangeAlt fontSize="16px" />
|
|
Edit User
|
|
</Button>
|
|
</DialogTrigger>
|
|
<DialogContent>
|
|
<form onSubmit={handleSubmit(onSubmit)}>
|
|
<DialogHeader>
|
|
<DialogTitle>Edit User</DialogTitle>
|
|
</DialogHeader>
|
|
<DialogBody>
|
|
<Text mb={4}>Update the user details below.</Text>
|
|
<VStack gap={4}>
|
|
<Field
|
|
required
|
|
invalid={!!errors.email}
|
|
errorText={errors.email?.message}
|
|
label="Email"
|
|
>
|
|
<Input
|
|
id="email"
|
|
{...register("email", {
|
|
required: "Email is required",
|
|
pattern: emailPattern,
|
|
})}
|
|
placeholder="Email"
|
|
type="email"
|
|
/>
|
|
</Field>
|
|
|
|
<Field
|
|
invalid={!!errors.full_name}
|
|
errorText={errors.full_name?.message}
|
|
label="Full Name"
|
|
>
|
|
<Input
|
|
id="name"
|
|
{...register("full_name")}
|
|
placeholder="Full name"
|
|
type="text"
|
|
/>
|
|
</Field>
|
|
|
|
<Field
|
|
invalid={!!errors.password}
|
|
errorText={errors.password?.message}
|
|
label="Set Password"
|
|
>
|
|
<Input
|
|
id="password"
|
|
{...register("password", {
|
|
minLength: {
|
|
value: 8,
|
|
message: "Password must be at least 8 characters",
|
|
},
|
|
})}
|
|
placeholder="Password"
|
|
type="password"
|
|
/>
|
|
</Field>
|
|
|
|
<Field
|
|
invalid={!!errors.confirm_password}
|
|
errorText={errors.confirm_password?.message}
|
|
label="Confirm Password"
|
|
>
|
|
<Input
|
|
id="confirm_password"
|
|
{...register("confirm_password", {
|
|
validate: (value) =>
|
|
value === getValues().password ||
|
|
"The passwords do not match",
|
|
})}
|
|
placeholder="Password"
|
|
type="password"
|
|
/>
|
|
</Field>
|
|
</VStack>
|
|
|
|
<Flex mt={4} direction="column" gap={4}>
|
|
<Controller
|
|
control={control}
|
|
name="is_superuser"
|
|
render={({ field }) => (
|
|
<Field disabled={field.disabled} colorPalette="teal">
|
|
<Checkbox
|
|
checked={field.value}
|
|
onCheckedChange={({ checked }) => field.onChange(checked)}
|
|
>
|
|
Is superuser?
|
|
</Checkbox>
|
|
</Field>
|
|
)}
|
|
/>
|
|
<Controller
|
|
control={control}
|
|
name="is_active"
|
|
render={({ field }) => (
|
|
<Field disabled={field.disabled} colorPalette="teal">
|
|
<Checkbox
|
|
checked={field.value}
|
|
onCheckedChange={({ checked }) => field.onChange(checked)}
|
|
>
|
|
Is active?
|
|
</Checkbox>
|
|
</Field>
|
|
)}
|
|
/>
|
|
</Flex>
|
|
</DialogBody>
|
|
|
|
<DialogFooter gap={2}>
|
|
<DialogActionTrigger asChild>
|
|
<Button
|
|
variant="subtle"
|
|
colorPalette="gray"
|
|
disabled={isSubmitting}
|
|
>
|
|
Cancel
|
|
</Button>
|
|
</DialogActionTrigger>
|
|
<Button variant="solid" type="submit" loading={isSubmitting}>
|
|
Save
|
|
</Button>
|
|
</DialogFooter>
|
|
<DialogCloseTrigger />
|
|
</form>
|
|
</DialogContent>
|
|
</DialogRoot>
|
|
)
|
|
}
|
|
|
|
export default EditUser
|
|
|