refactoring: authentication with react-query.

This commit is contained in:
a.bouhuolia
2021-02-20 15:33:20 +02:00
parent 8f680e2068
commit a079f711d4
57 changed files with 1629 additions and 1290 deletions

View File

@@ -1,7 +1,7 @@
import React from 'react';
import Icon from 'components/Icon';
function AuthCopyright() {
export default function AuthCopyright() {
return (
<div class="auth-copyright">
<div class="auth-copyright__text">
@@ -11,6 +11,4 @@ function AuthCopyright() {
<Icon width={122} height={22} icon={'bigcapital'} />
</div>
);
}
export default AuthCopyright;
}

View File

@@ -1,7 +1,9 @@
import React from 'react';
import Icon from 'components/Icon';
import AuthCopyright from './AuthCopyright';
/**
* Authentication insider page.
*/
export default function AuthInsider({
logo = true,
copyright = true,

View File

@@ -1,276 +1,20 @@
import React, { useCallback, useMemo, useState } from 'react';
import * as Yup from 'yup';
import { useFormik } from 'formik';
import {
Button,
InputGroup,
Intent,
FormGroup,
Position,
Spinner,
} from '@blueprintjs/core';
import React from 'react';
import { useParams } from 'react-router-dom';
import { Row, Col } from 'react-grid-system';
import { Link, useHistory } from 'react-router-dom';
import { FormattedMessage as T, useIntl } from 'react-intl';
import AppToaster from 'components/AppToaster';
import ErrorMessage from 'components/ErrorMessage';
import Icon from 'components/Icon';
import { If } from 'components';
import useAsync from 'hooks/async';
import InviteAcceptForm from './InviteAcceptForm';
import AuthInsider from 'containers/Authentication/AuthInsider';
import { InviteAcceptProvider } from './InviteAcceptProvider';
import withAuthenticationActions from './withAuthenticationActions';
import { compose } from 'utils';
function Invite({ requestInviteAccept, requestInviteMetaByToken }) {
const { formatMessage } = useIntl();
/**
* Authentication invite page.
*/
export default function Invite() {
const { token } = useParams();
const history = useHistory();
const [shown, setShown] = useState(false);
const passwordRevealer = useCallback(() => {
setShown(!shown);
}, [shown]);
const ValidationSchema = Yup.object().shape({
first_name: Yup.string().required().label(formatMessage({id:'first_name_'})),
last_name: Yup.string().required().label(formatMessage({id:'last_name_'})),
phone_number: Yup.string()
.matches()
.required()
.label(formatMessage({id:'phone_number'})),
password: Yup.string()
.min(4)
.required().label(formatMessage({id:'password'}))
});
const inviteMeta = useAsync(() => {
return requestInviteMetaByToken(token);
});
const inviteErrors = inviteMeta.error || [];
const inviteValue = {
organization_name: '',
invited_email: '',
...(inviteMeta.value ? inviteMeta.value.data.data : {}),
};
if (inviteErrors.find((e) => e.type === 'INVITE.TOKEN.NOT.FOUND')) {
AppToaster.show({
message: 'An unexpected error occurred',
intent: Intent.DANGER,
position: Position.BOTTOM,
});
history.push('/auth/login');
}
const initialValues = useMemo(
() => ({
first_name: '',
last_name: '',
phone_number: '',
password: '',
}),
[]
);
const {
touched,
errors,
handleSubmit,
getFieldProps,
isSubmitting,
} = useFormik({
enableReinitialize: true,
validationSchema: ValidationSchema,
initialValues: {
...initialValues,
},
onSubmit: (values, { setSubmitting, setErrors }) => {
requestInviteAccept(values, token)
.then((response) => {
AppToaster.show({
message: `Congrats! Your account has been created and invited to
<strong>${inviteValue.organization_name}</strong> organization successfully.`,
intent: Intent.SUCCESS,
});
setSubmitting(false);
})
.catch((errors) => {
if (errors.find((e) => e.type === 'INVITE.TOKEN.NOT.FOUND')) {
AppToaster.show({
message: formatMessage({ id: 'an_unexpected_error_occurred' }),
intent: Intent.DANGER,
position: Position.BOTTOM,
});
history.push('/auth/login');
}
if (errors.find((e) => e.type === 'PHONE_MUMNER.ALREADY.EXISTS')) {
setErrors({
phone_number: 'This phone number is used in another account.',
});
}
setSubmitting(false);
});
},
});
const passwordRevealerTmp = useMemo(
() => (
<span class='password-revealer' onClick={() => passwordRevealer()}>
<If condition={shown}>
<>
<Icon icon='eye-slash' />{' '}
<span class='text'>
<T id={'hide'} />
</span>
</>
</If>
<If condition={!shown}>
<>
<Icon icon='eye' />{' '}
<span class='text'>
<T id={'show'} />
</span>
</>
</If>
</span>
),
[shown, passwordRevealer]
);
return (
<AuthInsider>
<div className={'invite-form'}>
<div className={'authentication-page__label-section'}>
<h3>
<T id={'welcome_to_bigcapital'} />
</h3>
<p>
<T id={'enter_your_personal_information'} />
<b>{inviteValue.organization_name}</b> <T id={'organization'}/>
</p>
</div>
<form onSubmit={handleSubmit}>
<Row>
<Col md={6}>
<FormGroup
label={<T id={'First Name'} />}
className={'form-group--first_name'}
intent={
errors.first_name && touched.first_name && Intent.DANGER
}
helperText={
<ErrorMessage name={'first_name'} {...{ errors, touched }} />
}
>
<InputGroup
intent={
errors.first_name && touched.first_name && Intent.DANGER
}
{...getFieldProps('first_name')}
/>
</FormGroup>
</Col>
<Col md={6}>
<FormGroup
label={<T id={'Last Name'} />}
className={'form-group--last_name'}
intent={errors.last_name && touched.last_name && Intent.DANGER}
helperText={
<ErrorMessage name={'last_name'} {...{ errors, touched }} />
}
>
<InputGroup
intent={
errors.last_name && touched.last_name && Intent.DANGER
}
{...getFieldProps('last_name')}
/>
</FormGroup>
</Col>
</Row>
<FormGroup
label={<T id={'Phone Number'} />}
className={'form-group--phone_number'}
intent={
errors.phone_number && touched.phone_number && Intent.DANGER
}
helperText={
<ErrorMessage name={'phone_number'} {...{ errors, touched }} />
}
>
<InputGroup
intent={
errors.phone_number && touched.phone_number && Intent.DANGER
}
{...getFieldProps('phone_number')}
/>
</FormGroup>
<FormGroup
label={<T id={'password'} />}
labelInfo={passwordRevealerTmp}
className={'form-group--password has-password-revealer'}
intent={errors.password && touched.password && Intent.DANGER}
helperText={
<ErrorMessage name={'password'} {...{ errors, touched }} />
}
>
<InputGroup
lang={true}
type={shown ? 'text' : 'password'}
intent={errors.password && touched.password && Intent.DANGER}
{...getFieldProps('password')}
/>
</FormGroup>
<div className={'invite-form__statement-section'}>
<p>
<T id={'You email address is'} />{' '}
<b>{inviteValue.invited_email},</b> <br />
<T id={'you_will_use_this_address_to_sign_in_to_bigcapital'} />
</p>
<p>
<T id={'signing_in_or_creating'} /> <br />
<Link>
<T id={'terms_conditions'} />
</Link>{' '}
<T id={'and'} />
<Link>
{' '}
<T id={'privacy_statement'} />
</Link>
</p>
</div>
<div className={'authentication-page__submit-button-wrap'}>
<Button
intent={Intent.PRIMARY}
type='submit'
fill={true}
loading={isSubmitting}
>
<T id={'create_account'} />
</Button>
</div>
</form>
{inviteMeta.pending && (
<div class='authentication-page__loading-overlay'>
<Spinner size={40} />
</div>
)}
</div>
<InviteAcceptProvider token={token}>
<InviteAcceptForm />
</InviteAcceptProvider>
</AuthInsider>
);
}
export default compose(withAuthenticationActions)(Invite);

View File

@@ -0,0 +1,100 @@
import React from 'react';
import { Intent, Position } from '@blueprintjs/core';
import { Formik } from 'formik';
import { useHistory } from 'react-router-dom';
import { FormattedMessage as T, useIntl } from 'react-intl';
import { isEmpty } from 'lodash';
import { useInviteAcceptContext } from './InviteAcceptProvider';
import { AppToaster } from 'components';
import { InviteAcceptSchema } from './utils';
import InviteAcceptFormContent from './InviteAcceptFormContent';
export default function InviteAcceptForm() {
const history = useHistory();
const { formatMessage } = useIntl();
// Invite accept context.
const {
inviteAcceptMutate,
inviteMeta,
token,
} = useInviteAcceptContext();
// Invite value.
const inviteValue = {
organization_name: '',
invited_email: '',
...(!isEmpty(inviteMeta)
? {
invited_email: inviteMeta.email,
organization_name: inviteMeta.organizationName,
}
: {}),
};
// Handle form submitting.
const handleSubmit = (values, { setSubmitting, setErrors }) => {
inviteAcceptMutate([values, token])
.then((response) => {
AppToaster.show({
message: `Congrats! Your account has been created and invited to
<strong>${inviteValue.organization_name}</strong> organization successfully.`,
intent: Intent.SUCCESS,
});
history.push('/auth/login');
setSubmitting(false);
})
.catch(
({
response: {
data: { errors },
},
}) => {
if (errors.find((e) => e.type === 'INVITE.TOKEN.NOT.FOUND')) {
AppToaster.show({
message: formatMessage({ id: 'an_unexpected_error_occurred' }),
intent: Intent.DANGER,
position: Position.BOTTOM,
});
history.push('/auth/login');
}
if (errors.find((e) => e.type === 'PHONE_MUMNER.ALREADY.EXISTS')) {
setErrors({
phone_number: 'This phone number is used in another account.',
});
}
if (errors.find((e) => e.type === 'INVITE.TOKEN.NOT.FOUND')) {
AppToaster.show({
message: 'An unexpected error occurred',
intent: Intent.DANGER,
position: Position.BOTTOM,
});
history.push('/auth/login');
}
setSubmitting(false);
},
);
};
return (
<div className={'invite-form'}>
<div className={'authentication-page__label-section'}>
<h3>
<T id={'welcome_to_bigcapital'} />
</h3>
<p>
<T id={'enter_your_personal_information'} />{' '}
<b>{inviteValue.organization_name}</b> <T id={'organization'} />
</p>
</div>
<Formik
validationSchema={InviteAcceptSchema}
initialValues={inviteValue}
onSubmit={handleSubmit}
component={InviteAcceptFormContent}
/>
</div>
);
}

View File

@@ -0,0 +1,122 @@
import React from 'react';
import { Button, InputGroup, Intent, FormGroup } from '@blueprintjs/core';
import { Form, ErrorMessage, FastField, useFormikContext } from 'formik';
import { Link } from 'react-router-dom';
import { FormattedMessage as T } from 'react-intl';
import { inputIntent } from 'utils';
import { Col, Row } from 'components';
import { useInviteAcceptContext } from './InviteAcceptProvider';
import { PasswordRevealer } from './components';
/**
* Invite user form.
*/
export default function InviteUserFormContent() {
// Invite accept context.
const { inviteMeta } = useInviteAcceptContext();
// Formik context.
const { isSubmitting } = useFormikContext();
return (
<Form>
<Row>
<Col md={6}>
<FastField name={'first_name'}>
{({ form, field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'First Name'} />}
className={'form-group--first_name'}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name={'first_name'} />}
>
<InputGroup
intent={inputIntent({ error, touched })}
{...field}
/>
</FormGroup>
)}
</FastField>
</Col>
<Col md={6}>
<FastField name={'last_name'}>
{({ form, field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'Last Name'} />}
className={'form-group--last_name'}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name={'last_name'} />}
>
<InputGroup
intent={inputIntent({ error, touched })}
{...field}
/>
</FormGroup>
)}
</FastField>
</Col>
</Row>
<FastField name={'phone_number'}>
{({ form, field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'Phone Number'} />}
className={'form-group--phone_number'}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name={'phone_number'} />}
>
<InputGroup intent={inputIntent({ error, touched })} {...field} />
</FormGroup>
)}
</FastField>
<FastField name={'password'}>
{({ form, field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'password'} />}
labelInfo={<PasswordRevealer />}
className={'form-group--password has-password-revealer'}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name={'password'} />}
>
<InputGroup
lang={true}
// type={shown ? 'text' : 'password'}
intent={inputIntent({ error, touched })}
{...field}
/>
</FormGroup>
)}
</FastField>
<div className={'invite-form__statement-section'}>
<p>
<T id={'You email address is'} /> <b>{inviteMeta.email},</b> <br />
<T id={'you_will_use_this_address_to_sign_in_to_bigcapital'} />
</p>
<p>
<T id={'signing_in_or_creating'} /> <br />
<Link>
<T id={'terms_conditions'} />
</Link>{' '}
<T id={'and'} />
<Link>
{' '}
<T id={'privacy_statement'} />
</Link>
</p>
</div>
<div className={'authentication-page__submit-button-wrap'}>
<Button
intent={Intent.PRIMARY}
type="submit"
fill={true}
loading={isSubmitting}
>
<T id={'create_account'} />
</Button>
</div>
</Form>
);
}

