feat: yup validator & some refactor in layout

This commit is contained in:
Sergey Yarkov 2021-08-30 14:18:11 +03:00
parent 846d96473f
commit cd27d81ccd
10 changed files with 265 additions and 162 deletions

46
package-lock.json generated
View File

@ -2062,6 +2062,11 @@
"@hapi/hoek": "^8.3.0"
}
},
"@hookform/resolvers": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-2.8.0.tgz",
"integrity": "sha512-ALlr0Bg6zDHdRsdqkwfWGPMnIxP4hqMRPvFDHp7lWXQeA7rKykto3cWqQQh1s0PzX043RHwMB6OHVPMwFJqwxg=="
},
"@humanwhocodes/config-array": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz",
@ -9816,6 +9821,11 @@
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
},
"lodash-es": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
},
"lodash._reinterpolate": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz",
@ -10283,6 +10293,11 @@
"integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==",
"optional": true
},
"nanoclone": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/nanoclone/-/nanoclone-0.2.1.tgz",
"integrity": "sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA=="
},
"nanoid": {
"version": "3.1.25",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.25.tgz",
@ -12176,6 +12191,11 @@
"react-is": "^16.8.1"
}
},
"property-expr": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.4.tgz",
"integrity": "sha512-sFPkHQjVKheDNnPvotjQmm3KD3uk1fWKUN7CrpdbwmUx3CrG3QiM8QpTSimvig5vTXmTvjz7+TDvXOI9+4rkcg=="
},
"proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@ -14808,6 +14828,11 @@
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
"integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw=="
},
"toposort": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz",
"integrity": "sha1-riF2gXXRVZ1IvvNUILL0li8JwzA="
},
"tough-cookie": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz",
@ -16695,6 +16720,27 @@
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="
},
"yup": {
"version": "0.32.9",
"resolved": "https://registry.npmjs.org/yup/-/yup-0.32.9.tgz",
"integrity": "sha512-Ci1qN+i2H0XpY7syDQ0k5zKQ/DoxO0LzPg8PAR/X4Mpj6DqaeCoIYEEjDJwhArh3Fa7GWbQQVDZKeXYlSH4JMg==",
"requires": {
"@babel/runtime": "^7.10.5",
"@types/lodash": "^4.14.165",
"lodash": "^4.17.20",
"lodash-es": "^4.17.15",
"nanoclone": "^0.2.1",
"property-expr": "^2.0.4",
"toposort": "^2.0.2"
},
"dependencies": {
"lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
}
}
}
}
}

View File

