Skip to content

✨ Add form validation to Admin, Items and Login #616

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 2 commits into from
Feb 28, 2024
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
63 changes: 38 additions & 25 deletions src/new-frontend/src/components/Admin/AddUser.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';

import { Button, Checkbox, Flex, FormControl, FormLabel, Input, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay } from '@chakra-ui/react';
import { Button, Checkbox, Flex, FormControl, FormErrorMessage, FormLabel, Input, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay } from '@chakra-ui/react';
import { SubmitHandler, useForm } from 'react-hook-form';

import { UserCreate } from '../../client';
Expand All @@ -14,29 +14,35 @@ interface AddUserProps {
}

interface UserCreateForm extends UserCreate {
confirmPassword: string;
confirm_password: string;

}

const AddUser: React.FC<AddUserProps> = ({ isOpen, onClose }) => {
const showToast = useCustomToast();
const { register, handleSubmit, reset, formState: { isSubmitting } } = useForm<UserCreateForm>();
const { register, handleSubmit, reset, getValues, formState: { errors, isSubmitting } } = useForm<UserCreateForm>({
mode: 'onBlur',
criteriaMode: 'all',
defaultValues: {
email: '',
full_name: '',
password: '',
confirm_password: '',
is_superuser: false,
is_active: false
}
});
const { addUser } = useUsersStore();

const onSubmit: SubmitHandler<UserCreateForm> = async (data) => {
if (data.password === data.confirmPassword) {
try {
await addUser(data);
showToast('Success!', 'User created successfully.', 'success');
reset();
onClose();
} catch (err) {
const errDetail = (err as ApiError).body.detail;
showToast('Something went wrong.', `${errDetail}`, 'error');
}
} else {
// TODO: Complete when form validation is implemented
console.log("Passwords don't match")
try {
await addUser(data);
showToast('Success!', 'User created successfully.', 'success');
reset();
onClose();
} catch (err) {
const errDetail = (err as ApiError).body.detail;
showToast('Something went wrong.', `${errDetail}`, 'error');
}
}

Expand All @@ -53,21 +59,28 @@ const AddUser: React.FC<AddUserProps> = ({ isOpen, onClose }) => {
<ModalHeader>Add User</ModalHeader>
<ModalCloseButton />
<ModalBody pb={6} >
<FormControl>
<FormControl isRequired isInvalid={!!errors.email}>
<FormLabel htmlFor='email'>Email</FormLabel>
<Input id='email' {...register('email')} placeholder='Email' type='email' />
<Input id='email' {...register('email', { required: 'Email is required', pattern: { value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i, message: 'Invalid email address' } })} placeholder='Email' type='email' />
{errors.email && <FormErrorMessage>{errors.email.message}</FormErrorMessage>}
</FormControl>
<FormControl mt={4}>
<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}>
<FormControl mt={4} isRequired isInvalid={!!errors.password}>
<FormLabel htmlFor='password'>Set Password</FormLabel>
<Input id='password' {...register('password')} placeholder='Password' type='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' />
{errors.password && <FormErrorMessage>{errors.password.message}</FormErrorMessage>}
</FormControl>
<FormControl mt={4}>
<FormLabel htmlFor='confirmPassword'>Confirm Password</FormLabel>
<Input id='confirmPassword' {...register('confirmPassword')} placeholder='Password' type='password' />
<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' />
{errors.confirm_password && <FormErrorMessage>{errors.confirm_password.message}</FormErrorMessage>}
</FormControl>
<Flex mt={4}>
<FormControl>
Expand All @@ -79,7 +92,7 @@ const AddUser: React.FC<AddUserProps> = ({ isOpen, onClose }) => {
</Flex>
</ModalBody>
<ModalFooter gap={3}>
<Button bg='ui.main' color='white' type='submit' isLoading={isSubmitting}>
<Button bg='ui.main' color='white' _hover={{ opacity: 0.8 }} type='submit' isLoading={isSubmitting}>
Save
</Button>
<Button onClick={onClose}>Cancel</Button>
Expand Down
66 changes: 40 additions & 26 deletions src/new-frontend/src/components/Admin/EditUser.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';

import { Button, Checkbox, Flex, FormControl, FormLabel, Input, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay } from '@chakra-ui/react';
import { Button, Checkbox, Flex, FormControl, FormErrorMessage, FormLabel, Input, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay } from '@chakra-ui/react';
import { SubmitHandler, useForm } from 'react-hook-form';

import { ApiError, UserUpdate } from '../../client';
Expand All @@ -19,25 +19,34 @@ interface UserUpdateForm extends UserUpdate {

const EditUser: React.FC<EditUserProps> = ({ user_id, isOpen, onClose }) => {
const showToast = useCustomToast();
const { register, handleSubmit, reset, formState: { isSubmitting } } = useForm<UserUpdateForm>();
const { editUser, users } = useUsersStore();

const currentUser = users.find((user) => user.id === user_id);
const { register, handleSubmit, reset, getValues, formState: { errors, isSubmitting } } = useForm<UserUpdateForm>({
mode: 'onBlur',
criteriaMode: 'all',
defaultValues: {
email: currentUser?.email,
full_name: currentUser?.full_name,
password: '',
confirm_password: '',
is_superuser: currentUser?.is_superuser,
is_active: currentUser?.is_active
}
});


const onSubmit: SubmitHandler<UserUpdateForm> = async (data) => {
if (data.password === data.confirm_password) {
try {
await editUser(user_id, data);
showToast('Success!', 'User updated successfully.', 'success');
reset();
onClose();
} catch (err) {
const errDetail = (err as ApiError).body.detail;
showToast('Something went wrong.', `${errDetail}`, 'error');
try {
if (data.password === '') {
delete data.password;
}
} else {
// TODO: Complete when form validation is implemented
console.log("Passwords don't match")
await editUser(user_id, data);
showToast('Success!', 'User updated successfully.', 'success');
reset();
onClose();
} catch (err) {
const errDetail = (err as ApiError).body.detail;
showToast('Something went wrong.', `${errDetail}`, 'error');
}
}

Expand All @@ -59,28 +68,33 @@ const EditUser: React.FC<EditUserProps> = ({ user_id, isOpen, onClose }) => {
<ModalHeader>Edit User</ModalHeader>
<ModalCloseButton />
<ModalBody pb={6}>
<FormControl>
<FormControl isInvalid={!!errors.email}>
<FormLabel htmlFor='email'>Email</FormLabel>
<Input id="email" {...register('email')} defaultValue={currentUser?.email} type='email' />
<Input id='email' {...register('email', { pattern: { value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i, message: 'Invalid email address' } })} placeholder='Email' type='email' />
{errors.email && <FormErrorMessage>{errors.email.message}</FormErrorMessage>}
</FormControl>
<FormControl mt={4}>
<FormLabel htmlFor='name'>Full name</FormLabel>
<Input id="name" {...register('full_name')} defaultValue={currentUser?.full_name} type='text' />
<Input id="name" {...register('full_name')} type='text' />
</FormControl>
<FormControl mt={4}>
<FormLabel htmlFor='password'>Password</FormLabel>
<Input id="password" {...register('password')} placeholder='••••••••' type='password' />
<FormControl mt={4} isInvalid={!!errors.password}>
<FormLabel htmlFor='password'>Set Password</FormLabel>
<Input id='password' {...register('password', { minLength: { value: 8, message: 'Password must be at least 8 characters' } })} placeholder='••••••••' type='password' />
{errors.password && <FormErrorMessage>{errors.password.message}</FormErrorMessage>}
</FormControl>
<FormControl mt={4}>
<FormLabel htmlFor='confirmPassword'>Confirmation Password</FormLabel>
<Input id='confirmPassword' {...register('confirm_password')} placeholder='••••••••' type='password' />
<FormControl mt={4} isInvalid={!!errors.confirm_password}>
<FormLabel htmlFor='confirm_password'>Confirm Password</FormLabel>
<Input id='confirm_password' {...register('confirm_password', {
validate: value => value === getValues().password || 'The passwords do not match'
})} placeholder='••••••••' type='password' />
{errors.confirm_password && <FormErrorMessage>{errors.confirm_password.message}</FormErrorMessage>}
</FormControl>
<Flex>
<FormControl mt={4}>
<Checkbox {...register('is_superuser')} defaultChecked={currentUser?.is_superuser} colorScheme='teal'>Is superuser?</Checkbox>
<Checkbox {...register('is_superuser')} colorScheme='teal'>Is superuser?</Checkbox>
</FormControl>
<FormControl mt={4}>
<Checkbox {...register('is_active')} defaultChecked={currentUser?.is_active} colorScheme='teal'>Is active?</Checkbox>
<Checkbox {...register('is_active')} colorScheme='teal'>Is active?</Checkbox>
</FormControl>
</Flex>
</ModalBody>
Expand Down
10 changes: 3 additions & 7 deletions src/new-frontend/src/components/Common/DeleteAlert.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,17 @@ interface DeleteProps {
const Delete: React.FC<DeleteProps> = ({ type, id, isOpen, onClose }) => {
const showToast = useCustomToast();
const cancelRef = React.useRef<HTMLButtonElement | null>(null);
const [isLoading, setIsLoading] = useState(false);
const { handleSubmit } = useForm();
const { handleSubmit, formState: {isSubmitting} } = useForm();
const { deleteItem } = useItemsStore();
const { deleteUser } = useUsersStore();

const onSubmit = async () => {
setIsLoading(true);
try {
type === 'Item' ? await deleteItem(id) : await deleteUser(id);
showToast('Success', `The ${type.toLowerCase()} was deleted successfully.`, 'success');
onClose();
} catch (err) {
showToast('An error occurred.', `An error occurred while deleting the ${type.toLowerCase()}.`, 'error');
} finally {
setIsLoading(false);
}
}

Expand All @@ -56,10 +52,10 @@ const Delete: React.FC<DeleteProps> = ({ type, id, isOpen, onClose }) => {
</AlertDialogBody>

<AlertDialogFooter gap={3}>
<Button bg="ui.danger" color="white" _hover={{ opacity: 0.8 }} type="submit" isLoading={isLoading}>
<Button bg="ui.danger" color="white" _hover={{ opacity: 0.8 }} type="submit" isLoading={isSubmitting}>
Delete
</Button>
<Button ref={cancelRef} onClick={onClose} isDisabled={isLoading}>
<Button ref={cancelRef} onClick={onClose} isDisabled={isSubmitting}>
Cancel
</Button>
</AlertDialogFooter>
Expand Down
26 changes: 15 additions & 11 deletions src/new-frontend/src/components/Items/AddItem.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useState } from 'react';
import React from 'react';

import { Button, FormControl, FormLabel, Input, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay } from '@chakra-ui/react';
import { Button, FormControl, FormErrorMessage, FormLabel, Input, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay } from '@chakra-ui/react';
import { SubmitHandler, useForm } from 'react-hook-form';

import { ApiError, ItemCreate } from '../../client';
Expand All @@ -14,12 +14,17 @@ interface AddItemProps {

const AddItem: React.FC<AddItemProps> = ({ isOpen, onClose }) => {
const showToast = useCustomToast();
const [isLoading, setIsLoading] = useState(false);
const { register, handleSubmit, reset } = useForm<ItemCreate>();
const { register, handleSubmit, reset, formState: { errors, isSubmitting } } = useForm<ItemCreate>({
mode: 'onBlur',
criteriaMode: 'all',
defaultValues: {
title: '',
description: '',
},
});
const { addItem } = useItemsStore();

const onSubmit: SubmitHandler<ItemCreate> = async (data) => {
setIsLoading(true);
try {
await addItem(data);
showToast('Success!', 'Item created successfully.', 'success');
Expand All @@ -28,8 +33,6 @@ const AddItem: React.FC<AddItemProps> = ({ isOpen, onClose }) => {
} catch (err) {
const errDetail = (err as ApiError).body.detail;
showToast('Something went wrong.', `${errDetail}`, 'error');
} finally {
setIsLoading(false);
}
};

Expand All @@ -46,14 +49,15 @@ const AddItem: React.FC<AddItemProps> = ({ isOpen, onClose }) => {
<ModalHeader>Add Item</ModalHeader>
<ModalCloseButton />
<ModalBody pb={6}>
<FormControl>
<FormControl isRequired isInvalid={!!errors.title}>
<FormLabel htmlFor='title'>Title</FormLabel>
<Input
id='title'
{...register('title')}
{...register('title', { required: 'Title is required.' })}
placeholder='Title'
type='text'
/>
{errors.title && <FormErrorMessage>{errors.title.message}</FormErrorMessage>}
</FormControl>
<FormControl mt={4}>
<FormLabel htmlFor='description'>Description</FormLabel>
Expand All @@ -67,10 +71,10 @@ const AddItem: React.FC<AddItemProps> = ({ isOpen, onClose }) => {
</ModalBody>

<ModalFooter gap={3}>
<Button bg='ui.main' color='white' _hover={{ opacity: 0.8 }} type='submit' isLoading={isLoading}>
<Button bg='ui.main' color='white' _hover={{ opacity: 0.8 }} type='submit' isLoading={isSubmitting}>
Save
</Button>
<Button onClick={onClose} isDisabled={isLoading}>
<Button onClick={onClose}>
Cancel
</Button>
</ModalFooter>
Expand Down
7 changes: 3 additions & 4 deletions src/new-frontend/src/components/Items/EditItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,9 @@ interface EditItemProps {

const EditItem: React.FC<EditItemProps> = ({ id, isOpen, onClose }) => {
const showToast = useCustomToast();
const { register, handleSubmit, reset, formState: { isSubmitting }, } = useForm<ItemUpdate>();
const { editItem, items } = useItemsStore();

const currentItem = items.find((item) => item.id === id);
const { register, handleSubmit, reset, formState: { isSubmitting }, } = useForm<ItemUpdate>({ defaultValues: { title: currentItem?.title, description: currentItem?.description } });

const onSubmit: SubmitHandler<ItemUpdate> = async (data) => {
try {
Expand Down Expand Up @@ -52,11 +51,11 @@ const EditItem: React.FC<EditItemProps> = ({ id, isOpen, onClose }) => {
<ModalBody pb={6}>
<FormControl>
<FormLabel htmlFor='title'>Title</FormLabel>
<Input id='title' {...register('title')} defaultValue={currentItem?.title} type='text' />
<Input id='title' {...register('title')} type='text' />
</FormControl>
<FormControl mt={4}>
<FormLabel htmlFor='description'>Description</FormLabel>
<Input id='description' {...register('description')} defaultValue={currentItem?.description} placeholder='Description' type='text' />
<Input id='description' {...register('description')} placeholder='Description' type='text' />
</FormControl>
</ModalBody>
<ModalFooter gap={3}>
Expand Down
4 changes: 2 additions & 2 deletions src/new-frontend/src/components/UserSettings/Appearance.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ const Appearance: React.FC = () => {
<Stack>
{/* TODO: Add system default option */}
<Radio value='light' colorScheme='teal'>
Light Mode<Badge ml='1' colorScheme='teal'>Default</Badge>
Light mode<Badge ml='1' colorScheme='teal'>Default</Badge>
</Radio>
<Radio value='dark' colorScheme='teal'>
Dark Mode
Dark mode
</Radio>
</Stack>
</RadioGroup>
Expand Down
Loading