✨ Add form validation to Admin, Items and Login (#616)
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
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 { SubmitHandler, useForm } from 'react-hook-form';
|
||||||
|
|
||||||
import { UserCreate } from '../../client';
|
import { UserCreate } from '../../client';
|
||||||
@@ -14,17 +14,27 @@ interface AddUserProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface UserCreateForm extends UserCreate {
|
interface UserCreateForm extends UserCreate {
|
||||||
confirmPassword: string;
|
confirm_password: string;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const AddUser: React.FC<AddUserProps> = ({ isOpen, onClose }) => {
|
const AddUser: React.FC<AddUserProps> = ({ isOpen, onClose }) => {
|
||||||
const showToast = useCustomToast();
|
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 { addUser } = useUsersStore();
|
||||||
|
|
||||||
const onSubmit: SubmitHandler<UserCreateForm> = async (data) => {
|
const onSubmit: SubmitHandler<UserCreateForm> = async (data) => {
|
||||||
if (data.password === data.confirmPassword) {
|
|
||||||
try {
|
try {
|
||||||
await addUser(data);
|
await addUser(data);
|
||||||
showToast('Success!', 'User created successfully.', 'success');
|
showToast('Success!', 'User created successfully.', 'success');
|
||||||
@@ -34,10 +44,6 @@ const AddUser: React.FC<AddUserProps> = ({ isOpen, onClose }) => {
|
|||||||
const errDetail = (err as ApiError).body.detail;
|
const errDetail = (err as ApiError).body.detail;
|
||||||
showToast('Something went wrong.', `${errDetail}`, 'error');
|
showToast('Something went wrong.', `${errDetail}`, 'error');
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// TODO: Complete when form validation is implemented
|
|
||||||
console.log("Passwords don't match")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -53,21 +59,28 @@ const AddUser: React.FC<AddUserProps> = ({ isOpen, onClose }) => {
|
|||||||
<ModalHeader>Add User</ModalHeader>
|
<ModalHeader>Add User</ModalHeader>
|
||||||
<ModalCloseButton />
|
<ModalCloseButton />
|
||||||
<ModalBody pb={6} >
|
<ModalBody pb={6} >
|
||||||
<FormControl>
|
<FormControl isRequired isInvalid={!!errors.email}>
|
||||||
<FormLabel htmlFor='email'>Email</FormLabel>
|
<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>
|
||||||
<FormControl mt={4}>
|
<FormControl mt={4} isInvalid={!!errors.full_name}>
|
||||||
<FormLabel htmlFor='name'>Full name</FormLabel>
|
<FormLabel htmlFor='name'>Full name</FormLabel>
|
||||||
<Input id='name' {...register('full_name')} placeholder='Full name' type='text' />
|
<Input id='name' {...register('full_name')} placeholder='Full name' type='text' />
|
||||||
|
{errors.full_name && <FormErrorMessage>{errors.full_name.message}</FormErrorMessage>}
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl mt={4}>
|
<FormControl mt={4} isRequired isInvalid={!!errors.password}>
|
||||||
<FormLabel htmlFor='password'>Set Password</FormLabel>
|
<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>
|
||||||
<FormControl mt={4}>
|
<FormControl mt={4} isRequired isInvalid={!!errors.confirm_password}>
|
||||||
<FormLabel htmlFor='confirmPassword'>Confirm Password</FormLabel>
|
<FormLabel htmlFor='confirm_password'>Confirm Password</FormLabel>
|
||||||
<Input id='confirmPassword' {...register('confirmPassword')} placeholder='Password' type='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' />
|
||||||
|
{errors.confirm_password && <FormErrorMessage>{errors.confirm_password.message}</FormErrorMessage>}
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<Flex mt={4}>
|
<Flex mt={4}>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
@@ -79,7 +92,7 @@ const AddUser: React.FC<AddUserProps> = ({ isOpen, onClose }) => {
|
|||||||
</Flex>
|
</Flex>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
<ModalFooter gap={3}>
|
<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
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={onClose}>Cancel</Button>
|
<Button onClick={onClose}>Cancel</Button>
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
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 { SubmitHandler, useForm } from 'react-hook-form';
|
||||||
|
|
||||||
import { ApiError, UserUpdate } from '../../client';
|
import { ApiError, UserUpdate } from '../../client';
|
||||||
@@ -19,14 +19,27 @@ interface UserUpdateForm extends UserUpdate {
|
|||||||
|
|
||||||
const EditUser: React.FC<EditUserProps> = ({ user_id, isOpen, onClose }) => {
|
const EditUser: React.FC<EditUserProps> = ({ user_id, isOpen, onClose }) => {
|
||||||
const showToast = useCustomToast();
|
const showToast = useCustomToast();
|
||||||
const { register, handleSubmit, reset, formState: { isSubmitting } } = useForm<UserUpdateForm>();
|
|
||||||
const { editUser, users } = useUsersStore();
|
const { editUser, users } = useUsersStore();
|
||||||
|
|
||||||
const currentUser = users.find((user) => user.id === user_id);
|
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) => {
|
const onSubmit: SubmitHandler<UserUpdateForm> = async (data) => {
|
||||||
if (data.password === data.confirm_password) {
|
|
||||||
try {
|
try {
|
||||||
|
if (data.password === '') {
|
||||||
|
delete data.password;
|
||||||
|
}
|
||||||
await editUser(user_id, data);
|
await editUser(user_id, data);
|
||||||
showToast('Success!', 'User updated successfully.', 'success');
|
showToast('Success!', 'User updated successfully.', 'success');
|
||||||
reset();
|
reset();
|
||||||
@@ -35,10 +48,6 @@ const EditUser: React.FC<EditUserProps> = ({ user_id, isOpen, onClose }) => {
|
|||||||
const errDetail = (err as ApiError).body.detail;
|
const errDetail = (err as ApiError).body.detail;
|
||||||
showToast('Something went wrong.', `${errDetail}`, 'error');
|
showToast('Something went wrong.', `${errDetail}`, 'error');
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// TODO: Complete when form validation is implemented
|
|
||||||
console.log("Passwords don't match")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const onCancel = () => {
|
const onCancel = () => {
|
||||||
@@ -59,28 +68,33 @@ const EditUser: React.FC<EditUserProps> = ({ user_id, isOpen, onClose }) => {
|
|||||||
<ModalHeader>Edit User</ModalHeader>
|
<ModalHeader>Edit User</ModalHeader>
|
||||||
<ModalCloseButton />
|
<ModalCloseButton />
|
||||||
<ModalBody pb={6}>
|
<ModalBody pb={6}>
|
||||||
<FormControl>
|
<FormControl isInvalid={!!errors.email}>
|
||||||
<FormLabel htmlFor='email'>Email</FormLabel>
|
<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>
|
||||||
<FormControl mt={4}>
|
<FormControl mt={4}>
|
||||||
<FormLabel htmlFor='name'>Full name</FormLabel>
|
<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>
|
||||||
<FormControl mt={4}>
|
<FormControl mt={4} isInvalid={!!errors.password}>
|
||||||
<FormLabel htmlFor='password'>Password</FormLabel>
|
<FormLabel htmlFor='password'>Set Password</FormLabel>
|
||||||
<Input id="password" {...register('password')} placeholder='••••••••' type='password' />
|
<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>
|
||||||
<FormControl mt={4}>
|
<FormControl mt={4} isInvalid={!!errors.confirm_password}>
|
||||||
<FormLabel htmlFor='confirmPassword'>Confirmation Password</FormLabel>
|
<FormLabel htmlFor='confirm_password'>Confirm Password</FormLabel>
|
||||||
<Input id='confirmPassword' {...register('confirm_password')} placeholder='••••••••' type='password' />
|
<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>
|
</FormControl>
|
||||||
<Flex>
|
<Flex>
|
||||||
<FormControl mt={4}>
|
<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>
|
||||||
<FormControl mt={4}>
|
<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>
|
</FormControl>
|
||||||
</Flex>
|
</Flex>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
|
@@ -17,21 +17,17 @@ interface DeleteProps {
|
|||||||
const Delete: React.FC<DeleteProps> = ({ type, id, isOpen, onClose }) => {
|
const Delete: React.FC<DeleteProps> = ({ type, id, isOpen, onClose }) => {
|
||||||
const showToast = useCustomToast();
|
const showToast = useCustomToast();
|
||||||
const cancelRef = React.useRef<HTMLButtonElement | null>(null);
|
const cancelRef = React.useRef<HTMLButtonElement | null>(null);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const { handleSubmit, formState: {isSubmitting} } = useForm();
|
||||||
const { handleSubmit } = useForm();
|
|
||||||
const { deleteItem } = useItemsStore();
|
const { deleteItem } = useItemsStore();
|
||||||
const { deleteUser } = useUsersStore();
|
const { deleteUser } = useUsersStore();
|
||||||
|
|
||||||
const onSubmit = async () => {
|
const onSubmit = async () => {
|
||||||
setIsLoading(true);
|
|
||||||
try {
|
try {
|
||||||
type === 'Item' ? await deleteItem(id) : await deleteUser(id);
|
type === 'Item' ? await deleteItem(id) : await deleteUser(id);
|
||||||
showToast('Success', `The ${type.toLowerCase()} was deleted successfully.`, 'success');
|
showToast('Success', `The ${type.toLowerCase()} was deleted successfully.`, 'success');
|
||||||
onClose();
|
onClose();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
showToast('An error occurred.', `An error occurred while deleting the ${type.toLowerCase()}.`, 'error');
|
showToast('An error occurred.', `An error occurred while deleting the ${type.toLowerCase()}.`, 'error');
|
||||||
} finally {
|
|
||||||
setIsLoading(false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,10 +52,10 @@ const Delete: React.FC<DeleteProps> = ({ type, id, isOpen, onClose }) => {
|
|||||||
</AlertDialogBody>
|
</AlertDialogBody>
|
||||||
|
|
||||||
<AlertDialogFooter gap={3}>
|
<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
|
Delete
|
||||||
</Button>
|
</Button>
|
||||||
<Button ref={cancelRef} onClick={onClose} isDisabled={isLoading}>
|
<Button ref={cancelRef} onClick={onClose} isDisabled={isSubmitting}>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
</AlertDialogFooter>
|
</AlertDialogFooter>
|
||||||
|
@@ -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 { SubmitHandler, useForm } from 'react-hook-form';
|
||||||
|
|
||||||
import { ApiError, ItemCreate } from '../../client';
|
import { ApiError, ItemCreate } from '../../client';
|
||||||
@@ -14,12 +14,17 @@ interface AddItemProps {
|
|||||||
|
|
||||||
const AddItem: React.FC<AddItemProps> = ({ isOpen, onClose }) => {
|
const AddItem: React.FC<AddItemProps> = ({ isOpen, onClose }) => {
|
||||||
const showToast = useCustomToast();
|
const showToast = useCustomToast();
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const { register, handleSubmit, reset, formState: { errors, isSubmitting } } = useForm<ItemCreate>({
|
||||||
const { register, handleSubmit, reset } = useForm<ItemCreate>();
|
mode: 'onBlur',
|
||||||
|
criteriaMode: 'all',
|
||||||
|
defaultValues: {
|
||||||
|
title: '',
|
||||||
|
description: '',
|
||||||
|
},
|
||||||
|
});
|
||||||
const { addItem } = useItemsStore();
|
const { addItem } = useItemsStore();
|
||||||
|
|
||||||
const onSubmit: SubmitHandler<ItemCreate> = async (data) => {
|
const onSubmit: SubmitHandler<ItemCreate> = async (data) => {
|
||||||
setIsLoading(true);
|
|
||||||
try {
|
try {
|
||||||
await addItem(data);
|
await addItem(data);
|
||||||
showToast('Success!', 'Item created successfully.', 'success');
|
showToast('Success!', 'Item created successfully.', 'success');
|
||||||
@@ -28,8 +33,6 @@ const AddItem: React.FC<AddItemProps> = ({ isOpen, onClose }) => {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
const errDetail = (err as ApiError).body.detail;
|
const errDetail = (err as ApiError).body.detail;
|
||||||
showToast('Something went wrong.', `${errDetail}`, 'error');
|
showToast('Something went wrong.', `${errDetail}`, 'error');
|
||||||
} finally {
|
|
||||||
setIsLoading(false);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -46,14 +49,15 @@ const AddItem: React.FC<AddItemProps> = ({ isOpen, onClose }) => {
|
|||||||
<ModalHeader>Add Item</ModalHeader>
|
<ModalHeader>Add Item</ModalHeader>
|
||||||
<ModalCloseButton />
|
<ModalCloseButton />
|
||||||
<ModalBody pb={6}>
|
<ModalBody pb={6}>
|
||||||
<FormControl>
|
<FormControl isRequired isInvalid={!!errors.title}>
|
||||||
<FormLabel htmlFor='title'>Title</FormLabel>
|
<FormLabel htmlFor='title'>Title</FormLabel>
|
||||||
<Input
|
<Input
|
||||||
id='title'
|
id='title'
|
||||||
{...register('title')}
|
{...register('title', { required: 'Title is required.' })}
|
||||||
placeholder='Title'
|
placeholder='Title'
|
||||||
type='text'
|
type='text'
|
||||||
/>
|
/>
|
||||||
|
{errors.title && <FormErrorMessage>{errors.title.message}</FormErrorMessage>}
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl mt={4}>
|
<FormControl mt={4}>
|
||||||
<FormLabel htmlFor='description'>Description</FormLabel>
|
<FormLabel htmlFor='description'>Description</FormLabel>
|
||||||
@@ -67,10 +71,10 @@ const AddItem: React.FC<AddItemProps> = ({ isOpen, onClose }) => {
|
|||||||
</ModalBody>
|
</ModalBody>
|
||||||
|
|
||||||
<ModalFooter gap={3}>
|
<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
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={onClose} isDisabled={isLoading}>
|
<Button onClick={onClose}>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
|
@@ -15,10 +15,9 @@ interface EditItemProps {
|
|||||||
|
|
||||||
const EditItem: React.FC<EditItemProps> = ({ id, isOpen, onClose }) => {
|
const EditItem: React.FC<EditItemProps> = ({ id, isOpen, onClose }) => {
|
||||||
const showToast = useCustomToast();
|
const showToast = useCustomToast();
|
||||||
const { register, handleSubmit, reset, formState: { isSubmitting }, } = useForm<ItemUpdate>();
|
|
||||||
const { editItem, items } = useItemsStore();
|
const { editItem, items } = useItemsStore();
|
||||||
|
|
||||||
const currentItem = items.find((item) => item.id === id);
|
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) => {
|
const onSubmit: SubmitHandler<ItemUpdate> = async (data) => {
|
||||||
try {
|
try {
|
||||||
@@ -52,11 +51,11 @@ const EditItem: React.FC<EditItemProps> = ({ id, isOpen, onClose }) => {
|
|||||||
<ModalBody pb={6}>
|
<ModalBody pb={6}>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<FormLabel htmlFor='title'>Title</FormLabel>
|
<FormLabel htmlFor='title'>Title</FormLabel>
|
||||||
<Input id='title' {...register('title')} defaultValue={currentItem?.title} type='text' />
|
<Input id='title' {...register('title')} type='text' />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl mt={4}>
|
<FormControl mt={4}>
|
||||||
<FormLabel htmlFor='description'>Description</FormLabel>
|
<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>
|
</FormControl>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
<ModalFooter gap={3}>
|
<ModalFooter gap={3}>
|
||||||
|
@@ -15,10 +15,10 @@ const Appearance: React.FC = () => {
|
|||||||
<Stack>
|
<Stack>
|
||||||
{/* TODO: Add system default option */}
|
{/* TODO: Add system default option */}
|
||||||
<Radio value='light' colorScheme='teal'>
|
<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>
|
||||||
<Radio value='dark' colorScheme='teal'>
|
<Radio value='dark' colorScheme='teal'>
|
||||||
Dark Mode
|
Dark mode
|
||||||
</Radio>
|
</Radio>
|
||||||
</Stack>
|
</Stack>
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState } from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { AlertDialog, AlertDialogBody, AlertDialogContent, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, Button } from '@chakra-ui/react';
|
import { AlertDialog, AlertDialogBody, AlertDialogContent, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, Button } from '@chakra-ui/react';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
@@ -54,7 +54,7 @@ const DeleteConfirmation: React.FC<DeleteProps> = ({ isOpen, onClose }) => {
|
|||||||
<Button bg='ui.danger' color='white' _hover={{ opacity: 0.8 }} type='submit' isLoading={isSubmitting}>
|
<Button bg='ui.danger' color='white' _hover={{ opacity: 0.8 }} type='submit' isLoading={isSubmitting}>
|
||||||
Confirm
|
Confirm
|
||||||
</Button>
|
</Button>
|
||||||
<Button ref={cancelRef} onClick={onClose} isDisabled={isLoading}>
|
<Button ref={cancelRef} onClick={onClose} isDisabled={isSubmitting}>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
</AlertDialogFooter>
|
</AlertDialogFooter>
|
||||||
|
@@ -75,7 +75,7 @@ const UserInformation: React.FC = () => {
|
|||||||
{editMode ? 'Save' : 'Edit'}
|
{editMode ? 'Save' : 'Edit'}
|
||||||
</Button>
|
</Button>
|
||||||
{editMode &&
|
{editMode &&
|
||||||
<Button onClick={onCancel}>
|
<Button onClick={onCancel} isDisabled={isSubmitting}>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>}
|
</Button>}
|
||||||
</Flex>
|
</Flex>
|
||||||
|
@@ -1,23 +1,35 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { ViewIcon, ViewOffIcon } from '@chakra-ui/icons';
|
import { ViewIcon, ViewOffIcon } from '@chakra-ui/icons';
|
||||||
import { Button, Center, Container, FormControl, Icon, Image, Input, InputGroup, InputRightElement, Link, useBoolean } from '@chakra-ui/react';
|
import { Button, Center, Container, FormControl, FormErrorMessage, Icon, Image, Input, InputGroup, InputRightElement, Link, useBoolean } from '@chakra-ui/react';
|
||||||
import { SubmitHandler, useForm } from 'react-hook-form';
|
import { SubmitHandler, useForm } from 'react-hook-form';
|
||||||
import { Link as ReactRouterLink, useNavigate } from 'react-router-dom';
|
import { Link as ReactRouterLink } from 'react-router-dom';
|
||||||
|
|
||||||
import Logo from '../assets/images/fastapi-logo.svg';
|
import Logo from '../assets/images/fastapi-logo.svg';
|
||||||
|
import { ApiError } from '../client';
|
||||||
import { Body_login_login_access_token as AccessToken } from '../client/models/Body_login_login_access_token';
|
import { Body_login_login_access_token as AccessToken } from '../client/models/Body_login_login_access_token';
|
||||||
import useAuth from '../hooks/useAuth';
|
import useAuth from '../hooks/useAuth';
|
||||||
|
|
||||||
const Login: React.FC = () => {
|
const Login: React.FC = () => {
|
||||||
const [show, setShow] = useBoolean();
|
const [show, setShow] = useBoolean();
|
||||||
const navigate = useNavigate();
|
const [error, setError] = React.useState<string | null>(null);
|
||||||
const { register, handleSubmit } = useForm<AccessToken>();
|
const { register, handleSubmit, formState: { errors, isSubmitting } } = useForm<AccessToken>({
|
||||||
|
mode: 'onBlur',
|
||||||
|
criteriaMode: 'all',
|
||||||
|
defaultValues: {
|
||||||
|
username: '',
|
||||||
|
password: ''
|
||||||
|
}
|
||||||
|
});
|
||||||
const { login } = useAuth();
|
const { login } = useAuth();
|
||||||
|
|
||||||
const onSubmit: SubmitHandler<AccessToken> = async (data) => {
|
const onSubmit: SubmitHandler<AccessToken> = async (data) => {
|
||||||
|
try {
|
||||||
await login(data);
|
await login(data);
|
||||||
navigate('/');
|
} catch (err) {
|
||||||
|
const errDetail = (err as ApiError).body.detail;
|
||||||
|
setError(errDetail)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -32,11 +44,12 @@ const Login: React.FC = () => {
|
|||||||
gap={4}
|
gap={4}
|
||||||
centerContent
|
centerContent
|
||||||
>
|
>
|
||||||
<Image src={Logo} alt='FastAPI logo' height='auto' maxW='2xs' alignSelf='center' />
|
<Image src={Logo} alt='FastAPI logo' height='auto' maxW='2xs' alignSelf='center' mb={4} />
|
||||||
<FormControl id='email'>
|
<FormControl id='username' isInvalid={!!errors.username || !!error}>
|
||||||
<Input {...register('username')} placeholder='Email' type='text' />
|
<Input id='username' {...register('username', { pattern: { value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i, message: 'Invalid email address' } })} placeholder='Email' type='text' />
|
||||||
|
{errors.username && <FormErrorMessage>{errors.username.message}</FormErrorMessage>}
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl id='password'>
|
<FormControl id='password' isInvalid={!!error}>
|
||||||
<InputGroup>
|
<InputGroup>
|
||||||
<Input
|
<Input
|
||||||
{...register('password')}
|
{...register('password')}
|
||||||
@@ -55,13 +68,16 @@ const Login: React.FC = () => {
|
|||||||
</Icon>
|
</Icon>
|
||||||
</InputRightElement>
|
</InputRightElement>
|
||||||
</InputGroup>
|
</InputGroup>
|
||||||
|
{error && <FormErrorMessage>
|
||||||
|
{error}
|
||||||
|
</FormErrorMessage>}
|
||||||
|
</FormControl>
|
||||||
<Center>
|
<Center>
|
||||||
<Link as={ReactRouterLink} to='/recover-password' color='blue.500' mt={2}>
|
<Link as={ReactRouterLink} to='/recover-password' color='blue.500'>
|
||||||
Forgot password?
|
Forgot password?
|
||||||
</Link>
|
</Link>
|
||||||
</Center>
|
</Center>
|
||||||
</FormControl>
|
<Button bg='ui.main' color='white' _hover={{ opacity: 0.8 }} type='submit' isLoading={isSubmitting}>
|
||||||
<Button bg='ui.main' color='white' _hover={{ opacity: 0.8 }} type='submit'>
|
|
||||||
Log In
|
Log In
|
||||||
</Button>
|
</Button>
|
||||||
</Container>
|
</Container>
|
||||||
|
Reference in New Issue
Block a user