feat: coming soon

This commit is contained in:
Divlo
2021-10-24 05:48:06 +02:00
parent 21123c4477
commit 33bd2bb6bf
176 changed files with 36858 additions and 22133 deletions

View File

@ -1,99 +0,0 @@
import Link from 'next/link'
import useTranslation from 'next-translate/useTranslation'
import { Input } from 'components/design/Input'
import { FormState } from 'components/Authentication/FormState'
import { ValidatorSchema } from 'hooks/useFastestValidator'
import { AuthenticationProps } from '.'
import { AuthenticationFormLayout } from './AuthenticationFormLayout'
import { useForm } from 'hooks/useForm'
export const emailSchema: ValidatorSchema = {
email: {
type: 'email',
empty: false,
trim: true
}
}
export const nameSchema: ValidatorSchema = {
name: {
type: 'string',
min: 3,
max: 30,
trim: true
}
}
export const passwordSchema: ValidatorSchema = {
password: {
type: 'string',
empty: false,
trim: true
}
}
export const AuthenticationForm: React.FC<AuthenticationProps> = (props) => {
const { mode, onSubmit } = props
const { t } = useTranslation()
const {
getErrorMessages,
formState,
message,
handleChange,
handleSubmit
} = useForm({
validatorSchema: {
...(mode === 'signup' && { ...nameSchema }),
...emailSchema,
...passwordSchema
}
})
return (
<>
<AuthenticationFormLayout
onChange={handleChange}
onSubmit={handleSubmit(onSubmit)}
link={
<p>
<Link href={mode === 'signup' ? '/authentication/signin' : '/authentication/signup'}>
<a>
{mode === 'signup'
? t('authentication:already-have-an-account')
: t('authentication:dont-have-an-account')}
</a>
</Link>
</p>
}
>
{mode === 'signup' && (
<Input
errors={getErrorMessages('name')}
type='text'
placeholder={t('authentication:name')}
name='name'
label={t('authentication:name')}
/>
)}
<Input
errors={getErrorMessages('email')}
type='email'
placeholder='Email'
name='email'
label='Email'
/>
<Input
errors={getErrorMessages('password')}
type='password'
placeholder={t('authentication:password')}
name='password'
label={t('authentication:password')}
showForgotPassword={mode === 'signin'}
/>
</AuthenticationFormLayout>
<FormState state={formState} message={message} />
</>
)
}

View File

@ -1,53 +0,0 @@
import Form, { HandleForm } from 'react-component-form'
import { Button } from 'components/design/Button'
import useTranslation from 'next-translate/useTranslation'
export interface AuthenticationFormLayoutProps {
onChange?: HandleForm
onSubmit?: HandleForm
link?: React.ReactNode
}
export const AuthenticationFormLayout: React.FC<AuthenticationFormLayoutProps> = (
props
) => {
const { children, onChange, onSubmit, link } = props
const { t } = useTranslation()
return (
<>
<Form onChange={onChange} onSubmit={onSubmit}>
<div className='form-container'>
<div className='form'>
{children}
<Button style={{ width: '100%' }} type='submit'>
{t('authentication:submit')}
</Button>
{link}
</div>
</div>
</Form>
<style jsx>
{`
@media (max-width: 330px) {
.form {
width: auto !important;
}
}
.form {
flex-shrink: 0;
width: 310px;
}
.form-container {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
}
`}
</style>
</>
)
}

View File

