refactor: auth page layout

This commit is contained in:
Sergey Yarkov 2021-12-06 21:07:26 +03:00
parent 4368498dcd
commit 2c0bf3a8c5
7 changed files with 175 additions and 52 deletions

View File

@ -0,0 +1,15 @@
import React from 'react';
import { Box, BoxProps, useColorModeValue } from '@chakra-ui/react';
const Card: React.FC<BoxProps> = (props: BoxProps) => (
<Box
bg={useColorModeValue('white', 'gray.700')}
py='8'
px={{ base: '4', md: '10' }}
shadow='base'
rounded={{ sm: 'lg' }}
{...props}
/>
);
export default Card;

View File

@ -0,0 +1,3 @@
import Card from './Card';
export { Card };

View File

@ -0,0 +1,76 @@
import React, { useRef } from 'react';
import {
Box,
Flex,
FormControl,
FormLabel,
Text,
IconButton,
Input,
InputGroup,
InputProps,
InputRightElement,
InputLeftElement,
Icon,
useDisclosure,
useMergeRefs,
useColorModeValue as mode,
} from '@chakra-ui/react';
import { HiEye, HiEyeOff } from 'react-icons/hi';
import { MdVpnKey } from 'react-icons/md';
interface PasswordFieldPropsType extends InputProps {
errorMessage?: string | undefined;
}
export const PasswordField = React.forwardRef<HTMLInputElement, PasswordFieldPropsType>((props, ref) => {
const { errorMessage, ...other } = props;
const { isOpen, onToggle } = useDisclosure();
const inputRef = useRef<HTMLInputElement>(null);
const mergeRef = useMergeRefs(inputRef, ref);
const onClickReveal = () => {
onToggle();
const input = inputRef.current;
if (input) {
input.focus({ preventScroll: true });
const length = input.value.length * 2;
requestAnimationFrame(() => {
input.setSelectionRange(length, length);
});
}
};
return (
<FormControl id='password'>
<Flex justify='space-between'>
<FormLabel>Password</FormLabel>
</Flex>
<InputGroup>
<InputLeftElement children={<Icon as={MdVpnKey} />} />
<InputRightElement>
<IconButton
bg='transparent !important'
variant='ghost'
aria-label={isOpen ? 'Mask password' : 'Reveal password'}
icon={isOpen ? <HiEyeOff /> : <HiEye />}
onClick={onClickReveal}
/>
</InputRightElement>
<Input
ref={mergeRef}
name='password'
type={isOpen ? 'text' : 'password'}
autoComplete='current-password'
required
{...other}
/>
</InputGroup>
{errorMessage && (
<Text as='small' color='red.500'>
{errorMessage}
</Text>
)}
</FormControl>
);
});

View File

@ -0,0 +1,28 @@
import React from 'react';
import { chakra, HTMLChakraProps, useColorModeValue, useToken } from '@chakra-ui/react';
const Logo: React.FC<HTMLChakraProps<'svg'>> = props => {
const [white, black] = useToken('colors', ['white', 'gray.800']);
return (
<chakra.svg
color={useColorModeValue('blue.500', 'blue.300')}
aria-hidden
viewBox='0 0 123 32'
fill='none'
h='6'
flexShrink={0}
{...props}
>
<path
d='M6.66665 17.5733V22.9067L16 28L25.3333 22.9067V17.5733L16 22.6667L6.66665 17.5733ZM16 4L1.33331 12L16 20L28 13.4533V22.6667H30.6666V12L16 4Z'
fill='#3182CE'
/>
<path
d='M40.8807 22H48.9943V19.7159H43.6932V17.3239H48.5795V15.0341H43.6932V12.6477H48.9943V10.3636H40.8807V22ZM53.9432 22.125C55.2614 22.125 56.0455 21.392 56.3977 20.5852H56.483V22H59.233V10.3636H56.4545V14.767H56.3977C56.0682 13.9659 55.3068 13.1591 53.9318 13.1591C52.108 13.1591 50.4545 14.5625 50.4545 17.642C50.4545 20.6136 52.017 22.125 53.9432 22.125ZM54.9091 19.9602C53.8807 19.9602 53.3068 19.0455 53.3068 17.6364C53.3068 16.233 53.875 15.3295 54.9091 15.3295C55.9261 15.3295 56.517 16.2102 56.517 17.6364C56.517 19.0511 55.9205 19.9602 54.9091 19.9602ZM66.6207 18.233C66.6207 19.25 65.973 19.8182 65.1491 19.8182C64.2969 19.8182 63.7741 19.2386 63.7685 18.2898V13.2727H60.9901V18.8352C60.9957 20.8068 62.1946 22.1136 63.9901 22.1136C65.2969 22.1136 66.2685 21.4432 66.6548 20.375H66.7457V22H69.3935V13.2727H66.6207V18.233ZM75.2358 22.1648C77.6903 22.1648 79.2131 20.7386 79.2869 18.5795H76.6903C76.5881 19.5057 76.0312 20.0114 75.2699 20.0114C74.2926 20.0114 73.6562 19.1875 73.6562 17.6364C73.6562 16.0966 74.2983 15.2727 75.2699 15.2727C76.0653 15.2727 76.5824 15.8125 76.6903 16.7045H79.2869C79.2244 14.5568 77.6562 13.1591 75.2301 13.1591C72.4858 13.1591 70.8267 14.9886 70.8267 17.6648C70.8267 20.3295 72.4744 22.1648 75.2358 22.1648ZM85.6349 13.2727H84.0611V11.1818H81.2827V13.2727H80.1293V15.3182H81.2827V19.5625C81.2656 21.3295 82.4134 22.2159 84.3736 22.125C85.044 22.0909 85.527 21.9545 85.794 21.875L85.3736 19.8693C85.2543 19.8977 84.9759 19.9545 84.7656 19.9545C84.3168 19.9545 84.0611 19.7727 84.0611 19.2557V15.3182H85.6349V13.2727Z'
fill={useColorModeValue(black, white)}
/>
</chakra.svg>
);
};
export default Logo;

