Ott 1500 remove authentication pages (#449)
* fix(1500): DateFilter key fix * refactor(1500): removed Login page * refactor(1500): removed Registration page * fix(1500): redirect to auth provider if no tokenkeep-around/af30b88d367751c9e05a735e4a0467a96238ef47
parent
6945f318a4
commit
92d4d6be4c
@ -1,16 +0,0 @@ |
|||||||
import { useEffect } from 'react' |
|
||||||
|
|
||||||
import { useAuthStore } from 'features/AuthStore' |
|
||||||
import { queryParamStorage } from 'features/QueryParamsStorage' |
|
||||||
|
|
||||||
export const RedirectCallback = () => { |
|
||||||
const { signinRedirectCallback } = useAuthStore() |
|
||||||
|
|
||||||
useEffect(() => { |
|
||||||
signinRedirectCallback().then(() => { |
|
||||||
queryParamStorage.clear() |
|
||||||
}) |
|
||||||
}, [signinRedirectCallback]) |
|
||||||
|
|
||||||
return null |
|
||||||
} |
|
||||||
@ -1,76 +0,0 @@ |
|||||||
import { lazy } from 'react' |
|
||||||
|
|
||||||
import { |
|
||||||
Redirect, |
|
||||||
Route, |
|
||||||
Switch, |
|
||||||
} from 'react-router-dom' |
|
||||||
|
|
||||||
import styled, { css } from 'styled-components/macro' |
|
||||||
|
|
||||||
import { PAGES } from 'config' |
|
||||||
import { publicLexics } from 'config/lexics/public' |
|
||||||
|
|
||||||
import { useLexicsConfig } from 'features/LexicsStore' |
|
||||||
import { LanguageSelect } from 'features/LanguageSelect' |
|
||||||
import { HeaderGroup } from 'features/ProfileHeader/styled' |
|
||||||
import { isMobileDevice } from 'config/userAgent' |
|
||||||
|
|
||||||
import { RedirectCallback } from './RedirectCallback' |
|
||||||
|
|
||||||
const Login = lazy(() => import('features/Login')) |
|
||||||
const Register = lazy(() => import('features/Register')) |
|
||||||
|
|
||||||
const Main = styled.main` |
|
||||||
width: 100%; |
|
||||||
${isMobileDevice |
|
||||||
? css` |
|
||||||
@media screen and (orientation: landscape){ |
|
||||||
min-height: 100vh; |
|
||||||
} |
|
||||||
` |
|
||||||
: ''}; |
|
||||||
` |
|
||||||
|
|
||||||
const HeaderStyled = styled.header` |
|
||||||
display: flex; |
|
||||||
justify-content: space-between; |
|
||||||
height: 3.02rem; |
|
||||||
padding: 0.755rem 1.369rem 0 1.04rem; |
|
||||||
margin-bottom: 1.416rem; |
|
||||||
${isMobileDevice |
|
||||||
? css` |
|
||||||
padding-top: 15px; |
|
||||||
` |
|
||||||
: ''}; |
|
||||||
` |
|
||||||
|
|
||||||
export const UnauthenticatedApp = () => { |
|
||||||
useLexicsConfig(publicLexics) |
|
||||||
|
|
||||||
return ( |
|
||||||
<Main> |
|
||||||
<HeaderStyled> |
|
||||||
<HeaderGroup /> |
|
||||||
<HeaderGroup> |
|
||||||
<LanguageSelect /> |
|
||||||
</HeaderGroup> |
|
||||||
</HeaderStyled> |
|
||||||
<Switch> |
|
||||||
<Route path={PAGES.login}> |
|
||||||
<Login /> |
|
||||||
</Route> |
|
||||||
|
|
||||||
<Route path={PAGES.register}> |
|
||||||
<Register /> |
|
||||||
</Route> |
|
||||||
|
|
||||||
<Route path='/redirect'> |
|
||||||
<RedirectCallback /> |
|
||||||
</Route> |
|
||||||
|
|
||||||
<Redirect to={PAGES.login} /> |
|
||||||
</Switch> |
|
||||||
</Main> |
|
||||||
) |
|
||||||
} |
|
||||||
@ -1,18 +0,0 @@ |
|||||||
import { useHistory } from 'react-router-dom' |
|
||||||
|
|
||||||
import { PAGES } from 'config' |
|
||||||
import { register } from 'requests' |
|
||||||
|
|
||||||
type Args = Parameters<typeof register>[0] |
|
||||||
|
|
||||||
export const useRegister = () => { |
|
||||||
const history = useHistory() |
|
||||||
|
|
||||||
const goToRegistrationSuccess = () => { |
|
||||||
history.replace(`${PAGES.register}/successful`) |
|
||||||
} |
|
||||||
|
|
||||||
return async (args: Args) => ( |
|
||||||
register(args).then(goToRegistrationSuccess) |
|
||||||
) |
|
||||||
} |
|
||||||
@ -1,66 +0,0 @@ |
|||||||
import map from 'lodash/map' |
|
||||||
|
|
||||||
import { useLexicsStore } from 'features/LexicsStore' |
|
||||||
import { OutsideClick } from 'features/OutsideClick' |
|
||||||
|
|
||||||
import { useToggle } from 'hooks' |
|
||||||
|
|
||||||
import type { Languages } from './config' |
|
||||||
import { langsList } from './config' |
|
||||||
import { |
|
||||||
Wrapper, |
|
||||||
WorldIcon, |
|
||||||
LangsList, |
|
||||||
LangsItem, |
|
||||||
FlagIcon, |
|
||||||
} from './styled' |
|
||||||
|
|
||||||
export const LanguageSelect = () => { |
|
||||||
const { changeLang, translate } = useLexicsStore() |
|
||||||
const { |
|
||||||
close, |
|
||||||
isOpen, |
|
||||||
open, |
|
||||||
} = useToggle() |
|
||||||
|
|
||||||
const handleLangChange = (locale: Languages) => () => { |
|
||||||
changeLang(locale) |
|
||||||
close() |
|
||||||
} |
|
||||||
|
|
||||||
return ( |
|
||||||
<Wrapper> |
|
||||||
<WorldIcon |
|
||||||
onClick={open} |
|
||||||
active={isOpen} |
|
||||||
title={translate('select_language')} |
|
||||||
aria-expanded={isOpen} |
|
||||||
aria-controls='langsList' |
|
||||||
/> |
|
||||||
{isOpen && ( |
|
||||||
<OutsideClick onClick={close}> |
|
||||||
<LangsList id='langsList'> |
|
||||||
{ |
|
||||||
map( |
|
||||||
langsList, |
|
||||||
({ |
|
||||||
className, |
|
||||||
locale, |
|
||||||
title, |
|
||||||
}) => ( |
|
||||||
<LangsItem |
|
||||||
key={locale} |
|
||||||
title={title} |
|
||||||
onClick={handleLangChange(locale)} |
|
||||||
> |
|
||||||
<FlagIcon flag={className} /> |
|
||||||
</LangsItem> |
|
||||||
), |
|
||||||
) |
|
||||||
} |
|
||||||
</LangsList> |
|
||||||
</OutsideClick> |
|
||||||
)} |
|
||||||
</Wrapper> |
|
||||||
) |
|
||||||
} |
|
||||||
@ -1,145 +0,0 @@ |
|||||||
import styled, { css } from 'styled-components/macro' |
|
||||||
import { isMobileDevice } from 'config/userAgent' |
|
||||||
|
|
||||||
export const Wrapper = styled.div` |
|
||||||
position: relative; |
|
||||||
display: flex; |
|
||||||
align-items: center; |
|
||||||
` |
|
||||||
|
|
||||||
type WorldIconProps = { |
|
||||||
active?: boolean, |
|
||||||
} |
|
||||||
|
|
||||||
export const WorldIcon = styled.button<WorldIconProps>` |
|
||||||
display: block; |
|
||||||
width: 0.85rem; |
|
||||||
height: 0.85rem; |
|
||||||
border: none; |
|
||||||
background: none; |
|
||||||
outline: none; |
|
||||||
background-image: url(/images/worldIcon.svg); |
|
||||||
background-repeat: no-repeat; |
|
||||||
cursor: pointer; |
|
||||||
${isMobileDevice |
|
||||||
? css` |
|
||||||
background-size: cover; |
|
||||||
width: 19px; |
|
||||||
min-width: 19px; |
|
||||||
height: 19px; |
|
||||||
position: absolute; |
|
||||||
top: 5px; |
|
||||||
right: 10px; |
|
||||||
` |
|
||||||
: ''}; |
|
||||||
|
|
||||||
:hover { |
|
||||||
opacity: 1; |
|
||||||
} |
|
||||||
|
|
||||||
${({ active }) => (active ? 'opacity: 1;' : 'opacity: 0.7;')} |
|
||||||
` |
|
||||||
|
|
||||||
export const LangsList = styled.ul` |
|
||||||
display: flex; |
|
||||||
flex-wrap: wrap; |
|
||||||
width: 4.6rem; |
|
||||||
position: absolute; |
|
||||||
background-color: #666666; |
|
||||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.5); |
|
||||||
border-radius: 2px; |
|
||||||
right: -92%; |
|
||||||
top: calc(100% + 0.19rem); |
|
||||||
${isMobileDevice |
|
||||||
? css` |
|
||||||
top: 35px; |
|
||||||
right: 10px; |
|
||||||
width: 55px; |
|
||||||
height: 25px; |
|
||||||
justify-content: center; |
|
||||||
align-items: center; |
|
||||||
gap: 5px; |
|
||||||
` |
|
||||||
: ''} |
|
||||||
:before { |
|
||||||
content: ''; |
|
||||||
width: 0.567rem; |
|
||||||
height: 0.567rem; |
|
||||||
transform: translateY(-50%) rotate(45deg); |
|
||||||
position: absolute; |
|
||||||
right: 0.9rem; |
|
||||||
background-color: #666666; |
|
||||||
z-index: 0; |
|
||||||
${isMobileDevice |
|
||||||
? css` |
|
||||||
width: 10px; |
|
||||||
height: 20px; |
|
||||||
right: 10px; |
|
||||||
` |
|
||||||
: ''} |
|
||||||
} |
|
||||||
` |
|
||||||
|
|
||||||
export const LangsItem = styled.li` |
|
||||||
text-align: center; |
|
||||||
display: flex; |
|
||||||
align-items: center; |
|
||||||
justify-content: center; |
|
||||||
width: 2.28rem; |
|
||||||
height: 2.28rem; |
|
||||||
z-index: 1; |
|
||||||
border-radius: 2px; |
|
||||||
${isMobileDevice |
|
||||||
? css` |
|
||||||
width: 22px; |
|
||||||
` |
|
||||||
: ''} |
|
||||||
:hover { |
|
||||||
background-color: #999999; |
|
||||||
cursor: pointer; |
|
||||||
} |
|
||||||
` |
|
||||||
|
|
||||||
type Position = { |
|
||||||
col: number, |
|
||||||
row: number, |
|
||||||
} |
|
||||||
|
|
||||||
const flagPositions: Record<string, Position> = { |
|
||||||
gb: { |
|
||||||
col: 4, |
|
||||||
row: 12, |
|
||||||
}, |
|
||||||
ru: { |
|
||||||
col: 8, |
|
||||||
row: 9, |
|
||||||
}, |
|
||||||
} |
|
||||||
|
|
||||||
const getFlagPosition = (flag: string) => { |
|
||||||
const { col, row } = flagPositions[flag] |
|
||||||
return `${col * -24}px ${row * -16}px` |
|
||||||
} |
|
||||||
|
|
||||||
type FlagIconProps = { |
|
||||||
flag: string, |
|
||||||
} |
|
||||||
|
|
||||||
export const FlagIcon = styled.span<FlagIconProps>` |
|
||||||
display: inline-block; |
|
||||||
width: 28px; |
|
||||||
height: 16px; |
|
||||||
transition: 0.3s; |
|
||||||
|
|
||||||
background-image: url('/images/flags-sprite.png'); |
|
||||||
background-position: 1000px 1000px; |
|
||||||
background-repeat: no-repeat; |
|
||||||
display: inline-block; |
|
||||||
position: relative; |
|
||||||
|
|
||||||
background-size: 360px; |
|
||||||
height: 16px; |
|
||||||
width: 24px; |
|
||||||
|
|
||||||
background-position: ${({ flag }) => getFlagPosition(flag)}; |
|
||||||
` |
|
||||||
@ -1,74 +0,0 @@ |
|||||||
import styled, { css } from 'styled-components/macro' |
|
||||||
|
|
||||||
import { isMobileDevice } from 'config/userAgent' |
|
||||||
|
|
||||||
import { ButtonSolid as BaseButtonSolid } from 'features/Common' |
|
||||||
|
|
||||||
const backgroundColors = { |
|
||||||
facebook: '#4468B2', |
|
||||||
google: '#FFFFFF', |
|
||||||
} |
|
||||||
|
|
||||||
const textColors = { |
|
||||||
facebook: '#FFFFFF', |
|
||||||
google: '#4F4F4F', |
|
||||||
} |
|
||||||
|
|
||||||
const Button = styled(BaseButtonSolid)<Props>` |
|
||||||
display: flex; |
|
||||||
justify-content: center; |
|
||||||
align-items: center; |
|
||||||
width: 100%; |
|
||||||
border-radius: 10px; |
|
||||||
box-shadow: 0px 1px 0.283rem rgba(0, 0, 0, 0.2); |
|
||||||
font-weight: 500; |
|
||||||
font-size: 0.95rem; |
|
||||||
letter-spacing: -0.408px; |
|
||||||
|
|
||||||
color: ${({ provider }) => textColors[provider]}; |
|
||||||
background-color: ${({ provider }) => backgroundColors[provider]}; |
|
||||||
margin-bottom: 0.71rem; |
|
||||||
${isMobileDevice |
|
||||||
? css` |
|
||||||
border-radius: 6px; |
|
||||||
font-size: 12.31px; |
|
||||||
margin-bottom: 9.23px; |
|
||||||
@media screen and (orientation: landscape) { |
|
||||||
max-width: 290px; |
|
||||||
} |
|
||||||
` |
|
||||||
: ''} |
|
||||||
|
|
||||||
` |
|
||||||
|
|
||||||
const Icon = styled.span<Props>` |
|
||||||
display: inline-block; |
|
||||||
width: 1rem; |
|
||||||
height: 1rem; |
|
||||||
margin-right: 0.38rem; |
|
||||||
background-image: url(/images/${({ provider }) => provider}.png); |
|
||||||
background-size: contain; |
|
||||||
background-repeat: no-repeat; |
|
||||||
${isMobileDevice |
|
||||||
? css` |
|
||||||
width: 12.92px; |
|
||||||
height: 12.92px; |
|
||||||
` |
|
||||||
: ''} |
|
||||||
` |
|
||||||
|
|
||||||
const lexics = { |
|
||||||
facebook: 'Войти через Facebook', |
|
||||||
google: 'Войти через Google', |
|
||||||
} |
|
||||||
|
|
||||||
type Props = { |
|
||||||
provider: keyof typeof backgroundColors, |
|
||||||
} |
|
||||||
|
|
||||||
export const AuthProviderButton = ({ provider }: Props) => ( |
|
||||||
<Button type='button' provider={provider}> |
|
||||||
<Icon provider={provider} /> |
|
||||||
{lexics[provider]} |
|
||||||
</Button> |
|
||||||
) |
|
||||||
@ -1,53 +0,0 @@ |
|||||||
import type { MouseEvent } from 'react' |
|
||||||
|
|
||||||
import { |
|
||||||
CloseButton, |
|
||||||
Header, |
|
||||||
HeaderActions, |
|
||||||
} from 'features/PopupComponents' |
|
||||||
import { InputGroup, NewInput } from 'features/Common' |
|
||||||
|
|
||||||
import { ButtonSolid } from '../../styled' |
|
||||||
import { |
|
||||||
Modal, |
|
||||||
Body, |
|
||||||
Title, |
|
||||||
ButtonWrapper, |
|
||||||
} from './styled' |
|
||||||
|
|
||||||
type Props = { |
|
||||||
onHide: () => void, |
|
||||||
} |
|
||||||
|
|
||||||
export const PasswordResetPopup = ({ onHide }: Props) => { |
|
||||||
const close = (e: MouseEvent<HTMLButtonElement>) => { |
|
||||||
e.stopPropagation() |
|
||||||
onHide() |
|
||||||
} |
|
||||||
return ( |
|
||||||
<Modal |
|
||||||
isOpen |
|
||||||
close={onHide} |
|
||||||
withCloseButton={false} |
|
||||||
> |
|
||||||
<Header height={50}> |
|
||||||
<Title> |
|
||||||
Восстановление пароля |
|
||||||
</Title> |
|
||||||
<HeaderActions position='right'> |
|
||||||
<CloseButton |
|
||||||
onClick={close} |
|
||||||
/> |
|
||||||
</HeaderActions> |
|
||||||
</Header> |
|
||||||
<Body> |
|
||||||
<InputGroup> |
|
||||||
<NewInput type='email' placeholder='Введите email, указанный при регистрации' /> |
|
||||||
</InputGroup> |
|
||||||
<ButtonWrapper> |
|
||||||
<ButtonSolid width='270px' onClick={close}>Отправить</ButtonSolid> |
|
||||||
</ButtonWrapper> |
|
||||||
</Body> |
|
||||||
</Modal> |
|
||||||
) |
|
||||||
} |
|
||||||
@ -1,57 +0,0 @@ |
|||||||
import styled, { css } from 'styled-components/macro' |
|
||||||
|
|
||||||
import { isMobileDevice } from 'config/userAgent' |
|
||||||
|
|
||||||
import { Modal as BaseModal } from 'features/Modal' |
|
||||||
import { ModalWindow } from 'features/Modal/styled' |
|
||||||
import { HeaderTitle } from 'features/PopupComponents' |
|
||||||
|
|
||||||
export const Modal = styled(BaseModal)` |
|
||||||
background-color: rgba(0, 0, 0, 0.7); |
|
||||||
|
|
||||||
${ModalWindow} { |
|
||||||
width: 577px; |
|
||||||
padding: 15px 0; |
|
||||||
background-color: #333333; |
|
||||||
border-radius: 5px; |
|
||||||
${isMobileDevice |
|
||||||
? css` |
|
||||||
width: 100%; |
|
||||||
padding: 15px 30px 0; |
|
||||||
` |
|
||||||
: ''} |
|
||||||
} |
|
||||||
` |
|
||||||
|
|
||||||
export const Body = styled.div` |
|
||||||
display: flex; |
|
||||||
flex-direction: column; |
|
||||||
align-items: center; |
|
||||||
|
|
||||||
margin-top: 24px; |
|
||||||
padding: 0 40px; |
|
||||||
${isMobileDevice |
|
||||||
? css` |
|
||||||
width: 100%; |
|
||||||
padding: 0; |
|
||||||
@media screen and (orientation: landscape){ |
|
||||||
margin-top: 10px; |
|
||||||
} |
|
||||||
` |
|
||||||
: ''} |
|
||||||
` |
|
||||||
|
|
||||||
export const Title = styled(HeaderTitle)`` |
|
||||||
|
|
||||||
export const ButtonWrapper = styled.div` |
|
||||||
padding: 25px 0; |
|
||||||
${isMobileDevice |
|
||||||
? css` |
|
||||||
width: 100%; |
|
||||||
padding: 12px 0 35px; |
|
||||||
@media screen and (orientation: landscape){ |
|
||||||
padding-top: 20px; |
|
||||||
} |
|
||||||
` |
|
||||||
: ''} |
|
||||||
` |
|
||||||
@ -1,16 +0,0 @@ |
|||||||
import type { FormEvent } from 'react' |
|
||||||
|
|
||||||
import { useAuthStore } from 'features/AuthStore' |
|
||||||
|
|
||||||
export const useLoginForm = () => { |
|
||||||
const { login } = useAuthStore() |
|
||||||
|
|
||||||
const handleSubmit = async (event: FormEvent<HTMLFormElement>) => { |
|
||||||
event.preventDefault() |
|
||||||
login() |
|
||||||
} |
|
||||||
|
|
||||||
return { |
|
||||||
handleSubmit, |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,59 +0,0 @@ |
|||||||
import { PAGES } from 'config' |
|
||||||
|
|
||||||
import { T9n } from 'features/T9n' |
|
||||||
import { Logo } from 'features/Logo' |
|
||||||
|
|
||||||
import { useLoginForm } from './hooks' |
|
||||||
import { |
|
||||||
BlockTitle, |
|
||||||
CenterBlock, |
|
||||||
ButtonsBlock, |
|
||||||
Form, |
|
||||||
RegisterButton, |
|
||||||
ButtonSolid, |
|
||||||
// Or,
|
|
||||||
// BorderlessButton,
|
|
||||||
} from './styled' |
|
||||||
// import { AuthProviderButton } from './components/AuthProviderButton'
|
|
||||||
// import { PasswordResetPopup } from './components/PasswordResetPopup'
|
|
||||||
|
|
||||||
const Login = () => { |
|
||||||
const { |
|
||||||
handleSubmit, |
|
||||||
} = useLoginForm() |
|
||||||
|
|
||||||
return ( |
|
||||||
<CenterBlock> |
|
||||||
<Logo /> |
|
||||||
<Form onSubmit={handleSubmit}> |
|
||||||
<BlockTitle t='step_title_login' /> |
|
||||||
|
|
||||||
<ButtonsBlock> |
|
||||||
<ButtonSolid type='submit'> |
|
||||||
<T9n t='login' /> |
|
||||||
</ButtonSolid> |
|
||||||
<RegisterButton to={PAGES.register}> |
|
||||||
<T9n t='register' /> |
|
||||||
</RegisterButton> |
|
||||||
</ButtonsBlock> |
|
||||||
{/* TODO: раскомментить когда будет готово */} |
|
||||||
{/* <Or>или</Or> |
|
||||||
<AuthProviderButton provider='facebook' /> |
|
||||||
<AuthProviderButton provider='google' /> |
|
||||||
<BorderlessButton |
|
||||||
type='button' |
|
||||||
onClick={toggleResetPopup} |
|
||||||
> |
|
||||||
Забыли пароль? |
|
||||||
</BorderlessButton> |
|
||||||
{ |
|
||||||
isResetPopupOpen && ( |
|
||||||
<PasswordResetPopup onHide={toggleResetPopup} /> |
|
||||||
) |
|
||||||
} */} |
|
||||||
</Form> |
|
||||||
</CenterBlock> |
|
||||||
) |
|
||||||
} |
|
||||||
|
|
||||||
export default Login |
|
||||||
@ -1,157 +0,0 @@ |
|||||||
import { Link } from 'react-router-dom' |
|
||||||
|
|
||||||
import styled, { css } from 'styled-components/macro' |
|
||||||
|
|
||||||
import { isMobileDevice } from 'config/userAgent' |
|
||||||
|
|
||||||
import { |
|
||||||
outlineButtonStyles, |
|
||||||
ButtonSolid as BaseButtonSolid, |
|
||||||
ButtonOutline, |
|
||||||
} from 'features/Common' |
|
||||||
import { T9n } from 'features/T9n' |
|
||||||
|
|
||||||
export const CenterBlock = styled.div` |
|
||||||
display: flex; |
|
||||||
flex-direction: column; |
|
||||||
align-items: center; |
|
||||||
justify-content: flex-start; |
|
||||||
margin-top: 6.133rem; |
|
||||||
${isMobileDevice |
|
||||||
? css` |
|
||||||
margin-top: 110px; |
|
||||||
@media screen and (orientation: landscape) { |
|
||||||
width: 290px; |
|
||||||
margin: auto; |
|
||||||
} |
|
||||||
` |
|
||||||
: ''}; |
|
||||||
` |
|
||||||
|
|
||||||
export const Form = styled.form<{ forRegPage?: boolean }>` |
|
||||||
width: 23.255rem; |
|
||||||
margin-top: 1.982rem; |
|
||||||
margin-bottom: 6.604rem; |
|
||||||
display: flex; |
|
||||||
flex-direction: column; |
|
||||||
align-items: center; |
|
||||||
${isMobileDevice |
|
||||||
? css` |
|
||||||
width: 100%; |
|
||||||
padding: 0 28px; |
|
||||||
margin-top: 22px; |
|
||||||
@media screen and (orientation: landscape){ |
|
||||||
margin-bottom: 20px; |
|
||||||
margin-top: 10px; |
|
||||||
padding: 0; |
|
||||||
} |
|
||||||
` |
|
||||||
: ''}; |
|
||||||
` |
|
||||||
|
|
||||||
export const BlockTitle = styled(T9n)` |
|
||||||
display: block; |
|
||||||
font-style: normal; |
|
||||||
font-weight: bold; |
|
||||||
font-size: 1.14rem; |
|
||||||
line-height: 1.14rem; |
|
||||||
color: ${({ theme: { colors } }) => colors.text100}; |
|
||||||
margin-bottom: 1.3rem; |
|
||||||
transition: color 0.3s ease-in-out; |
|
||||||
${isMobileDevice |
|
||||||
? css` |
|
||||||
font-size: 14.77px; |
|
||||||
line-height: 14.77px;
|
|
||||||
margin-bottom: 18.46px; |
|
||||||
` |
|
||||||
: ''}; |
|
||||||
` |
|
||||||
|
|
||||||
type ButtonsBlockProps = { |
|
||||||
marginTop?: number, |
|
||||||
} |
|
||||||
|
|
||||||
export const ButtonsBlock = styled.div<ButtonsBlockProps>` |
|
||||||
width: 100%; |
|
||||||
display: flex; |
|
||||||
flex-direction: column; |
|
||||||
align-items: flex-start; |
|
||||||
margin-top: ${({ marginTop = 0 }) => marginTop}px; |
|
||||||
` |
|
||||||
|
|
||||||
type ButtonSolidProps = { |
|
||||||
width?: string, |
|
||||||
} |
|
||||||
|
|
||||||
export const ButtonSolid = styled(BaseButtonSolid)<ButtonSolidProps>` |
|
||||||
width: ${({ width = '100%' }) => width}; |
|
||||||
border-radius: 5px; |
|
||||||
font-weight: normal; |
|
||||||
${isMobileDevice |
|
||||||
? css` |
|
||||||
display: block; |
|
||||||
width: 100%; |
|
||||||
font-size: 12.31px; |
|
||||||
@media screen and (orientation: landscape) { |
|
||||||
max-width: 290px; |
|
||||||
margin: auto; |
|
||||||
} |
|
||||||
` |
|
||||||
: ''};
|
|
||||||
` |
|
||||||
|
|
||||||
export const RegisterButton = styled(Link)` |
|
||||||
${outlineButtonStyles} |
|
||||||
width: 100%; |
|
||||||
border-radius: 5px; |
|
||||||
margin-top: 0.71rem; |
|
||||||
display: flex; |
|
||||||
align-items: center; |
|
||||||
justify-content: center; |
|
||||||
${isMobileDevice |
|
||||||
? css` |
|
||||||
min-height: 30px;
|
|
||||||
margin-top: 9.23px; |
|
||||||
font-size: 12.31px; |
|
||||||
@media screen and (orientation: landscape) { |
|
||||||
max-width: 290px; |
|
||||||
margin: 9.23px auto 0; |
|
||||||
} |
|
||||||
` |
|
||||||
: ''}; |
|
||||||
` |
|
||||||
|
|
||||||
export const BorderlessButton = styled(ButtonOutline)` |
|
||||||
border: none; |
|
||||||
padding: 0; |
|
||||||
width: auto; |
|
||||||
height: 2.123rem; |
|
||||||
font-weight: normal; |
|
||||||
font-size: 0.755rem; |
|
||||||
align-self: flex-start; |
|
||||||
${isMobileDevice |
|
||||||
? css` |
|
||||||
font-size: 9.8px; |
|
||||||
` |
|
||||||
: ''}; |
|
||||||
` |
|
||||||
|
|
||||||
export const Or = styled.span` |
|
||||||
margin-top: 0.85rem; |
|
||||||
margin-bottom: 0.65rem; |
|
||||||
color: ${({ theme: { colors } }) => colors.text100}; |
|
||||||
font-style: normal; |
|
||||||
font-weight: normal; |
|
||||||
font-size: 0.755rem; |
|
||||||
line-height: 2.123rem; |
|
||||||
${isMobileDevice |
|
||||||
? css` |
|
||||||
font-size: 14.77px; |
|
||||||
padding: 20px 0;
|
|
||||||
margin: 0; |
|
||||||
@media screen and (orientation: landscape){ |
|
||||||
padding: 10px 0; |
|
||||||
} |
|
||||||
` |
|
||||||
: ''}; |
|
||||||
` |
|
||||||
@ -1,11 +0,0 @@ |
|||||||
import { useAuthForm } from 'hooks/useAuthForm' |
|
||||||
|
|
||||||
import { useAuthStore } from 'features/AuthStore' |
|
||||||
|
|
||||||
export const useRegistrationForm = () => { |
|
||||||
const { register } = useAuthStore() |
|
||||||
|
|
||||||
return { |
|
||||||
...useAuthForm({ onSubmit: register }), |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,85 +0,0 @@ |
|||||||
import styled, { css } from 'styled-components/macro' |
|
||||||
|
|
||||||
import { useHistory } from 'react-router' |
|
||||||
|
|
||||||
import { isMobileDevice } from 'config/userAgent' |
|
||||||
|
|
||||||
import { T9n } from 'features/T9n' |
|
||||||
import { |
|
||||||
ButtonOutline as ButtonOutlineBase, |
|
||||||
NewInput, |
|
||||||
InputGroup, |
|
||||||
PasswordInput, |
|
||||||
} from 'features/Common' |
|
||||||
import { Error } from 'features/Common/Input/styled' |
|
||||||
import { |
|
||||||
BlockTitle, |
|
||||||
ButtonsBlock, |
|
||||||
ButtonSolid, |
|
||||||
Form, |
|
||||||
} from 'features/Login/styled' |
|
||||||
|
|
||||||
import { useRegistrationForm } from './hooks' |
|
||||||
|
|
||||||
const ButtonOutline = styled(ButtonOutlineBase)` |
|
||||||
width: 100%; |
|
||||||
margin-top: 0.71rem; |
|
||||||
border-radius: 5px; |
|
||||||
${isMobileDevice |
|
||||||
? css` |
|
||||||
margin-top: 9.23px; |
|
||||||
min-height: 30px; |
|
||||||
font-size: 12.31px; |
|
||||||
` |
|
||||||
: ''}; |
|
||||||
` |
|
||||||
|
|
||||||
export const RegistrationStep = () => { |
|
||||||
const history = useHistory() |
|
||||||
const { |
|
||||||
email, |
|
||||||
error, |
|
||||||
handleSubmit, |
|
||||||
onEmailBlur, |
|
||||||
onEmailChange, |
|
||||||
onPasswordBlur, |
|
||||||
onPasswordChange, |
|
||||||
password, |
|
||||||
} = useRegistrationForm() |
|
||||||
|
|
||||||
return ( |
|
||||||
<Form onSubmit={handleSubmit} forRegPage> |
|
||||||
<BlockTitle t='step_title_registration' /> |
|
||||||
<InputGroup> |
|
||||||
<NewInput |
|
||||||
type='email' |
|
||||||
placeholderLexic='form_email' |
|
||||||
value={email} |
|
||||||
onChange={onEmailChange} |
|
||||||
onBlur={onEmailBlur} |
|
||||||
/> |
|
||||||
<PasswordInput |
|
||||||
placeholderLexic='form_password' |
|
||||||
value={password} |
|
||||||
onChange={onPasswordChange} |
|
||||||
onBlur={onPasswordBlur} |
|
||||||
/> |
|
||||||
</InputGroup> |
|
||||||
<ButtonsBlock> |
|
||||||
<Error t={error} marginBottom={0.472} /> |
|
||||||
<ButtonSolid |
|
||||||
type='submit' |
|
||||||
disabled={!email || !password || Boolean(error)} |
|
||||||
> |
|
||||||
<T9n t='sign_up' /> |
|
||||||
</ButtonSolid> |
|
||||||
<ButtonOutline |
|
||||||
type='button' |
|
||||||
onClick={history.goBack} |
|
||||||
> |
|
||||||
<T9n t='go_back' /> |
|
||||||
</ButtonOutline> |
|
||||||
</ButtonsBlock> |
|
||||||
</Form> |
|
||||||
) |
|
||||||
} |
|
||||||
@ -1,20 +0,0 @@ |
|||||||
import { PAGES } from 'config' |
|
||||||
|
|
||||||
import { T9n } from 'features/T9n' |
|
||||||
import { BlockTitle } from 'features/Login/styled' |
|
||||||
|
|
||||||
import { |
|
||||||
Container, |
|
||||||
GreenTick, |
|
||||||
ButtonLink, |
|
||||||
} from './styled' |
|
||||||
|
|
||||||
export const RegistrationSuccessful = () => ( |
|
||||||
<Container> |
|
||||||
<BlockTitle t='registration_successful' /> |
|
||||||
<GreenTick /> |
|
||||||
<ButtonLink to={PAGES.login}> |
|
||||||
<T9n t='login' /> |
|
||||||
</ButtonLink> |
|
||||||
</Container> |
|
||||||
) |
|
||||||
@ -1,71 +0,0 @@ |
|||||||
import { Link } from 'react-router-dom' |
|
||||||
import styled, { css } from 'styled-components/macro' |
|
||||||
|
|
||||||
import { devices } from 'config/devices' |
|
||||||
import { isMobileDevice } from 'config/userAgent' |
|
||||||
|
|
||||||
import { solidButtonStyles } from 'features/Common' |
|
||||||
import { BlockTitle } from 'features/Login/styled' |
|
||||||
|
|
||||||
export const Container = styled.div` |
|
||||||
display: flex; |
|
||||||
flex-direction: column; |
|
||||||
align-items: center; |
|
||||||
margin-top: 80px; |
|
||||||
${isMobileDevice |
|
||||||
? css` |
|
||||||
@media screen and (orientation: landscape){ |
|
||||||
margin-top: 20px; |
|
||||||
}
|
|
||||||
` |
|
||||||
: ''}; |
|
||||||
|
|
||||||
@media ${devices.tablet} { |
|
||||||
|
|
||||||
${BlockTitle} { |
|
||||||
text-align: center; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@media ${devices.mobile} { |
|
||||||
margin-top: 20px; |
|
||||||
} |
|
||||||
` |
|
||||||
|
|
||||||
export const GreenTick = styled.div` |
|
||||||
width: 288px; |
|
||||||
height: 288px; |
|
||||||
margin: 60px 0 156px 0; |
|
||||||
background-image: url(/images/greenTick.svg); |
|
||||||
background-position-y: -98px; |
|
||||||
background-position-x: center; |
|
||||||
|
|
||||||
${isMobileDevice |
|
||||||
? css` |
|
||||||
width: 200px; |
|
||||||
height: 200px; |
|
||||||
background-repeat: no-repeat; |
|
||||||
background-position: center; |
|
||||||
background-size: contain; |
|
||||||
margin: 0 auto 15%; |
|
||||||
@media screen and (orientation: landscape){ |
|
||||||
margin: 0; |
|
||||||
} |
|
||||||
` |
|
||||||
: ''}; |
|
||||||
` |
|
||||||
|
|
||||||
export const ButtonLink = styled(Link)` |
|
||||||
${solidButtonStyles} |
|
||||||
display: flex; |
|
||||||
justify-content: center; |
|
||||||
align-items: center; |
|
||||||
${isMobileDevice |
|
||||||
? css` |
|
||||||
border-radius: 5px; |
|
||||||
font-size: 12.31px; |
|
||||||
font-weight: normal; |
|
||||||
` |
|
||||||
: ''}; |
|
||||||
|
|
||||||
` |
|
||||||
@ -1,23 +0,0 @@ |
|||||||
import { Route } from 'react-router' |
|
||||||
|
|
||||||
import { PAGES } from 'config' |
|
||||||
|
|
||||||
import { Logo } from 'features/Logo' |
|
||||||
import { CenterBlock } from 'features/Login/styled' |
|
||||||
|
|
||||||
import { RegistrationStep } from './components/RegistrationStep' |
|
||||||
import { RegistrationSuccessful } from './components/RegistrationSuccessful' |
|
||||||
|
|
||||||
const Register = () => ( |
|
||||||
<CenterBlock> |
|
||||||
<Logo /> |
|
||||||
<Route exact path={`${PAGES.register}`}> |
|
||||||
<RegistrationStep /> |
|
||||||
</Route> |
|
||||||
<Route exact path={`${PAGES.register}/successful`}> |
|
||||||
<RegistrationSuccessful /> |
|
||||||
</Route> |
|
||||||
</CenterBlock> |
|
||||||
) |
|
||||||
|
|
||||||
export default Register |
|
||||||
@ -1,22 +0,0 @@ |
|||||||
import { isValidEmail } from '..' |
|
||||||
|
|
||||||
it('invalid emails', () => { |
|
||||||
expect(isValidEmail('a')).toBeFalsy() |
|
||||||
expect(isValidEmail('1')).toBeFalsy() |
|
||||||
expect(isValidEmail('a@')).toBeFalsy() |
|
||||||
expect(isValidEmail('a@m')).toBeFalsy() |
|
||||||
expect(isValidEmail('a@m.')).toBeFalsy() |
|
||||||
expect(isValidEmail('a@mail')).toBeFalsy() |
|
||||||
expect(isValidEmail('a@mail.')).toBeFalsy() |
|
||||||
expect(isValidEmail('abcd.mail.com')).toBeFalsy() |
|
||||||
}) |
|
||||||
|
|
||||||
it('valid emails', () => { |
|
||||||
expect(isValidEmail('a@mail.com')).toBeTruthy() |
|
||||||
expect(isValidEmail('A@MAIL.COM')).toBeTruthy() |
|
||||||
expect(isValidEmail('123@mail.com')).toBeTruthy() |
|
||||||
expect(isValidEmail('A123@mail.com')).toBeTruthy() |
|
||||||
expect(isValidEmail('a.b@mail.com')).toBeTruthy() |
|
||||||
expect(isValidEmail('a-b-c@mail.com')).toBeTruthy() |
|
||||||
expect(isValidEmail('a-b@a-b.com')).toBeTruthy() |
|
||||||
}) |
|
||||||
@ -1,9 +0,0 @@ |
|||||||
import size from 'lodash/size' |
|
||||||
|
|
||||||
export const emailRegex = '[a-z0-9!#$%&/\'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&/\'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?' |
|
||||||
|
|
||||||
const emailRegExp = new RegExp(emailRegex) |
|
||||||
|
|
||||||
export const isValidEmail = (email: string) => ( |
|
||||||
emailRegExp.test(email.toLowerCase()) && size(email) <= 100 |
|
||||||
) |
|
||||||
@ -1,62 +0,0 @@ |
|||||||
import type { FocusEvent, FormEvent } from 'react' |
|
||||||
import { useState } from 'react' |
|
||||||
|
|
||||||
import { isValidEmail } from 'helpers/isValidEmail' |
|
||||||
import { isValidPassword } from 'helpers/isValidPassword' |
|
||||||
|
|
||||||
export type Values = { |
|
||||||
email: string, |
|
||||||
password: string, |
|
||||||
} |
|
||||||
|
|
||||||
type Args = { |
|
||||||
onSubmit: (values: Values) => Promise<void>, |
|
||||||
} |
|
||||||
|
|
||||||
export const useAuthForm = ({ onSubmit }: Args) => { |
|
||||||
const [email, setEmail] = useState('') |
|
||||||
const [password, setPassword] = useState('') |
|
||||||
const [error, setError] = useState('') |
|
||||||
|
|
||||||
const onEmailChange = ({ target: { value } }: FocusEvent<HTMLInputElement>) => { |
|
||||||
setError('') |
|
||||||
setEmail(value) |
|
||||||
} |
|
||||||
|
|
||||||
const onPasswordChange = ({ target: { value } }: FocusEvent<HTMLInputElement>) => { |
|
||||||
setError('') |
|
||||||
setPassword(value) |
|
||||||
} |
|
||||||
|
|
||||||
const onEmailBlur = ({ target: { value } }: FocusEvent<HTMLInputElement>) => { |
|
||||||
if (!value) { |
|
||||||
setError('error_empty_email') |
|
||||||
} else if (!isValidEmail(value)) { |
|
||||||
setError('error_invalid_email_format') |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
const onPasswordBlur = ({ target: { value } }: FocusEvent<HTMLInputElement>) => { |
|
||||||
if (!value) { |
|
||||||
setError('error_empty_password') |
|
||||||
} else if (!isValidPassword(value)) { |
|
||||||
setError('error_simple_password') |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
const handleSubmit = async (event: FormEvent<HTMLFormElement>) => { |
|
||||||
event.preventDefault() |
|
||||||
onSubmit({ email, password }).catch(setError) |
|
||||||
} |
|
||||||
|
|
||||||
return { |
|
||||||
email, |
|
||||||
error, |
|
||||||
handleSubmit, |
|
||||||
onEmailBlur, |
|
||||||
onEmailChange, |
|
||||||
onPasswordBlur, |
|
||||||
onPasswordChange, |
|
||||||
password, |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,54 +0,0 @@ |
|||||||
import { DATA_URL, PROCEDURES } from 'config' |
|
||||||
import { callApiBase } from 'helpers' |
|
||||||
|
|
||||||
const proc = PROCEDURES.create_user |
|
||||||
|
|
||||||
const statusCodes = { |
|
||||||
EMAIL_IN_USE: 2, |
|
||||||
SUCCESS: 1, |
|
||||||
} |
|
||||||
|
|
||||||
const errorMessages = { |
|
||||||
[statusCodes.EMAIL_IN_USE]: 'error_email_already_in_use', |
|
||||||
} |
|
||||||
|
|
||||||
type Response = { |
|
||||||
_p_error: string | null, |
|
||||||
_p_status: 1 | 2, |
|
||||||
} |
|
||||||
|
|
||||||
type Args = { |
|
||||||
email: string, |
|
||||||
password: string, |
|
||||||
} |
|
||||||
|
|
||||||
export const register = async ({ |
|
||||||
email, |
|
||||||
password, |
|
||||||
}: Args) => { |
|
||||||
const config = { |
|
||||||
body: { |
|
||||||
params: { |
|
||||||
_p_email: email, |
|
||||||
_p_password: password, |
|
||||||
}, |
|
||||||
proc, |
|
||||||
}, |
|
||||||
} |
|
||||||
|
|
||||||
try { |
|
||||||
const response = await callApiBase({ |
|
||||||
config, |
|
||||||
url: DATA_URL, |
|
||||||
}) |
|
||||||
|
|
||||||
const { _p_status }: Response = await response.json() |
|
||||||
|
|
||||||
if (_p_status === statusCodes.SUCCESS) { |
|
||||||
return Promise.resolve(response) |
|
||||||
} |
|
||||||
return Promise.reject(errorMessages[_p_status]) |
|
||||||
} catch { |
|
||||||
return Promise.reject() |
|
||||||
} |
|
||||||
} |
|
||||||
Loading…
Reference in new issue