@ -1,109 +0,0 @@
import { useRouter } from 'next/router'
import {
SocialMediaButton,
SocialMedia
} from 'components/design/SocialMediaButton'
import { api } from 'utils/api'
import { Authentication, Tokens } from 'utils/authentication'
import { useEffect } from 'react'
const isTokens = (data: { [key: string]: any }): data is Tokens => {
return (
'accessToken' in data &&
'refreshToken' in data &&
'type' in data &&
'expiresIn' in data
)
}
export const AuthenticationSocialMedia: React.FC = () => {
const router = useRouter()
const handleAuthentication = async (
socialMedia: SocialMedia
): Promise<void> => {
const redirect = window.location.href
const { data: url } = await api.get(
`/users/oauth2/${socialMedia.toLowerCase()}/signin?redirectURI=${redirect}`
)
window.location.href = url
}
useEffect(() => {
const data = router.query
if (isTokens(data)) {
const authentication = new Authentication(data)
authentication.signin()
router.push('/application').catch(() => {})
}
}, [router.query])
return (
<>
<div className='social-container'>
<div className='social-buttons'>
<SocialMediaButton
onClick={async () => await handleAuthentication('Google')}
className='social-button'
socialMedia='Google'
/>
<SocialMediaButton
onClick={async () => await handleAuthentication('GitHub')}
className='social-button'
socialMedia='GitHub'
/>
<SocialMediaButton
onClick={async () => await handleAuthentication('Discord')}
className='social-button'
socialMedia='Discord'
/>
</div>
</div>
<style jsx>
{`
@media (max-width: 600px) {
:global(.social-button) {
margin-top: 15px !important;
}
.social-container {
margin-top: 20px !important;
}
.social-buttons {
height: 100% !important;
}
}
.social-container {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
}
.social-buttons {
display: flex;
justify-content: space-evenly;
width: 60%;
}
@media (max-width: 970px) {
.social-buttons {
width: 80%;
}
}
@media (max-width: 770px) {
.social-buttons {
width: 100%;
}
}
@media (max-width: 600px) {
.social-buttons {
flex-direction: column;
align-items: center;
height: 30%;
}
}
`}
</style>
</>
)
}

View File

@ -1,76 +0,0 @@
import useTranslation from 'next-translate/useTranslation'
export interface ErrorMessageProps {
errors: string[]
fontSize?: number
}
export const ErrorMessage: React.FC<ErrorMessageProps> = (props) => {
const { errors, fontSize = 14 } = props
const { t } = useTranslation()
if (errors.length === 0) {
return null
}
return (
<>
<div className='error-message'>
{errors.length === 1 && (
<>
<div className='error-thumbnail' />
<span className='error-text'>{errors[0]}</span>
</>
)}
{errors.length > 1 && (
<>
<div className='error-container'>
<div className='error-thumbnail' />
<span className='error-text'>{t('authentication:errors')} :</span>
</div>
<ul className='errors-list'>
{errors.map((error, index) => {
return <li key={index}>{error}</li>
})}
</ul>
</>
)}
</div>
<style jsx>
{`
.error-message {
position: relative;
display: ${errors.length > 1 ? 'block' : 'flex'};
flex-flow: row;
align-items: center;
margin-top: 12px;
left: -3px;
color: var(--color-error);
font-family: 'Poppins', 'Arial', 'sans-serif';
font-size: ${fontSize}px;
line-height: 21px;
}
.error-container {
display: flex;
align-items: center;
}
.errors-list {
margin: 10px 0 0 0;
}
.error-thumbnail {
display: inline-block;
min-width: 20px;
width: 20px;
height: 20px;
background-image: url(/images/svg/icons/input/error.svg);
background-size: cover;
}
.error-text {
padding-left: 5px;
}
`}
</style>
</>
)
}

View File

@ -1,104 +0,0 @@
import useTranslation from 'next-translate/useTranslation'
import { FormState as FormStateType } from 'hooks/useFormState'
import { ErrorMessage } from './ErrorMessage'
import { Loader } from 'components/design/Loader'
export interface FormStateProps {
state: FormStateType
message?: string
}
export const FormState: React.FC<FormStateProps> = (props) => {
const { state, message } = props
const { t } = useTranslation()
if (state === 'loading') {
return (
<>
<div data-testid='loader' className='loader'>
<Loader />
</div>
<style jsx>
{`
.loader {
margin-top: 30px;
display: flex;
justify-content: center;
}
`}
</style>
</>
)
}
if (state === 'idle' || message == null) {
return null
}
if (state === 'success') {
return (
<>
<div className='success'>
<div className='success-message'>
<div className='success-thumbnail' />
<span className='success-text'>
<b>{t('authentication:success')} :</b> {message}
</span>
</div>
</div>
<style jsx>
{`
.success {
margin-top: 20px;
display: flex;
justify-content: center;
}
.success-message {
position: relative;
display: flex;
flex-flow: row;
align-items: center;
justify-content: center;
margin-top: 12px;
left: -3px;
color: var(--color-success);
font-family: 'Arial', 'sans-serif';
font-size: 16px;
line-height: 21px;
}
.success-thumbnail {
display: inline-block;
width: 20px;
height: 22px;
background-image: url(/images/svg/icons/input/success.svg);
background-size: cover;
}
.success-text {
padding-left: 5px;
}
`}
</style>
</>
)
}
return (
<>
<div data-testid='error' className='error'>
<ErrorMessage fontSize={16} errors={[message]} />
</div>
<style jsx>
{`
.error {
margin-top: 20px;
display: flex;
justify-content: center;
}
`}
</style>
</>
)
}

