Skip to content

🛂 Migrate to Chakra UI v3 #1496

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

Merged
merged 9 commits into from
Feb 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion frontend/openapi-ts.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export default defineConfig({
// @ts-ignore
let name: string = operation.name
// @ts-ignore
let service: string = operation.service
const service: string = operation.service

if (service && name.toLowerCase().startsWith(service.toLowerCase())) {
name = name.slice(service.length)
Expand Down
8,793 changes: 3,970 additions & 4,823 deletions frontend/package-lock.json

Large diffs are not rendered by default.

10 changes: 4 additions & 6 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,19 @@
"generate-client": "openapi-ts"
},
"dependencies": {
"@chakra-ui/icons": "2.1.1",
"@chakra-ui/react": "2.8.2",
"@emotion/react": "11.11.3",
"@emotion/styled": "11.11.0",
"@chakra-ui/react": "^3.8.0",
"@emotion/react": "^11.14.0",
"@tanstack/react-query": "^5.28.14",
"@tanstack/react-query-devtools": "^5.28.14",
"@tanstack/react-router": "1.19.1",
"axios": "1.7.4",
"form-data": "4.0.0",
"framer-motion": "10.16.16",
"next-themes": "^0.4.4",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-error-boundary": "^4.0.13",
"react-hook-form": "7.49.3",
"react-icons": "5.0.1"
"react-icons": "^5.4.0"
},
"devDependencies": {
"@biomejs/biome": "1.6.1",
Expand Down
292 changes: 170 additions & 122 deletions frontend/src/components/Admin/AddUser.tsx
Original file line number Diff line number Diff line change
@@ -1,45 +1,48 @@
import { useMutation, useQueryClient } from "@tanstack/react-query"
import { Controller, type SubmitHandler, useForm } from "react-hook-form"

import {
Button,
Checkbox,
DialogActionTrigger,
DialogTitle,
Flex,
FormControl,
FormErrorMessage,
FormLabel,
Input,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
Text,
VStack,
} from "@chakra-ui/react"
import { useMutation, useQueryClient } from "@tanstack/react-query"
import { type SubmitHandler, useForm } from "react-hook-form"

import { useState } from "react"
import { FaPlus } from "react-icons/fa"
import { type UserCreate, UsersService } from "../../client"
import type { ApiError } from "../../client/core/ApiError"
import useCustomToast from "../../hooks/useCustomToast"
import { emailPattern, handleError } from "../../utils"

interface AddUserProps {
isOpen: boolean
onClose: () => void
}
import { Checkbox } from "../ui/checkbox"
import {
DialogBody,
DialogCloseTrigger,
DialogContent,
DialogFooter,
DialogHeader,
DialogRoot,
DialogTrigger,
} from "../ui/dialog"
import { Field } from "../ui/field"

interface UserCreateForm extends UserCreate {
confirm_password: string
}

const AddUser = ({ isOpen, onClose }: AddUserProps) => {
const AddUser = () => {
const [isOpen, setIsOpen] = useState(false)
const queryClient = useQueryClient()
const showToast = useCustomToast()
const { showSuccessToast } = useCustomToast()
const {
control,
register,
handleSubmit,
reset,
getValues,
formState: { errors, isSubmitting },
formState: { errors, isValid, isSubmitting },
} = useForm<UserCreateForm>({
mode: "onBlur",
criteriaMode: "all",
Expand All @@ -57,12 +60,12 @@ const AddUser = ({ isOpen, onClose }: AddUserProps) => {
mutationFn: (data: UserCreate) =>
UsersService.createUser({ requestBody: data }),
onSuccess: () => {
showToast("Success!", "User created successfully.", "success")
showSuccessToast("User created successfully.")
reset()
onClose()
setIsOpen(false)
},
onError: (err: ApiError) => {
handleError(err, showToast)
handleError(err)
},
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ["users"] })
Expand All @@ -74,108 +77,153 @@ const AddUser = ({ isOpen, onClose }: AddUserProps) => {
}

return (
<>
<Modal
isOpen={isOpen}
onClose={onClose}
size={{ base: "sm", md: "md" }}
isCentered
>
<ModalOverlay />
<ModalContent as="form" onSubmit={handleSubmit(onSubmit)}>
<ModalHeader>Add User</ModalHeader>
<ModalCloseButton />
<ModalBody pb={6}>
<FormControl isRequired isInvalid={!!errors.email}>
<FormLabel htmlFor="email">Email</FormLabel>
<Input
id="email"
{...register("email", {
required: "Email is required",
pattern: emailPattern,
})}
placeholder="Email"
type="email"
/>
{errors.email && (
<FormErrorMessage>{errors.email.message}</FormErrorMessage>
)}
</FormControl>
<FormControl mt={4} isInvalid={!!errors.full_name}>
<FormLabel htmlFor="name">Full name</FormLabel>
<Input
id="name"
{...register("full_name")}
placeholder="Full name"
type="text"
/>
{errors.full_name && (
<FormErrorMessage>{errors.full_name.message}</FormErrorMessage>
)}
</FormControl>
<FormControl mt={4} isRequired isInvalid={!!errors.password}>
<FormLabel htmlFor="password">Set Password</FormLabel>
<Input
id="password"
{...register("password", {
required: "Password is required",
minLength: {
value: 8,
message: "Password must be at least 8 characters",
},
})}
placeholder="Password"
type="password"
<DialogRoot
size={{ base: "xs", md: "md" }}
placement="center"
open={isOpen}
onOpenChange={({ open }) => setIsOpen(open)}
>
<DialogTrigger asChild>
<Button value="add-user" my={4}>
<FaPlus fontSize="16px" />
Add User
</Button>
</DialogTrigger>
<DialogContent>
<form onSubmit={handleSubmit(onSubmit)}>
<DialogHeader>
<DialogTitle>Add User</DialogTitle>
</DialogHeader>
<DialogBody>
<Text mb={4}>
Fill in the form below to add a new user to the system.
</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
required
invalid={!!errors.password}
errorText={errors.password?.message}
label="Set Password"
>
<Input
id="password"
{...register("password", {
required: "Password is required",
minLength: {
value: 8,
message: "Password must be at least 8 characters",
},
})}
placeholder="Password"
type="password"
/>
</Field>

<Field
required
invalid={!!errors.confirm_password}
errorText={errors.confirm_password?.message}
label="Confirm Password"
>
<Input
id="confirm_password"
{...register("confirm_password", {
required: "Please confirm your 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>
)}
/>
{errors.password && (
<FormErrorMessage>{errors.password.message}</FormErrorMessage>
)}
</FormControl>
<FormControl
mt={4}
isRequired
isInvalid={!!errors.confirm_password}
>
<FormLabel htmlFor="confirm_password">Confirm Password</FormLabel>
<Input
id="confirm_password"
{...register("confirm_password", {
required: "Please confirm your password",
validate: (value) =>
value === getValues().password ||
"The passwords do not match",
})}
placeholder="Password"
type="password"
<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>
)}
/>
{errors.confirm_password && (
<FormErrorMessage>
{errors.confirm_password.message}
</FormErrorMessage>
)}
</FormControl>
<Flex mt={4}>
<FormControl>
<Checkbox {...register("is_superuser")} colorScheme="teal">
Is superuser?
</Checkbox>
</FormControl>
<FormControl>
<Checkbox {...register("is_active")} colorScheme="teal">
Is active?
</Checkbox>
</FormControl>
</Flex>
</ModalBody>
<ModalFooter gap={3}>
<Button variant="primary" type="submit" isLoading={isSubmitting}>
</DialogBody>

<DialogFooter gap={2}>
<DialogActionTrigger asChild>
<Button
variant="subtle"
colorPalette="gray"
disabled={isSubmitting}
>
Cancel
</Button>
</DialogActionTrigger>
<Button
variant="solid"
type="submit"
disabled={!isValid}
loading={isSubmitting}
>
Save
</Button>
<Button onClick={onClose}>Cancel</Button>
</ModalFooter>
</ModalContent>
</Modal>
</>
</DialogFooter>
</form>
<DialogCloseTrigger />
</DialogContent>
</DialogRoot>
)
}

Expand Down
Loading