feat: coming soon
This commit is contained in:
@ -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} />
|
||||
</>
|
||||
)
|
||||
}
|
@ -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>
|
||||
</>
|
||||
)
|
||||
}
|
@ -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>
|
||||
</>
|
||||
)
|
||||
}
|
@ -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>
|
||||
</>
|
||||
)
|
||||
}
|
@ -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>
|
||||
</>
|
||||
)
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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()
|
||||
})
|
||||
})
|
@ -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()
|
||||
})
|
||||
})
|
@ -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()
|
||||
})
|
||||
})
|
@ -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>
|
||||
</>
|
||||
)
|
||||
}
|
Reference in New Issue
Block a user