View File

@@ -0,0 +1,56 @@
import React, { createContext, useContext } from 'react';
import { useInviteMetaByToken, useAuthInviteAccept } from 'hooks/query';
import { InviteAcceptLoading } from './components';
import { useHistory } from 'react-router-dom';
const InviteAcceptContext = createContext();
/**
* Invite accept provider.
*/
function InviteAcceptProvider({ token, ...props }) {
// Invite meta by token.
const {
data: inviteMeta,
error: inviteMetaError,
isError: isInviteMetaError,
isFetching: isInviteMetaLoading,
} = useInviteMetaByToken(token, { retry: false });
// Invite accept mutate.
const { mutateAsync: inviteAcceptMutate } = useAuthInviteAccept({
retry: false,
});
// History context.
const history = useHistory();
React.useEffect(() => {
if (inviteMetaError) { history.push('/auth/login'); }
}, [history, inviteMetaError]);
// Provider payload.
const provider = {
token,
inviteMeta,
inviteMetaError,
isInviteMetaError,
isInviteMetaLoading,
inviteAcceptMutate
};
if (inviteMetaError) {
return null;
}
return (
<InviteAcceptLoading isLoading={isInviteMetaLoading}>
{ isInviteMetaError }
<InviteAcceptContext.Provider value={provider} {...props} />
</InviteAcceptLoading>
);
}
const useInviteAcceptContext = () => useContext(InviteAcceptContext);
export { InviteAcceptProvider, useInviteAcceptContext };