@ -7,6 +7,7 @@
"@chakra-ui/react": "^1.0.0",
"@emotion/react": "^11.0.0",
"@emotion/styled": "^11.0.0",
"@hookform/resolvers": "^2.8.0",
"@testing-library/jest-dom": "^5.9.0",
"@testing-library/react": "^10.2.1",
"@testing-library/user-event": "^12.0.2",
@ -27,7 +28,8 @@
"react-router-dom": "^5.2.0",
"react-scripts": "4.0.3",
"typescript": "^3.9.5",
"web-vitals": "^0.2.2"
"web-vitals": "^0.2.2",
"yup": "^0.32.9"
},
"scripts": {
"start": "react-scripts start",

View File

@ -1,57 +0,0 @@
import React from 'react';
import { MdAccountCircle, MdVpnKey } from 'react-icons/md';
import { FormControl, FormLabel, InputGroup, Input, InputLeftElement, Button, Icon } from '@chakra-ui/react';
import { SubmitHandler, useForm } from 'react-hook-form';
import useLoginQuery from 'hooks/useLoginQuery';
type AuthInputs = {
login: string;
password: string;
};
const AuthForm: React.FC = () => {
const { register, reset, handleSubmit } = useForm<AuthInputs>();
const { login, loading } = useLoginQuery();
const onSubmit: SubmitHandler<AuthInputs> = async data => {
try {
await login(data.login, data.password);
} catch (error) {
console.error(error);
} finally {
reset();
}
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<FormControl isRequired={true}>
<FormLabel>Login</FormLabel>
<InputGroup>
<InputLeftElement children={<Icon as={MdAccountCircle} />} />
<Input type='text' placeholder='Type your login' {...register('login', { required: true })} />
</InputGroup>
</FormControl>
<FormControl mt={5} isRequired={true}>
<FormLabel>Password</FormLabel>
<InputGroup>
<InputLeftElement children={<Icon as={MdVpnKey} />} />
<Input type='password' placeholder='********' {...register('password', { required: true })} />
</InputGroup>
</FormControl>
<Button
loadingText='Logging in...'
isLoading={loading}
type='submit'
colorScheme='blue'
variant='outline'
width='full'
mt={4}
>
Log in
</Button>
</form>
);
};
export default AuthForm;

View File

@ -0,0 +1,93 @@
import React from 'react';
import { MdAccountCircle, MdSchool, MdVpnKey } from 'react-icons/md';
import {
FormControl,
FormLabel,
InputGroup,
Input,
InputLeftElement,
Button,
Icon,
Text,
Flex,
Box,
} from '@chakra-ui/react';
import { SubmitHandler, useForm } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup';
import useLoginQuery from 'hooks/useLoginQuery';
import LoginValidatorSchema from 'validators/LoginValidatorSchema';
type AuthInputType = {
login: string;
password: string;
};
const AuthForm: React.FC = () => {
const {
register,
reset,
formState: { errors },
handleSubmit,
} = useForm<AuthInputType>({
resolver: yupResolver(LoginValidatorSchema),
});
const { login, loading } = useLoginQuery();
const onSubmit: SubmitHandler<AuthInputType> = data => {
login(data.login, data.password).finally(() => reset());
};
return (
<Box w='full' maxW='450px' borderWidth='1px' borderRadius='lg' p='8'>
<form onSubmit={handleSubmit(onSubmit)}>
<Flex justifyContent='center' alignItems='center'>
<Box w='full'>
<Box textAlign='center'>
<Flex justifyContent='center'>
<Box as={MdSchool} color='blue.500' size='64px' />
</Flex>
</Box>
<FormControl>
<FormLabel>Login</FormLabel>
<InputGroup>
<InputLeftElement children={<Icon as={MdAccountCircle} />} />
<Input type='text' placeholder='Login' {...register('login')} isInvalid={errors.login ? true : false} />
</InputGroup>
<Text as='small' color='red.500'>
{errors.login?.message}
</Text>
</FormControl>
<FormControl mt={5}>
<FormLabel>Password</FormLabel>
<InputGroup>
<InputLeftElement children={<Icon as={MdVpnKey} />} />
<Input
type='password'
placeholder='******'
{...register('password')}
isInvalid={errors.password ? true : false}
/>
</InputGroup>
<Text as='small' color='red.500'>
{errors.password?.message}
</Text>
</FormControl>
<Button
loadingText='Logging in...'
isLoading={loading}
type='submit'
colorScheme='blue'
variant='outline'
width='full'
mt={4}
>
Log in
</Button>
</Box>
</Flex>
</form>
</Box>
);
};
export default AuthForm;

View File

@ -0,0 +1,41 @@
import React from 'react';
import { MdSave } from 'react-icons/md';
import { Box, Heading, Stack } from '@chakra-ui/layout';
import { FormControl, FormHelperText } from '@chakra-ui/form-control';
import { Input } from '@chakra-ui/input';
import { Button } from '@chakra-ui/button';
const UpdateUserContactsForm: React.FC = () => {
return (
<Box as='form'>
<Box borderRadius='md' borderWidth='1px' padding='20px'>
<Heading as='h3' size='lg'>
My Contacts
</Heading>
<Stack spacing='5px' margin='20px 0'>
<FormControl id='tel'>
<FormHelperText color='gray.500'>Telephone</FormHelperText>
<Input type='number' size='md' variant='flushed' placeholder='123-456-7890' />
</FormControl>
<FormControl id='twitter'>
<FormHelperText color='gray.500'>Twitter</FormHelperText>
<Input type='text' size='md' variant='flushed' placeholder='https://twitter.com/id' />
</FormControl>
<FormControl id='telegram'>
<FormHelperText color='gray.500'>Telegram</FormHelperText>
<Input type='text' size='md' variant='flushed' placeholder='@tag' />
</FormControl>
<FormControl id='vk'>
<FormHelperText color='gray.500'>VKontakte</FormHelperText>
<Input type='text' size='md' variant='flushed' placeholder='https://vk.com/id' />
</FormControl>
</Stack>
</Box>
<Button colorScheme='blue' mt='4' type='submit' size='md' variant='outline' rightIcon={<MdSave />}>
Save
</Button>
</Box>
);
};
export default UpdateUserContactsForm;

View File

@ -0,0 +1,66 @@
import React from 'react';
import { Box, Heading, Stack, Flex, StackDivider, Text, Button } from '@chakra-ui/react';
import { IUser } from 'interfaces';
type UserAccountInfoPropsType = {
user: IUser;
};
const UserAccountInfo: React.FC<UserAccountInfoPropsType> = ({ user }) => {
return (
<Box borderRadius='md' borderWidth='1px' padding='20px'>
<Heading as='h3' size='lg'>
Account information
</Heading>
<Stack spacing='10px' mt='20px' divider={<StackDivider />}>
<Flex>
<Box>
<Text as='small' color='gray.500'>
First Name
</Text>
<Text fontWeight='medium' fontSize='lg'>
{user.first_name}
</Text>
</Box>
</Flex>
<Flex>
<Box>
<Text as='small' color='gray.500'>
Last Name
</Text>
<Text fontWeight='medium' fontSize='lg'>
{user.last_name}
</Text>
</Box>
</Flex>
<Flex justifyContent='space-between' alignItems='center'>
<Box>
<Text as='small' color='gray.500'>
Email
</Text>
<Text fontWeight='medium' fontSize='lg'>
{user.email}
</Text>
</Box>
<Button>Edit</Button>
</Flex>
<Flex justifyContent='space-between' alignItems='center'>
<Box>
<Text as='small' color='gray.500'>
Password
</Text>
<Text fontWeight='medium' fontSize='lg'>
**********
</Text>
</Box>
<Button>Change</Button>
</Flex>
</Stack>
</Box>
);
};
export default UserAccountInfo;

View File

@ -53,6 +53,7 @@ const useLoginQuery = () => {
break;
}
} else {
console.error(error);
handleError(error);
}
} finally {

View File

@ -3,11 +3,10 @@ import Helmet from 'react-helmet';
import { Redirect } from 'react-router-dom';
import { observer } from 'mobx-react';
import { IPageProps } from 'interfaces';
import { Flex, Box } from '@chakra-ui/react';
import { Flex } from '@chakra-ui/react';
import { useRootStore } from 'hooks/useRootStore';
import { MdSchool } from 'react-icons/md';
import AuthForm from 'components/Auth/AuthForm';
import AuthForm from 'components/Forms/Auth/AuthForm';
/**
* Auth page
@ -25,16 +24,7 @@ const AuthPage: React.FC<IPageProps> = () => {
<title>Auth</title>
</Helmet>
<Flex minHeight='100vh' align='center' justifyContent='center' padding='5'>
<Box p={8} width='full' maxWidth='450px' borderWidth={1} borderRadius={8}>
<Box textAlign='center'>
<Flex justifyContent='center'>
<Box as={MdSchool} color='blue.500' size='64px' />
</Flex>
</Box>
<Box>
<AuthForm />
</Box>
</Box>
<AuthForm />
</Flex>
</>
);

View File

@ -6,27 +6,21 @@ import {
Avatar,
useColorMode,
Text,
Stack,
Button,
StackDivider,
FormControl,
Input,
Tabs,
TabList,
Tab,
TabPanels,
TabPanel,
FormHelperText,
} from '@chakra-ui/react';
import { MdSave } from 'react-icons/md';
import { IPageProps } from 'interfaces';
import { useRootStore } from 'hooks/useRootStore';
import { useHistory } from 'react-router-dom';
import { observer } from 'mobx-react';
import UserBadge from 'components/User/UserBadge';
import LoadingProfilePage from 'components/Loading/LoadingProfilePage';
import UserAccountInfo from 'components/User/UserAccountInfo';
import UpdateUserContactsForm from 'components/Forms/User/UpdateUserContactsForm';
/**
* Profile page
@ -110,92 +104,11 @@ const ProfilePage: React.FC<IPageProps> = ({ title }) => {
<Tab>My contacts</Tab>
</TabList>
<TabPanels>
{/* Account info */}
<TabPanel padding='10px 0' mt='20px'>
<Box borderRadius='md' borderWidth='1px' padding='20px'>
<Heading as='h3' size='lg'>
Account information
</Heading>
<Stack spacing='10px' mt='20px' divider={<StackDivider />}>
<Flex>
<Box>
<Text as='small' color='gray.500'>
First Name
</Text>
<Text fontWeight='medium' fontSize='lg'>
{userStore.me.first_name}
</Text>
</Box>
</Flex>
<Flex>
<Box>
<Text as='small' color='gray.500'>
Last Name
</Text>
<Text fontWeight='medium' fontSize='lg'>
{userStore.me.last_name}
</Text>
</Box>
</Flex>
<Flex justifyContent='space-between' alignItems='center'>
<Box>
<Text as='small' color='gray.500'>
Email
</Text>
<Text fontWeight='medium' fontSize='lg'>
{userStore.me.email}
</Text>
</Box>
<Button>Edit</Button>
</Flex>
<Flex justifyContent='space-between' alignItems='center'>
<Box>
<Text as='small' color='gray.500'>
Password
</Text>
<Text fontWeight='medium' fontSize='lg'>
**********
</Text>
</Box>
<Button>Change</Button>
</Flex>
</Stack>
</Box>
<UserAccountInfo user={userStore.me} />
</TabPanel>
{/* Contacts */}
<TabPanel padding='10px 0' mt='20px'>
<Box as='form'>
<Box borderRadius='md' borderWidth='1px' padding='20px'>
<Heading as='h3' size='lg'>
My Contacts
</Heading>
<Stack spacing='5px' margin='20px 0'>
<FormControl id='tel'>
<FormHelperText color='gray.500'>Telephone</FormHelperText>
<Input type='number' size='md' variant='flushed' placeholder='123-456-7890' />
</FormControl>
<FormControl id='twitter'>
<FormHelperText color='gray.500'>Twitter</FormHelperText>
<Input type='text' size='md' variant='flushed' placeholder='https://twitter.com/id' />
</FormControl>
<FormControl id='telegram'>
<FormHelperText color='gray.500'>Telegram</FormHelperText>
<Input type='text' size='md' variant='flushed' placeholder='@tag' />
</FormControl>
<FormControl id='vk'>
<FormHelperText color='gray.500'>VKontakte</FormHelperText>
<Input type='text' size='md' variant='flushed' placeholder='https://vk.com/id' />
</FormControl>
</Stack>
</Box>
<Button colorScheme='blue' mt='4' type='submit' size='md' variant='outline' rightIcon={<MdSave />}>
Save
</Button>
</Box>
<UpdateUserContactsForm />
</TabPanel>
</TabPanels>
</Tabs>

View File

@ -0,0 +1,8 @@
import * as yup from 'yup';
const LoginValidatorSchema = yup.object().shape({
login: yup.string().required('Login is required'),
password: yup.string().min(6, 'Password must be at least 6 characters').required('Password is required'),
});
export default LoginValidatorSchema;