View File

@ -1,14 +0,0 @@
import { useTheme } from 'contexts/Theme'
export const Success: React.FC = () => {
const { theme } = useTheme()
return (
<svg data-testid='success' width='25' height='25' fill='none' xmlns='http://www.w3.org/2000/svg'>
<path
d='M12.5 0C5.607 0 0 5.607 0 12.5 0 19.392 5.607 25 12.5 25 19.392 25 25 19.392 25 12.5 25 5.607 19.392 0 12.5 0zm-2.499 18.016L5.36 13.385l1.765-1.77 2.874 2.869 6.617-6.618 1.768 1.768L10 18.016z'
fill={theme === 'light' ? '#1e4620' : '#90ee90'}
/>
</svg>
)
}

View File

@ -1,16 +0,0 @@
import { render } from '@testing-library/react'
import { ErrorMessage } from '../ErrorMessage'
describe('<ErrorMessage />', () => {
it('should return nothing if there are no errors', async () => {
const { container } = render(<ErrorMessage errors={[]} />)
expect(container.innerHTML.length).toEqual(0)
})
it('should render the single error', async () => {
const errorMessage = 'Error Message'
const { getByText } = render(<ErrorMessage errors={[errorMessage]} />)
expect(getByText(errorMessage)).toBeInTheDocument()
})
})

View File

@ -1,33 +0,0 @@
import { render } from '@testing-library/react'
import { FormState } from '../FormState'
describe('<FormState />', () => {
it('should return nothing if the state is idle', async () => {
const { container } = render(<FormState state='idle' />)
expect(container.innerHTML.length).toEqual(0)
})
it('should return nothing if the message is null', async () => {
const { container } = render(<FormState state='error' />)
expect(container.innerHTML.length).toEqual(0)
})
it('should render the <Loader /> if state is loading', async () => {
const { getByTestId } = render(<FormState state='loading' />)
expect(getByTestId('loader')).toBeInTheDocument()
})
it('should render the success message if state is success', async () => {
const message = 'Success Message'
const { getByText } = render(
<FormState state='success' message={message} />
)
expect(getByText(message)).toBeInTheDocument()
})
it('should render the error message if state is error', async () => {
const { getByTestId } = render(<FormState state='error' message='Error Message' />)
expect(getByTestId('error')).toBeInTheDocument()
})
})

View File

@ -1,10 +0,0 @@
import { render } from '@testing-library/react'
import { Success } from '../Success'
describe('<Success />', () => {
it('should render', async () => {
const { getByTestId } = render(<Success />)
expect(getByTestId('success')).toBeInTheDocument()
})
})

View File

@ -1,49 +0,0 @@
import useTranslation from 'next-translate/useTranslation'
import { Divider } from 'components/design/Divider'
import { Header } from 'components/Header'
import { AuthenticationForm } from 'components/Authentication/AuthenticationForm'
import { AuthenticationSocialMedia } from 'components/Authentication/AuthenticationSocialMedia'
import { Container } from 'components/design/Container'
import { HandleSubmitCallback } from 'hooks/useForm'
export interface AuthenticationProps {
mode: 'signup' | 'signin'
onSubmit: HandleSubmitCallback
}
export const Authentication: React.FC<AuthenticationProps> = (props) => {
const { mode, onSubmit } = props
const { t } = useTranslation()
return (
<>
<Header />
<Container className='container-authentication'>
<AuthenticationSocialMedia />
<div className='divider'>
<Divider content={t('authentication:or')} />
</div>
<AuthenticationForm onSubmit={onSubmit} mode={mode} />
</Container>
<style jsx>
{`
@media (max-height: 700px) {
:global(.container-authentication) {
height: auto !important;
}
}
@media (max-width: 600px) {
.divider {
margin: 20px 0 !important;
}
}
.divider {
margin: 40px 0;
}
`}
</style>
</>
)
}