View File

@@ -1,132 +1,38 @@
import React, { useMemo, useState } from 'react';
import { Link, useHistory } from 'react-router-dom';
import * as Yup from 'yup';
import { useFormik } from 'formik';
import { FormattedMessage as T, useIntl } from 'react-intl';
import {
Button,
InputGroup,
Intent,
FormGroup,
Checkbox,
} from '@blueprintjs/core';
import React from 'react';
import { Link } from 'react-router-dom';
import { Formik } from 'formik';
import { FormattedMessage as T } from 'react-intl';
import Toaster from 'components/AppToaster';
import ErrorMessage from 'components/ErrorMessage';
import AuthInsider from 'containers/Authentication/AuthInsider';
import { useAuthLogin } from 'hooks/query';
import Icon from 'components/Icon';
import { If } from 'components';
import LoginForm from './LoginForm';
import { LoginSchema, transformLoginErrorsToToasts } from './utils';
import withAuthenticationActions from './withAuthenticationActions';
import { compose } from 'utils';
const ERRORS_TYPES = {
INVALID_DETAILS: 'INVALID_DETAILS',
USER_INACTIVE: 'USER_INACTIVE',
LOGIN_TO_MANY_ATTEMPTS: 'LOGIN_TO_MANY_ATTEMPTS',
};
function Login({ requestLogin }) {
const { formatMessage } = useIntl();
const history = useHistory();
const [shown, setShown] = useState(false);
const passwordRevealer = () => {
setShown(!shown);
};
// Validation schema.
const loginValidationSchema = Yup.object().shape({
crediential: Yup.string()
.required()
.email()
.label(formatMessage({ id: 'email' })),
password: Yup.string()
.required()
.min(4)
.label(formatMessage({ id: 'password' })),
});
// Formik validation schema and submit handler.
const {
touched,
errors,
handleSubmit,
getFieldProps,
isSubmitting,
} = useFormik({
initialValues: {
crediential: '',
password: '',
},
validationSchema: loginValidationSchema,
onSubmit: (values, { setSubmitting }) => {
requestLogin({
crediential: values.crediential,
password: values.password,
/**
* Login page.
*/
export default function Login() {
const { mutateAsync: loginMutate } = useAuthLogin();
const handleSubmit = (values, { setSubmitting }) => {
loginMutate({
crediential: values.crediential,
password: values.password,
})
.then(() => {
setSubmitting(false);
})
.then(() => {
setSubmitting(false);
})
.catch((errors) => {
const toastBuilders = [];
if (errors.find((e) => e.type === ERRORS_TYPES.INVALID_DETAILS)) {
toastBuilders.push({
message: formatMessage({
id: 'email_and_password_entered_did_not_match',
}),
intent: Intent.DANGER,
});
}
if (errors.find((e) => e.type === ERRORS_TYPES.USER_INACTIVE)) {
toastBuilders.push({
message: formatMessage({
id: 'the_user_has_been_suspended_from_admin',
}),
intent: Intent.DANGER,
});
}
if (
errors.find((e) => e.type === ERRORS_TYPES.LOGIN_TO_MANY_ATTEMPTS)
) {
toastBuilders.push({
message: formatMessage({
id: 'your_account_has_been_locked',
}),
intent: Intent.DANGER,
});
}
toastBuilders.forEach((builder) => {
Toaster.show(builder);
});
setSubmitting(false);
});
},
});
.catch(({ response: { data: { errors } } }) => {
const toastBuilders = transformLoginErrorsToToasts(errors);
const passwordRevealerTmp = useMemo(
() => (
<span class="password-revealer" onClick={() => passwordRevealer()}>
<If condition={shown}>
<>
<Icon icon="eye-slash" />{' '}
<span class="text">
<T id={'hide'} />
</span>
</>
</If>
<If condition={!shown}>
<>
<Icon icon="eye" />{' '}
<span class="text">
<T id={'show'} />
</span>
</>
</If>
</span>
),
[shown, passwordRevealer],
);
toastBuilders.forEach((builder) => {
Toaster.show(builder);
});
setSubmitting(false);
});
};
return (
<AuthInsider>
@@ -142,59 +48,15 @@ function Login({ requestLogin }) {
</Link>
</div>
<form onSubmit={handleSubmit} className={'authentication-page__form'}>
<FormGroup
label={<T id={'email_or_phone_number'} />}
intent={errors.crediential && touched.crediential && Intent.DANGER}
helperText={
<ErrorMessage name={'crediential'} {...{ errors, touched }} />
}
className={'form-group--crediential'}
>
<InputGroup
intent={
errors.crediential && touched.crediential && Intent.DANGER
}
large={true}
{...getFieldProps('crediential')}
/>
</FormGroup>
<FormGroup
label={<T id={'password'} />}
labelInfo={passwordRevealerTmp}
intent={errors.password && touched.password && Intent.DANGER}
helperText={
<ErrorMessage name={'password'} {...{ errors, touched }} />
}
className={'form-group--password has-password-revealer'}
>
<InputGroup
large={true}
intent={errors.password && touched.password && Intent.DANGER}
type={shown ? 'text' : 'password'}
{...getFieldProps('password')}
/>
</FormGroup>
<div className={'login-form__checkbox-section'}>
<Checkbox large={true} className={'checkbox--remember-me'}>
<T id={'keep_me_logged_in'} />
</Checkbox>
</div>
<div className={'authentication-page__submit-button-wrap'}>
<Button
type={'submit'}
intent={Intent.PRIMARY}
fill={true}
lang={true}
loading={isSubmitting}
>
<T id={'log_in'} />
</Button>
</div>
</form>
<Formik
initialValues={{
crediential: '',
password: '',
}}
validationSchema={LoginSchema}
onSubmit={handleSubmit}
component={LoginForm}
/>
<div class="authentication-page__footer-links">
<Link to={'/auth/send_reset_password'}>
@@ -204,6 +66,4 @@ function Login({ requestLogin }) {
</div>
</AuthInsider>
);
}
export default compose(withAuthenticationActions)(Login);
}

View File

@@ -0,0 +1,77 @@
import React from 'react';
import {
Button,
InputGroup,
Intent,
FormGroup,
Checkbox,
} from '@blueprintjs/core';
import { Form, ErrorMessage, FastField } from 'formik';
import { FormattedMessage as T } from 'react-intl';
import { inputIntent } from 'utils';
import { PasswordRevealer } from './components';
/**
* Login form.
*/
export default function LoginForm({
isSubmitting
}) {
return (
<Form className={'authentication-page__form'}>
<FastField name={'crediential'}>
{({ form, field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'email_or_phone_number'} />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name={'crediential'} />}
className={'form-group--crediential'}
>
<InputGroup
intent={inputIntent({ error, touched })}
large={true}
{...field}
/>
</FormGroup>
)}
</FastField>
<FastField name={'password'}>
{({ form, field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'password'} />}
labelInfo={<PasswordRevealer />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name={'password'} />}
className={'form-group--password has-password-revealer'}
>
<InputGroup
large={true}
intent={inputIntent({ error, touched })}
// type={shown ? 'text' : 'password'}
{...field}
/>
</FormGroup>
)}
</FastField>
<div className={'login-form__checkbox-section'}>
<Checkbox large={true} className={'checkbox--remember-me'}>
<T id={'keep_me_logged_in'} />
</Checkbox>
</div>
<div className={'authentication-page__submit-button-wrap'}>
<Button
type={'submit'}
intent={Intent.PRIMARY}
fill={true}
lang={true}
loading={isSubmitting}
>
<T id={'log_in'} />
</Button>
</div>
</Form>
);
}

View File

@@ -1,56 +1,28 @@
import React, { useMemo, useState, useCallback } from 'react';
import * as Yup from 'yup';
import { useFormik } from 'formik';
import { Row, Col } from 'react-grid-system';
import React, { useMemo } from 'react';
import { Formik } from 'formik';
import { Link, useHistory } from 'react-router-dom';
import {
Button,
InputGroup,
Intent,
FormGroup,
Spinner,
} from '@blueprintjs/core';
import { FormattedMessage as T, useIntl } from 'react-intl';
import AppToaster from 'components/AppToaster';
import AuthInsider from 'containers/Authentication/AuthInsider';
import { useAuthLogin, useAuthRegister } from '../../hooks/query/authentication';
import ErrorMessage from 'components/ErrorMessage';
import Icon from 'components/Icon';
import { If } from 'components';
import withAuthenticationActions from 'containers/Authentication/withAuthenticationActions';
import RegisterForm from './RegisterForm';
import { RegisterSchema, transformRegisterErrorsToForm } from './utils';
import { compose } from 'utils';
function RegisterUserForm({ requestRegister, requestLogin }) {
/**
* Register form.
*/
export default function RegisterUserForm() {
const { formatMessage } = useIntl();
const history = useHistory();
const [shown, setShown] = useState(false);
const passwordRevealer = useCallback(() => {
setShown(!shown);
}, [shown]);
const ValidationSchema = Yup.object().shape({
first_name: Yup.string()
.required()
.label(formatMessage({ id: 'first_name_' })),
last_name: Yup.string()
.required()
.label(formatMessage({ id: 'last_name_' })),
email: Yup.string()
.email()
.required()
.label(formatMessage({ id: 'email' })),
phone_number: Yup.string()
.matches()
.required()
.label(formatMessage({ id: 'phone_number_' })),
password: Yup.string()
.min(4)
.required()
.label(formatMessage({ id: 'password' })),
});
const { mutateAsync: authLoginMutate } = useAuthLogin();
const { mutateAsync: authRegisterMutate } = useAuthRegister();
const initialValues = useMemo(
() => ({
first_name: '',
@@ -58,85 +30,37 @@ function RegisterUserForm({ requestRegister, requestLogin }) {
email: '',
phone_number: '',
password: '',
country: 'LY',
}),
[],
);
const {
errors,
touched,
handleSubmit,
getFieldProps,
isSubmitting,
} = useFormik({
enableReinitialize: true,
validationSchema: ValidationSchema,
initialValues: {
...initialValues,
country: 'LY',
},
onSubmit: (values, { setSubmitting, setErrors }) => {
requestRegister(values)
.then((response) => {
requestLogin({
crediential: values.email,
password: values.password,
})
.then(() => {
history.push('/register/subscription');
setSubmitting(false);
})
.catch((errors) => {
AppToaster.show({
message: formatMessage({ id: 'something_wentwrong' }),
intent: Intent.SUCCESS,
});
});
const handleSubmit = (values, { setSubmitting, setErrors }) => {
authRegisterMutate(values)
.then((response) => {
authLoginMutate({
crediential: values.email,
password: values.password,
})
.catch((errors) => {
if (errors.some((e) => e.type === 'PHONE_NUMBER_EXISTS')) {
setErrors({
phone_number: formatMessage({
id: 'the_phone_number_already_used_in_another_account',
}),
.then(() => {
history.push('/register/subscription');
setSubmitting(false);
})
.catch(({ response: { data: { errors } } }) => {
AppToaster.show({
message: formatMessage({ id: 'something_wentwrong' }),
intent: Intent.SUCCESS,
});
}
if (errors.some((e) => e.type === 'EMAIL.EXISTS')) {
setErrors({
email: formatMessage({
id: 'the_email_already_used_in_another_account',
}),
});
}
setSubmitting(false);
});
},
});
const passwordRevealerTmp = useMemo(
() => (
<span class="password-revealer" onClick={() => passwordRevealer()}>
<If condition={shown}>
<>
<Icon icon="eye-slash" />{' '}
<span class="text">
<T id={'hide'} />
</span>
</>
</If>
<If condition={!shown}>
<>
<Icon icon="eye" />{' '}
<span class="text">
<T id={'show'} />
</span>
</>
</If>
</span>
),
[shown, passwordRevealer],
);
});
})
.catch(({ response: { data: { errors } } }) => {
const formErrors = transformRegisterErrorsToForm(errors);
setErrors(formErrors);
setSubmitting(false);
});
};
return (
<AuthInsider>
<div className={'register-form'}>
@@ -146,136 +70,17 @@ function RegisterUserForm({ requestRegister, requestLogin }) {
</h3>
<T id={'you_have_a_bigcapital_account'} />
<Link to="/auth/login">
{' '}
<T id={'login'} />
</Link>
</div>
<form onSubmit={handleSubmit} className={'authentication-page__form'}>
<Row className={'name-section'}>
<Col md={6}>
<FormGroup
label={<T id={'first_name'} />}
intent={
errors.first_name && touched.first_name && Intent.DANGER
}
helperText={
<ErrorMessage name={'first_name'} {...{ errors, touched }} />
}
className={'form-group--first-name'}
>
<InputGroup
intent={
errors.first_name && touched.first_name && Intent.DANGER
}
{...getFieldProps('first_name')}
/>
</FormGroup>
</Col>
<Col md={6}>
<FormGroup
label={<T id={'last_name'} />}
intent={errors.last_name && touched.last_name && Intent.DANGER}
helperText={
<ErrorMessage name={'last_name'} {...{ errors, touched }} />
}
className={'form-group--last-name'}
>
<InputGroup
intent={
errors.last_name && touched.last_name && Intent.DANGER
}
{...getFieldProps('last_name')}
/>
</FormGroup>
</Col>
</Row>
<FormGroup
label={<T id={'phone_number'} />}
intent={
errors.phone_number && touched.phone_number && Intent.DANGER
}
helperText={
<ErrorMessage name={'phone_number'} {...{ errors, touched }} />
}
className={'form-group--phone-number'}
>
<InputGroup
intent={
errors.phone_number && touched.phone_number && Intent.DANGER
}
{...getFieldProps('phone_number')}
/>
</FormGroup>
<FormGroup
label={<T id={'email'} />}
intent={errors.email && touched.email && Intent.DANGER}
helperText={
<ErrorMessage name={'email'} {...{ errors, touched }} />
}
className={'form-group--email'}
>
<InputGroup
intent={errors.email && touched.email && Intent.DANGER}
{...getFieldProps('email')}
/>
</FormGroup>
<FormGroup
label={<T id={'password'} />}
labelInfo={passwordRevealerTmp}
intent={errors.password && touched.password && Intent.DANGER}
helperText={
<ErrorMessage name={'password'} {...{ errors, touched }} />
}
className={'form-group--password has-password-revealer'}
>
<InputGroup
lang={true}
type={shown ? 'text' : 'password'}
intent={errors.password && touched.password && Intent.DANGER}
{...getFieldProps('password')}
/>
</FormGroup>
<div className={'register-form__agreement-section'}>
<p>
<T id={'signing_in_or_creating'} /> <br />
<Link>
<T id={'terms_conditions'} />
</Link>{' '}
<T id={'and'} />
<Link>
{' '}
<T id={'privacy_statement'} />
</Link>
</p>
</div>
<div className={'authentication-page__submit-button-wrap'}>
<Button
className={'btn-register'}
intent={Intent.PRIMARY}
type="submit"
fill={true}
loading={isSubmitting}
>
<T id={'register'} />
</Button>
</div>
</form>
<If condition={isSubmitting}>
<div class="authentication-page__loading-overlay">
<Spinner size={50} />
</div>
</If>
<Formik
initialValues={initialValues}
validationSchema={RegisterSchema}
onSubmit={handleSubmit}
component={RegisterForm}
/>
</div>
</AuthInsider>
);
}
export default compose(withAuthenticationActions)(RegisterUserForm);
}

View File

@@ -0,0 +1,141 @@
import React from 'react';
import {
Button,
InputGroup,
Intent,
FormGroup,
Spinner
} from '@blueprintjs/core';
import { ErrorMessage, FastField, Form } from 'formik';
import { FormattedMessage as T } from 'react-intl';
import { Link } from 'react-router-dom';
import { Row, Col, If } from 'components';
import { inputIntent } from 'utils';
/**
* Register form.
*/
export default function RegisterForm({
isSubmitting,
}) {
return (
<Form className={'authentication-page__form'}>
<Row className={'name-section'}>
<Col md={6}>
<FastField name={'first_name'}>
{({ form, field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'first_name'} />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name={'first_name'} />}
className={'form-group--first-name'}
>
<InputGroup
intent={inputIntent({ error, touched })}
{...field}
/>
</FormGroup>
)}
</FastField>
</Col>
<Col md={6}>
<FastField name={'last_name'}>
{({ form, field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'last_name'} />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name={'last_name'} />}
className={'form-group--last-name'}
>
<InputGroup
intent={inputIntent({ error, touched })}
{...field}
/>
</FormGroup>
)}
</FastField>
</Col>
</Row>
<FastField name={'phone_number'}>
{({ form, field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'phone_number'} />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name={'phone_number'} />}
className={'form-group--phone-number'}
>
<InputGroup intent={inputIntent({ error, touched })} {...field} />
</FormGroup>
)}
</FastField>
<FastField name={'email'}>
{({ form, field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'email'} />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name={'email'} />}
className={'form-group--email'}
>
<InputGroup intent={inputIntent({ error, touched })} {...field} />
</FormGroup>
)}
</FastField>
<FastField name={'password'}>
{({ form, field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'password'} />}
// labelInfo={passwordRevealerTmp}
intent={inputIntent({ error, touched })}
helperText={
<ErrorMessage name={'password'} />
}
className={'form-group--password has-password-revealer'}
>
<InputGroup
lang={true}
// type={shown ? 'text' : 'password'}
intent={inputIntent({ error, touched })}
{...field}
/>
</FormGroup>
)}
</FastField>
<div className={'register-form__agreement-section'}>
<p>
<T id={'signing_in_or_creating'} /> <br />
<Link>
<T id={'terms_conditions'} />
</Link>{' '}
<T id={'and'} />
<Link>
{' '}
<T id={'privacy_statement'} />
</Link>
</p>
</div>
<div className={'authentication-page__submit-button-wrap'}>
<Button
className={'btn-register'}
intent={Intent.PRIMARY}
type="submit"
fill={true}
loading={isSubmitting}
>
<T id={'register'} />
</Button>
</div>
<If condition={isSubmitting}>
<div class="authentication-page__loading-overlay">
<Spinner size={50} />
</div>
</If>
</Form>
);
}

View File

@@ -1,40 +1,31 @@
import React, { useMemo } from 'react';
import * as Yup from 'yup';
import { useFormik } from 'formik';
import { Formik } from 'formik';
import {
Button,
InputGroup,
Intent,
FormGroup,
Position,
} from '@blueprintjs/core';
import { Link, useParams, useHistory } from 'react-router-dom';
import { FormattedMessage as T, useIntl } from 'react-intl';
import { useAuthResetPassword } from 'hooks/query';
import AppToaster from 'components/AppToaster';
import ErrorMessage from 'components/ErrorMessage';
import AuthInsider from 'containers/Authentication/AuthInsider';
import withAuthenticationActions from './withAuthenticationActions';
import { compose } from 'utils';
function ResetPassword({ requestResetPassword }) {
import ResetPasswordForm from './ResetPasswordForm';
import { ResetPasswordSchema } from './utils';
/**
* Reset password page.
*/
export default function ResetPassword() {
const { formatMessage } = useIntl();
const { token } = useParams();
const history = useHistory();
const ValidationSchema = Yup.object().shape({
password: Yup.string()
.min(4)
.required()
.label(formatMessage({ id: 'password' })),
confirm_password: Yup.string()
.oneOf([Yup.ref('password'), null])
.required()
.label(formatMessage({ id: 'confirm_password' })),
});
// Authentication reset password.
const { mutateAsync: authResetPasswordMutate } = useAuthResetPassword();
// Initial values of the form.
const initialValues = useMemo(
() => ({
password: '',
@@ -43,42 +34,30 @@ function ResetPassword({ requestResetPassword }) {
[],
);
const {
touched,
errors,
handleSubmit,
getFieldProps,
isSubmitting,
} = useFormik({
enableReinitialize: true,
validationSchema: ValidationSchema,
initialValues: {
...initialValues,
},
onSubmit: (values, { setSubmitting }) => {
requestResetPassword(values, token)
.then((response) => {
// Handle the form submitting.
const handleSubmit = (values, { setSubmitting }) => {
authResetPasswordMutate([token, values])
.then((response) => {
AppToaster.show({
message: formatMessage('password_successfully_updated'),
intent: Intent.DANGER,
position: Position.BOTTOM,
});
history.push('/auth/login');
setSubmitting(false);
})
.catch(({ response: { data: { errors } } }) => {
if (errors.find((e) => e.type === 'TOKEN_INVALID')) {
AppToaster.show({
message: formatMessage('password_successfully_updated'),
message: formatMessage({ id: 'an_unexpected_error_occurred' }),
intent: Intent.DANGER,
position: Position.BOTTOM,
});
history.push('/auth/login');
setSubmitting(false);
})
.catch((errors) => {
if (errors.find((e) => e.type === 'TOKEN_INVALID')) {
AppToaster.show({
message: formatMessage('an_unexpected_error_occurred'),
intent: Intent.DANGER,
position: Position.BOTTOM,
});
history.push('/auth/login');
}
setSubmitting(false);
});
},
});
}
setSubmitting(false);
});
};
return (
<AuthInsider>
@@ -93,66 +72,13 @@ function ResetPassword({ requestResetPassword }) {
</Link>
</div>
<form onSubmit={handleSubmit}>
<FormGroup
label={<T id={'new_password'} />}
intent={errors.password && touched.password && Intent.DANGER}
helperText={
<ErrorMessage name={'password'} {...{ errors, touched }} />
}
className={'form-group--password'}
>
<InputGroup
lang={true}
type={'password'}
intent={errors.password && touched.password && Intent.DANGER}
{...getFieldProps('password')}
/>
</FormGroup>
<FormGroup
label={<T id={'new_password'} />}
labelInfo={'(again):'}
intent={
errors.confirm_password &&
touched.confirm_password &&
Intent.DANGER
}
helperText={
<ErrorMessage
name={'confirm_password'}
{...{ errors, touched }}
/>
}
className={'form-group--confirm-password'}
>
<InputGroup
lang={true}
type={'password'}
intent={
errors.confirm_password &&
touched.confirm_password &&
Intent.DANGER
}
{...getFieldProps('confirm_password')}
/>
</FormGroup>
<div className={'authentication-page__submit-button-wrap'}>
<Button
fill={true}
className={'btn-new'}
intent={Intent.PRIMARY}
type="submit"
loading={isSubmitting}
>
<T id={'submit'} />
</Button>
</div>
</form>
<Formik
initialValues={initialValues}
validationSchema={ResetPasswordSchema}
onSubmit={handleSubmit}
component={ResetPasswordForm}
/>
</div>
</AuthInsider>
);
}
export default compose(withAuthenticationActions)(ResetPassword);
}

View File

@@ -0,0 +1,63 @@
import React from 'react';
import { Button, InputGroup, Intent, FormGroup } from '@blueprintjs/core';
import { Form, ErrorMessage, FastField } from 'formik';
import { FormattedMessage as T } from 'react-intl';
import { inputIntent } from 'utils';
/**
* Reset password form.
*/
export default function ResetPasswordForm({ isSubmitting }) {
return (
<Form>
<FastField name={'password'}>
{({ form, field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'new_password'} />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name={'password'} />}
className={'form-group--password'}
>
<InputGroup
lang={true}
type={'password'}
intent={inputIntent({ error, touched })}
{...field}
/>
</FormGroup>
)}
</FastField>
<FastField name={'confirm_password'}>
{({ form, field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'new_password'} />}
labelInfo={'(again):'}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name={'confirm_password'} />}
className={'form-group--confirm-password'}
>
<InputGroup
lang={true}
type={'password'}
intent={inputIntent({ error, touched })}
{...field}
/>
</FormGroup>
)}
</FastField>
<div className={'authentication-page__submit-button-wrap'}>
<Button
fill={true}
className={'btn-new'}
intent={Intent.PRIMARY}
type="submit"
loading={isSubmitting}
>
<T id={'submit'} />
</Button>
</div>
</Form>
);
}

View File

@@ -1,75 +1,60 @@
import React, { useMemo } from 'react';
import * as Yup from 'yup';
import { useFormik } from 'formik';
import { Formik } from 'formik';
import { FormattedMessage as T, useIntl } from 'react-intl';
import { Link, useHistory } from 'react-router-dom';
import { Button, InputGroup, Intent, FormGroup } from '@blueprintjs/core';
import { Intent } from '@blueprintjs/core';
import { compose } from 'utils';
import { useAuthSendResetPassword } from 'hooks/query';
import Toaster from 'components/AppToaster';
import SendResetPasswordForm from './SendResetPasswordForm';
import { SendResetPasswordSchema, transformSendResetPassErrorsToToasts } from './utils';
import AppToaster from 'components/AppToaster';
import ErrorMessage from 'components/ErrorMessage';
import AuthInsider from 'containers/Authentication/AuthInsider';
import withAuthenticationActions from './withAuthenticationActions';
function SendResetPassword({ requestSendResetPassword }) {
/**
* Send reset password page.
*/
export default function SendResetPassword({ requestSendResetPassword }) {
const { formatMessage } = useIntl();
const history = useHistory();
// Validation schema.
const ValidationSchema = Yup.object().shape({
crediential: Yup.string()
.required()
.email().label(formatMessage({ id: 'email' })),
});
const { mutateAsync: sendResetPasswordMutate } = useAuthSendResetPassword();
// Initial values.
const initialValues = useMemo(
() => ({
crediential: '',
}),
[]
[],
);
// Formik validation
const {
errors,
touched,
handleSubmit,
getFieldProps,
isSubmitting,
} = useFormik({
enableReinitialize: true,
validationSchema: ValidationSchema,
initialValues: {
...initialValues,
},
onSubmit: (values, { setSubmitting }) => {
requestSendResetPassword(values.crediential)
.then((response) => {
AppToaster.show({
message: formatMessage({id:'check_your_email_for_a_link_to_reset'}),
intent: Intent.SUCCESS,
});
history.push('/auth/login');
setSubmitting(false);
})
.catch((errors) => {
if (errors.find((e) => e.type === 'EMAIL.NOT.REGISTERED')) {
AppToaster.show({
message: formatMessage({id:'we_couldn_t_find_your_account_with_that_email'}),
intent: Intent.DANGER,
});
}
setSubmitting(false);
// Handle form submitting.
const handleSubmit = (values, { setSubmitting }) => {
sendResetPasswordMutate({ email: values.crediential })
.then((response) => {
AppToaster.show({
message: formatMessage({
id: 'check_your_email_for_a_link_to_reset',
}),
intent: Intent.SUCCESS,
});
},
});
history.push('/auth/login');
setSubmitting(false);
})
.catch(({ response: { data: { errors } } }) => {
const toastBuilders = transformSendResetPassErrorsToToasts(errors);
toastBuilders.forEach((builder) => {
Toaster.show(builder);
});
setSubmitting(false);
});
};
return (
<AuthInsider>
<div className='reset-form'>
<div className="reset-form">
<div className={'authentication-page__label-section'}>
<h3>
<T id={'you_can_t_login'} />
@@ -79,38 +64,14 @@ function SendResetPassword({ requestSendResetPassword }) {
</p>
</div>
<form onSubmit={handleSubmit} className={'send-reset-password'}>
<FormGroup
label={'Email or Phone Number'}
intent={errors.crediential && touched.crediential && Intent.DANGER}
helperText={
<ErrorMessage name={'crediential'} {...{ errors, touched }} />
}
className={'form-group--crediential'}
>
<InputGroup
intent={
errors.crediential && touched.crediential && Intent.DANGER
}
large={true}
{...getFieldProps('crediential')}
/>
</FormGroup>
<div className={'authentication-page__submit-button-wrap'}>
<Button
type={'submit'}
intent={Intent.PRIMARY}
fill={true}
loading={isSubmitting}
>
<T id={'send_reset_password_mail'} />
</Button>
</div>
</form>
<div class='authentication-page__footer-links'>
<Link to='/auth/login'>
<Formik
initialValues={initialValues}
onSubmit={handleSubmit}
validationSchema={SendResetPasswordSchema}
component={SendResetPasswordForm}
/>
<div class="authentication-page__footer-links">
<Link to="/auth/login">
<T id={'return_to_log_in'} />
</Link>
</div>
@@ -118,5 +79,3 @@ function SendResetPassword({ requestSendResetPassword }) {
</AuthInsider>
);
}
export default compose(withAuthenticationActions)(SendResetPassword);

View File

@@ -0,0 +1,49 @@
import React from 'react';
import {
Button,
InputGroup,
Intent,
FormGroup,
} from '@blueprintjs/core';
import { Form, ErrorMessage, FastField } from 'formik';
import { FormattedMessage as T } from 'react-intl';
import { inputIntent } from 'utils';
/**
* Send reset password form.
*/
export default function SendResetPasswordForm({
isSubmitting
}) {
return (
<Form className={'send-reset-password'}>
<FastField name={'crediential'}>
{({ form, field, meta: { error, touched } }) => (
<FormGroup
label={'Email or Phone Number'}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name={'crediential'} />}
className={'form-group--crediential'}
>
<InputGroup
intent={inputIntent({ error, touched })}
large={true}
{...field}
/>
</FormGroup>
)}
</FastField>
<div className={'authentication-page__submit-button-wrap'}>
<Button
type={'submit'}
intent={Intent.PRIMARY}
fill={true}
loading={isSubmitting}
>
<T id={'send_reset_password_mail'} />
</Button>
</div>
</Form>
);
}

View File

@@ -0,0 +1,49 @@
import React from 'react';
import { FormattedMessage as T } from 'react-intl';
import ContentLoader from 'react-content-loader';
import { If, Icon } from 'components';
export function PasswordRevealer({ shown, onClick }) {
return (
<span class="password-revealer" onClick={onClick}>
<If condition={shown}>
<Icon icon="eye-slash" />{' '}
<span class="text">
<T id={'hide'} />
</span>
</If>
<If condition={!shown}>
<Icon icon="eye" />{' '}
<span class="text">
<T id={'show'} />
</span>
</If>
</span>
);
}
/**
* Invite accept loading space.
*/
export function InviteAcceptLoading({ isLoading, children, ...props }) {
return isLoading ? (
<ContentLoader
speed={2}
width={400}
height={280}
viewBox="0 0 400 280"
backgroundColor="#f3f3f3"
foregroundColor="#e6e6e6"
{...props}
>
<rect x="0" y="80" rx="2" ry="2" width="200" height="20" />
<rect x="0" y="0" rx="2" ry="2" width="250" height="30" />
<rect x="0" y="38" rx="2" ry="2" width="300" height="15" />
<rect x="0" y="175" rx="2" ry="2" width="200" height="20" />
<rect x="1" y="205" rx="2" ry="2" width="385" height="38" />
<rect x="0" y="110" rx="2" ry="2" width="385" height="38" />
</ContentLoader>
) : (
children
);
}

View File

@@ -0,0 +1,145 @@
import * as Yup from 'yup';
import { Intent } from '@blueprintjs/core';
import { formatMessage } from 'services/intl';
export const LOGIN_ERRORS = {
INVALID_DETAILS: 'INVALID_DETAILS',
USER_INACTIVE: 'USER_INACTIVE',
LOGIN_TO_MANY_ATTEMPTS: 'LOGIN_TO_MANY_ATTEMPTS',
};
const REGISTER_ERRORS = {
PHONE_NUMBER_EXISTS: 'PHONE_NUMBER_EXISTS',
EMAIL_EXISTS: 'EMAIL.EXISTS',
};
export const LoginSchema = Yup.object().shape({
crediential: Yup.string()
.required()
.email()
.label(formatMessage({ id: 'email' })),
password: Yup.string()
.required()
.min(4)
.label(formatMessage({ id: 'password' })),
});
export const RegisterSchema = Yup.object().shape({
first_name: Yup.string()
.required()
.label(formatMessage({ id: 'first_name_' })),
last_name: Yup.string()
.required()
.label(formatMessage({ id: 'last_name_' })),
email: Yup.string()
.email()
.required()
.label(formatMessage({ id: 'email' })),
phone_number: Yup.string()
.matches()
.required()
.label(formatMessage({ id: 'phone_number_' })),
password: Yup.string()
.min(4)
.required()
.label(formatMessage({ id: 'password' })),
});
export const ResetPasswordSchema = Yup.object().shape({
password: Yup.string()
.min(4)
.required()
.label(formatMessage({ id: 'password' })),
confirm_password: Yup.string()
.oneOf([Yup.ref('password'), null])
.required()
.label(formatMessage({ id: 'confirm_password' })),
});
// Validation schema.
export const SendResetPasswordSchema = Yup.object().shape({
crediential: Yup.string()
.required()
.email()
.label(formatMessage({ id: 'email' })),
});
export const InviteAcceptSchema = Yup.object().shape({
first_name: Yup.string()
.required()
.label(formatMessage({ id: 'first_name_' })),
last_name: Yup.string()
.required()
.label(formatMessage({ id: 'last_name_' })),
phone_number: Yup.string()
.matches()
.required()
.label(formatMessage({ id: 'phone_number' })),
password: Yup.string()
.min(4)
.required()
.label(formatMessage({ id: 'password' })),
});
export const transformSendResetPassErrorsToToasts = (errors) => {
const toastBuilders = [];
if (errors.find((e) => e.type === 'EMAIL.NOT.REGISTERED')) {
toastBuilders.push({
message: formatMessage({
id: 'we_couldn_t_find_your_account_with_that_email',
}),
intent: Intent.DANGER,
});
}
return toastBuilders;
}
export const transformLoginErrorsToToasts = (errors) => {
const toastBuilders = [];
if (errors.find((e) => e.type === LOGIN_ERRORS.INVALID_DETAILS)) {
toastBuilders.push({
message: formatMessage({
id: 'email_and_password_entered_did_not_match',
}),
intent: Intent.DANGER,
});
}
if (errors.find((e) => e.type === LOGIN_ERRORS.USER_INACTIVE)) {
toastBuilders.push({
message: formatMessage({
id: 'the_user_has_been_suspended_from_admin',
}),
intent: Intent.DANGER,
});
}
if (
errors.find((e) => e.type === LOGIN_ERRORS.LOGIN_TO_MANY_ATTEMPTS)
) {
toastBuilders.push({
message: formatMessage({
id: 'your_account_has_been_locked',
}),
intent: Intent.DANGER,
});
}
return toastBuilders;
}
export const transformRegisterErrorsToForm = (errors) => {
const formErrors = {};
if (errors.some((e) => e.type === REGISTER_ERRORS.PHONE_NUMBER_EXISTS)) {
formErrors.phone_number = formatMessage({
id: 'the_phone_number_already_used_in_another_account',
});
}
if (errors.some((e) => e.type === REGISTER_ERRORS.EMAIL_EXISTS)) {
formErrors.email = formatMessage({
id: 'the_email_already_used_in_another_account',
});
}
return formErrors;
}

View File

@@ -1,22 +1,13 @@
import {
login,
resetPassword,
sendResetPassword,
inviteAccept,
register,
inviteMetaByToken,
} from 'store/authentication/authentication.actions';
import { connect } from 'react-redux';
import t from 'store/types';
const mapDispatchToProps = (dispatch) => ({
requestLogin: (form) => dispatch(login({ form })),
requestLogout: () => dispatch({ type: t.LOGOUT }),
requestRegister: (form) => dispatch(register({ form })),
requestSendResetPassword: (email) => dispatch(sendResetPassword({ email })),
requestResetPassword: (form, token) => dispatch(resetPassword({ form, token })),
requestInviteAccept: (form, token) => dispatch(inviteAccept({ form, token })),
requestInviteMetaByToken: (token) => dispatch(inviteMetaByToken({ token })),
// requestLogin: (form) => dispatch(login({ form })),
// requestLogout: () => dispatch({ type: t.LOGOUT }),
// requestRegister: (form) => dispatch(register({ form })),
// requestSendResetPassword: (email) => dispatch(sendResetPassword({ email })),
// requestResetPassword: (form, token) => dispatch(resetPassword({ form, token })),
// requestInviteAccept: (form, token) => dispatch(inviteAccept({ form, token })),
// requestInviteMetaByToken: (token) => dispatch(inviteMetaByToken({ token })),
});
export default connect(null, mapDispatchToProps);

View File

@@ -1,9 +1,8 @@
import React, { useMemo } from 'react';
import * as Yup from 'yup';
import { useFormik } from 'formik';
import { Row, Col } from 'react-grid-system';
import { Row, Col, ErrorMessage } from 'components';
import { FormattedMessage as T } from 'react-intl';
import { ErrorMessage } from 'components';
import {
Button,
Classes,

View File

@@ -1,14 +1,9 @@
import React, { useCallback } from 'react';
import React from 'react';
import { Icon, For } from 'components';
import { FormattedMessage as T } from 'react-intl';
import withAuthenticationActions from 'containers/Authentication/withAuthenticationActions';
import withAuthentication from 'containers/Authentication/withAuthentication';
import footerLinks from 'config/footerLinks';
import { compose } from 'utils';
import { useAuthActions, useAuthOrganizationId } from 'hooks/state';
function FooterLinkItem({ title, link }) {
return (
@@ -21,16 +16,13 @@ function FooterLinkItem({ title, link }) {
/**
* Wizard setup left section.
*/
function SetupLeftSection({
// #withAuthenticationActions
requestLogout,
export default function SetupLeftSection() {
const { setLogout } = useAuthActions();
const organizationId = useAuthOrganizationId();
// #withAuthentication
currentOrganizationId
}) {
const onClickLogout = useCallback(() => {
requestLogout();
}, [requestLogout]);
const onClickLogout = () => {
setLogout();
};
return (
<section className={'setup-page__left-section'}>
@@ -50,7 +42,7 @@ function SetupLeftSection({
<div className={'content__organization'}>
<span class="organization-id">
Oragnization ID: <span class="id">{ currentOrganizationId }</span>,
Oragnization ID: <span class="id">{ organizationId }</span>,
</span>
<br />
<span class="signout">
@@ -70,9 +62,4 @@ function SetupLeftSection({
</div>
</section>
)
}
export default compose(
withAuthenticationActions,
withAuthentication(({ currentOrganizationId }) => ({ currentOrganizationId })),
)(SetupLeftSection);
}

View File

@@ -14,7 +14,7 @@ export default function BillingPlansForm() {
return (
<div class="billing-plans">
<BillingPlansInput
title={<T id={'sele ct_a_plan'} values={{ order: 1 }} />}
title={<T id={'select_a_plan'} values={{ order: 1 }} />}
description={<T id={'please_enter_your_preferred_payment_method'} />}
/>
<BillingPeriodsInput