View File

@ -0,0 +1,3 @@
import Logo from './Logo';
export default Logo;

View File

@ -2,7 +2,7 @@ import React from 'react';
import Helmet from 'react-helmet'; import Helmet from 'react-helmet';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import { Redirect } from 'react-router-dom'; import { Redirect } from 'react-router-dom';
import { Flex } from '@chakra-ui/react'; import { Box, Heading, Text, Link } from '@chakra-ui/react';
/** /**
* Types * Types
@ -12,12 +12,15 @@ import { IPageProps } from '@educt/interfaces';
/** /**
* Components * Components
*/ */
import Logo from '@educt/components/Logo';
import AuthForm from './components/AuthForm'; import AuthForm from './components/AuthForm';
import { Card } from '@educt/components/Card';
/** /**
* Hooks * Hooks
*/ */
import { useRootStore } from '@educt/hooks/useRootStore'; import { useRootStore } from '@educt/hooks/useRootStore';
import { useColorModeValue } from '@chakra-ui/react';
/** /**
* Auth page * Auth page
@ -34,14 +37,20 @@ const AuthPage: React.FC<IPageProps> = ({ title }) => {
} }
return ( return (
<> <Box bg={useColorModeValue('gray.50', 'inherit')} minH='100vh' py='12' px={{ base: '4', lg: '8' }}>
<Helmet> <Helmet>
<title>{title}</title> <title>{title}</title>
</Helmet> </Helmet>
<Flex minHeight='100vh' align='center' justifyContent='center' padding='5'> <Box maxW='md' mx='auto'>
<AuthForm /> <Logo mx='auto' h='14' mb={{ base: '10', md: '20' }} />
</Flex> <Heading mb='8' textAlign='center' size='xl' fontWeight='extrabold'>
</> Login to your account
</Heading>
<Card>
<AuthForm />
</Card>
</Box>
</Box>
); );
}; };

View File

@ -8,10 +8,10 @@ import {
Button, Button,
Icon, Icon,
Text, Text,
Flex, Stack,
Box, Box,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { MdAccountCircle, MdSchool, MdVpnKey } from 'react-icons/md'; import { MdAccountCircle, MdVpnKey } from 'react-icons/md';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
/** /**
@ -29,6 +29,7 @@ import { useLogin } from '@educt/hooks/queries';
* Schema * Schema
*/ */
import LoginSchema from './AuthForm.validator'; import LoginSchema from './AuthForm.validator';
import { PasswordField } from '@educt/components/Fields/PasswordField';
type AuthInputType = { type AuthInputType = {
login: string; login: string;
@ -52,52 +53,40 @@ const AuthForm: React.FC = () => {
const onSubmit: SubmitHandler<AuthInputType> = data => login(data.login, data.password).finally(() => reset()); const onSubmit: SubmitHandler<AuthInputType> = data => login(data.login, data.password).finally(() => reset());
return ( return (
<Box flexBasis='500px' borderWidth='1px' borderRadius='lg' p='8'> <form onSubmit={handleSubmit(onSubmit)}>
<form onSubmit={handleSubmit(onSubmit)}> <Stack spacing='6'>
<Flex justifyContent='center' alignItems='center'> <FormControl id='login'>
<Box w='full'> <FormLabel>Login</FormLabel>
<Box textAlign='center'> <InputGroup>
<Flex justifyContent='center'> <InputLeftElement children={<Icon as={MdAccountCircle} />} />
<Box as={MdSchool} color='blue.500' size='64px' /> <Input type='text' placeholder='Login' {...register('login')} isInvalid={!!errors.login} />
</Flex> </InputGroup>
</Box> <Text as='small' color='red.500'>
<FormControl> {errors.login?.message}
<FormLabel>Login</FormLabel> </Text>
<InputGroup> </FormControl>
<InputLeftElement children={<Icon as={MdAccountCircle} />} />
<Input type='text' placeholder='Login' {...register('login')} isInvalid={!!errors.login} />
</InputGroup>
<Text as='small' color='red.500'>
{errors.login?.message}
</Text>
</FormControl>
<FormControl mt={5}> <PasswordField
<FormLabel>Password</FormLabel> {...register('password')}
<InputGroup> placeholder='******'
<InputLeftElement children={<Icon as={MdVpnKey} />} /> isInvalid={!!errors.password}
<Input type='password' placeholder='******' {...register('password')} isInvalid={!!errors.password} /> errorMessage={errors.password?.message}
</InputGroup> />
<Text as='small' color='red.500'>
{errors.password?.message}
</Text>
</FormControl>
<Button <Button
loadingText='Logging in...' type='submit'
isLoading={isLoading} colorScheme='blue'
type='submit' size='lg'
colorScheme='blue' fontSize='md'
variant='outline' loadingText='Logging in...'
width='full' isLoading={isLoading}
mt={4} width='full'
> mt={4}
Log in >
</Button> Log in
</Box> </Button>
</Flex> </Stack>
</form> </form>
</Box>
); );
}; };