WIP / Design login and register page.

This commit is contained in:
elforjani3
2020-04-29 17:36:02 +02:00
parent 6d0ad42582
commit b8b8283385
11 changed files with 1050 additions and 447 deletions

View File

@@ -1,5 +1,5 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { submitResetPassword } from 'store/resetPassword/resetPassword.action'; import { submitResetPassword } from 'store/authentication/authentication.actions';
export const mapStateToProps = (state, props) => { export const mapStateToProps = (state, props) => {
return {}; return {};
@@ -7,7 +7,7 @@ export const mapStateToProps = (state, props) => {
export const mapDispatchToProps = (dispatch) => ({ export const mapDispatchToProps = (dispatch) => ({
requestResetPassword: (password) => requestResetPassword: (password) =>
dispatch(submitResetPassword({password})), dispatch(submitResetPassword({ password })),
}); });
export default connect(mapStateToProps, mapDispatchToProps); export default connect(mapStateToProps, mapDispatchToProps);

View File

@@ -0,0 +1,13 @@
import { connect } from 'react-redux';
import { submitSendResetPassword } from 'store/authentication/authentication.actions';
export const mapStateToProps = (state, props) => {
return {};
};
export const mapDispatchToProps = (dispatch) => ({
requestSendResetPassword: (email) =>
dispatch(submitSendResetPassword({ email })),
});
export default connect(mapStateToProps, mapDispatchToProps);

View File

@@ -1,4 +1,4 @@
import React, { useEffect, useMemo } from 'react'; import React, { useEffect, useMemo, useState } from 'react';
import * as Yup from 'yup'; import * as Yup from 'yup';
import { useFormik } from 'formik'; import { useFormik } from 'formik';
import { useIntl } from 'react-intl'; import { useIntl } from 'react-intl';
@@ -13,8 +13,14 @@ import {
Intent, Intent,
FormGroup, FormGroup,
HTMLSelect, HTMLSelect,
Icon,
} from '@blueprintjs/core'; } from '@blueprintjs/core';
import { Row, Col } from 'react-grid-system';
import IconLog from 'components/Icon';
import Copyright from './copyright';
import { Link } from 'react-router-dom';
function Invite({ requestSubmitInvite }) { function Invite({ requestSubmitInvite }) {
const intl = useIntl(); const intl = useIntl();
let params = useParams('accept/:token'); let params = useParams('accept/:token');
@@ -23,18 +29,12 @@ function Invite({ requestSubmitInvite }) {
const phoneRegExp = /^((\\+[1-9]{1,4}[ \\-]*)|(\\([0-9]{2,3}\\)[ \\-]*)|([0-9]{2,4})[ \\-]*)*?[0-9]{3,4}?[ \\-]*[0-9]{3,4}?$/; const phoneRegExp = /^((\\+[1-9]{1,4}[ \\-]*)|(\\([0-9]{2,3}\\)[ \\-]*)|([0-9]{2,4})[ \\-]*)*?[0-9]{3,4}?[ \\-]*[0-9]{3,4}?$/;
const language = useMemo(() => [
{ value: null, label: 'Select Country' },
{ value: 'Arabic', label: 'Arabic' },
{ value: 'English', label: 'English' },
], []);
const ValidationSchema = Yup.object().shape({ const ValidationSchema = Yup.object().shape({
first_name: Yup.string().required(), first_name: Yup.string().required(),
last_name: Yup.string().required(), last_name: Yup.string().required(),
email: Yup.string().email().required(), email: Yup.string().email().required(),
phone_number: Yup.string().matches(phoneRegExp).required(), phone_number: Yup.string().matches(phoneRegExp).required(),
language: Yup.string().required(),
password: Yup.string() password: Yup.string()
.min(4, 'Password has to be longer than 4 characters!') .min(4, 'Password has to be longer than 4 characters!')
.required('Password is required!'), .required('Password is required!'),
@@ -51,122 +51,175 @@ function Invite({ requestSubmitInvite }) {
}), }),
[] []
); );
const { const formik = useFormik({
handleSubmit,
errors,
values,
touched,
getFieldProps,
} = useFormik({
enableReinitialize: true, enableReinitialize: true,
validationSchema: ValidationSchema, validationSchema: ValidationSchema,
initialValues: { initialValues: {
...initialValues, ...initialValues,
}, },
onSubmit: (values, { setSubmitting }) => { onSubmit: (values, { setSubmitting }) => {
requestSubmitInvite(values, token).then((response) => { requestSubmitInvite(values, token)
AppToaster.show({ .then((response) => {
message: 'success', AppToaster.show({
message: 'success',
});
setSubmitting(false);
})
.catch((error) => {
setSubmitting(false);
}); });
setSubmitting(false);
})
.catch((error) => {
setSubmitting(false);
});
}, },
}); });
const requiredSpan = useMemo(() => <span class='required'>*</span>, []); const requiredSpan = useMemo(() => <span class='required'>*</span>, []);
const [shown, setShown] = useState(false);
const passwordRevealer = () => {
setShown(!shown);
};
return ( return (
<div className={'invite-form'}> <div className={'invite-form'}>
<form onSubmit={handleSubmit}> <IconLog
<FormGroup className={'invite-form__icon-section'}
label={'First Name'} icon='bigcapital'
labelInfo={requiredSpan} iconSize={150}
className={'form-group--first_name'} />
intent={errors.first_name && touched.first_name && Intent.DANGER} <form onSubmit={formik.handleSubmit}>
helperText={<ErrorMessage name={'first_name'} />} <div className={'invite-form__label-section'}>
> <h3>Welcome to Bigcapital</h3>
<InputGroup <p>
intent={errors.first_name && touched.first_name && Intent.DANGER} Enter your personal information <b>{'Organization Name'}</b>{' '}
{...getFieldProps('first_name')} Organization.
/> </p>
</FormGroup> </div>
<FormGroup <Row>
label={'Last Name'} <Col md={6}>
labelInfo={requiredSpan} <FormGroup
className={'form-group--last_name'} label={'First Name'}
intent={errors.last_name && touched.last_name && Intent.DANGER} className={'form-group--first_name'}
helperText={<ErrorMessage name={'last_name'} />} intent={
> formik.errors.first_name &&
<InputGroup formik.touched.first_name &&
intent={errors.last_name && touched.last_name && Intent.DANGER} Intent.DANGER
{...getFieldProps('last_name')} }
/> helperText={<ErrorMessage name={'first_name'} {...formik} />}
</FormGroup> >
<InputGroup
intent={
formik.errors.first_name &&
formik.touched.first_name &&
Intent.DANGER
}
{...formik.getFieldProps('first_name')}
/>
</FormGroup>
</Col>
<Col md={6}>
<FormGroup
label={'Last Name'}
className={'form-group--last_name'}
intent={
formik.errors.last_name &&
formik.touched.last_name &&
Intent.DANGER
}
helperText={<ErrorMessage name={'last_name'} {...formik} />}
>
<InputGroup
intent={
formik.errors.last_name &&
formik.touched.last_name &&
Intent.DANGER
}
{...formik.getFieldProps('last_name')}
/>
</FormGroup>
</Col>
</Row>
<div>
<FormGroup
label={'Phone Number'}
className={'form-group--phone_number'}
intent={
formik.errors.phone_number &&
formik.touched.phone_number &&
Intent.DANGER
}
helperText={<ErrorMessage name={'phone_number'} {...formik} />}
>
<InputGroup
intent={
formik.errors.phone_number &&
formik.touched.phone_number &&
Intent.DANGER
}
{...formik.getFieldProps('phone_number')}
/>
</FormGroup>
</div>
<FormGroup {/* <FormGroup
label={'Phone Number'}
labelInfo={requiredSpan}
className={'form-group--phone_number'}
intent={errors.phone_number && touched.phone_number && Intent.DANGER}
helperText={<ErrorMessage name={'phone_number'} />}
>
<InputGroup
intent={(errors.phone_number && touched.phone_number) && Intent.DANGER}
{...getFieldProps('phone_number')}
/>
</FormGroup>
<FormGroup
label={'Language'}
labelInfo={requiredSpan}
className={'form-group--language'}
intent={(errors.language && touched.language) && Intent.DANGER}
helperText={<ErrorMessage name={'language'} />}
>
<HTMLSelect
fill={true}
options={language}
{...getFieldProps('language')}
/>
</FormGroup>
<FormGroup
label={'Email'} label={'Email'}
labelInfo={requiredSpan}
className={'form-group--email'} className={'form-group--email'}
intent={(errors.email && touched.email) && Intent.DANGER} intent={errors.email && touched.email && Intent.DANGER}
helperText={<ErrorMessage name={'email'} />} helperText={<ErrorMessage name={'email'} />}
> >
<InputGroup <InputGroup
intent={errors.email && touched.email && Intent.DANGER} intent={errors.email && touched.email && Intent.DANGER}
{...getFieldProps('email')} {...getFieldProps('email')}
/> />
</FormGroup> </FormGroup> */}
<div>
<FormGroup
label={'Password'}
labelInfo={
<span onClick={() => passwordRevealer()}>
<Icon icon='eye-open' />
Show
</span>
}
className={'form-group--password'}
intent={
formik.errors.password && formik.touched.password && Intent.DANGER
}
helperText={<ErrorMessage name={'password'} {...formik} />}
>
<InputGroup
lang={true}
type={shown ? 'text' : 'password'}
intent={
formik.errors.password &&
formik.touched.password &&
Intent.DANGER
}
{...formik.getFieldProps('password')}
/>
</FormGroup>
</div>
<FormGroup <div className={'invite-form__statement-section'}>
label={'Password'} <p>
labelInfo={requiredSpan} You email address is <b>{'xxx@gamil.com'},</b> <br />
className={'form-group--password'} You will use this address to sign in to Bigcapital.
intent={(errors.password && touched.password) && Intent.DANGER} </p>
helperText={<ErrorMessage name={'password'} />} <p>
> By signing in or creating an account, you agree with our <br />
<InputGroup <Link>Terms & Conditions</Link> and <Link> Privacy Statement</Link>
lang={true} </p>
type={'password'} </div>
intent={(errors.password && touched.password) && Intent.DANGER} <div class='invite-form__floating-footer'>
{...getFieldProps('password')} <Button
/> className={'btn-invite'}
</FormGroup> intent={Intent.PRIMARY}
type='submit'
<div class='form__floating-footer'> >
<Button intent={Intent.PRIMARY} type='submit'> Create Account
Invite
</Button> </Button>
</div> </div>
</form> </form>
<Copyright />
</div> </div>
); );
} }

View File

@@ -1,42 +1,47 @@
import React, { useEffect } from "react"; import React, { useEffect, useState } from 'react';
import {Link, useHistory} from 'react-router-dom'; import { Link, useHistory } from 'react-router-dom';
import * as Yup from 'yup'; import * as Yup from 'yup';
import {useFormik} from 'formik'; import { useFormik } from 'formik';
import {connect} from 'react-redux'; import { connect } from 'react-redux';
import {useIntl} from 'react-intl'; import { useIntl } from 'react-intl';
import { import {
Button, Button,
InputGroup, InputGroup,
Intent, Intent,
FormGroup, FormGroup,
} from "@blueprintjs/core"; Checkbox,
Icon,
} from '@blueprintjs/core';
import login from 'store/authentication/authentication.actions'; import login from 'store/authentication/authentication.actions';
import {hasErrorType, isAuthenticated} from 'store/authentication/authentication.reducer'; import {
hasErrorType,
isAuthenticated,
} from 'store/authentication/authentication.reducer';
import AuthenticationToaster from 'components/AppToaster'; import AuthenticationToaster from 'components/AppToaster';
import t from 'store/types'; import t from 'store/types';
import ErrorMessage from 'components/ErrorMessage';
import IconLog from 'components/Icon';
import Copyright from './copyright';
const ERRORS_TYPES = { const ERRORS_TYPES = {
INVALID_DETAILS: 'INVALID_DETAILS', INVALID_DETAILS: 'INVALID_DETAILS',
USER_INACTIVE: 'USER_INACTIVE', USER_INACTIVE: 'USER_INACTIVE',
}; };
function Login({ function Login({ login, errors, clearErrors, hasError }) {
login,
errors,
clearErrors,
hasError,
}) {
const intl = useIntl(); const intl = useIntl();
const history = useHistory(); const history = useHistory();
const [shown, setShown] = useState(false);
// Validation schema. // Validation schema.
const loginValidationSchema = Yup.object().shape({ const loginValidationSchema =
crediential: Yup Yup.object().shape({
.string() crediential: Yup.string()
.required(intl.formatMessage({'id': 'required'})) .required(intl.formatMessage({ id: 'required' }))
.email(intl.formatMessage({id: 'invalid_email_or_phone_numner'})), .email(intl.formatMessage({ id: 'invalid_email_or_phone_numner' })),
password: Yup password: Yup.string()
.string() .required(intl.formatMessage({ id: 'required' }))
.required(intl.formatMessage({id: 'required'}))
.min(4), .min(4),
}); });
@@ -65,11 +70,13 @@ function Login({
} }
if (hasError(ERRORS_TYPES.USER_INACTIVE)) { if (hasError(ERRORS_TYPES.USER_INACTIVE)) {
toastBuilders.push({ toastBuilders.push({
message: intl.formatMessage({ id: 'the_user_has_been_suspended_from_admin' }), message: intl.formatMessage({
id: 'the_user_has_been_suspended_from_admin',
}),
intent: Intent.WARNING, intent: Intent.WARNING,
}); });
} }
toastBuilders.forEach(builder => { toastBuilders.forEach((builder) => {
AuthenticationToaster.show(builder); AuthenticationToaster.show(builder);
}); });
}, [hasError, intl]); }, [hasError, intl]);
@@ -81,53 +88,87 @@ function Login({
} }
}); });
const passwordRevealer = () => {
setShown(!shown);
};
return ( return (
<div className="login-page"> <div className='login-form'>
<IconLog
className={'login-form__icon-section'}
icon='bigcapital'
iconSize={150}
/>
<form onSubmit={formik.handleSubmit}> <form onSubmit={formik.handleSubmit}>
<FormGroup <div className={'login-form__label-section'}>
className={'form-group--crediential'} <h3>Log in</h3>
intent={formik.errors.crediential && Intent.DANGER} Need a Bigcapital account ?
helperText={formik.errors.crediential && formik.errors.crediential}> <Link to='/auth/register'> Create an account</Link>
</div>
<div>
<FormGroup
label={'Email or Phone Number'}
intent={
formik.errors.crediential &&
formik.touched.crediential &&
Intent.DANGER
}
helperText={<ErrorMessage name={'crediential'} {...formik} />}
>
<InputGroup
intent={
formik.errors.crediential &&
formik.touched.crediential &&
Intent.DANGER
}
large={true}
{...formik.getFieldProps('crediential')}
/>
</FormGroup>
</div>
<div>
<FormGroup
label={'Password'}
labelInfo={
<span onClick={() => passwordRevealer()}>
<Icon icon='eye-open' />
Show
</span>
}
intent={
formik.errors.password && formik.touched.password && Intent.DANGER
}
helperText={<ErrorMessage name={'password'} {...formik} />}
>
<InputGroup
large={true}
intent={
formik.errors.password &&
formik.touched.password &&
Intent.DANGER
}
type={shown ? 'text' : 'password'}
{...formik.getFieldProps('password')}
/>
</FormGroup>
</div>
<div className={'login-form__checkbox-section'}>
<Checkbox>Keep me logged in</Checkbox>
</div>
<InputGroup <div className={'login-form__floating-footer-section'}>
leftIcon="user" <Button
placeholder={intl.formatMessage({'id': 'email_or_phone_number'})} className={'btn-login'}
large={true} type={'submit'}
intent={formik.errors.crediential && Intent.DANGER} intent={Intent.PRIMARY}
{...formik.getFieldProps('crediential')} /> fill={true}
</FormGroup> lang={true}
>
<FormGroup {intl.formatMessage({ id: 'Log in' })}
className={'form-group--password'} </Button>
intent={formik.errors.password && Intent.DANGER} </div>
helperText={formik.errors.password && formik.errors.password}>
<InputGroup
leftIcon="info-sign"
placeholder={intl.formatMessage({'id': 'password'})}
large={true}
intent={formik.errors.password && Intent.DANGER}
type={"password"}
{...formik.getFieldProps('password')} />
</FormGroup>
<Button
type="submit"
fill={true}
large={true}>
{intl.formatMessage({'id': 'login '})}
</Button>
</form> </form>
<Copyright />
<div className="authentication-page__footer">
<Link to="/auth/send_reset_password">
{intl.formatMessage({'id': 'reset_password '})}
</Link>
<Link to="/auth/register">
{intl.formatMessage({'id': 'register '})}
</Link>
</div>
</div> </div>
); );
} }
@@ -138,7 +179,7 @@ const mapStateToProps = (state) => ({
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
login: form => dispatch(login({ form })), login: (form) => dispatch(login({ form })),
clearErrors: () => dispatch({ type: t.LOGIN_CLEAR_ERRORS }), clearErrors: () => dispatch({ type: t.LOGIN_CLEAR_ERRORS }),
}); });

View File

@@ -1,4 +1,4 @@
import React, { useEffect, useMemo } from 'react'; import React, { useEffect, useMemo, useState } from 'react';
import * as Yup from 'yup'; import * as Yup from 'yup';
import { useFormik } from 'formik'; import { useFormik } from 'formik';
import { useIntl } from 'react-intl'; import { useIntl } from 'react-intl';
@@ -8,20 +8,27 @@ import {
Intent, Intent,
FormGroup, FormGroup,
HTMLSelect, HTMLSelect,
Icon,
} from '@blueprintjs/core'; } from '@blueprintjs/core';
import RegisterFromConnect from 'connectors/RegisterForm.connect'; import RegisterFromConnect from 'connectors/RegisterForm.connect';
import ErrorMessage from 'components/ErrorMessage'; import ErrorMessage from 'components/ErrorMessage';
import AppToaster from 'components/AppToaster'; import AppToaster from 'components/AppToaster';
import { compose, regExpCollection } from 'utils'; import { compose } from 'utils';
import { Row, Col } from 'react-grid-system';
import IconLog from 'components/Icon';
import Copyright from './copyright';
import { Link } from 'react-router-dom';
// import { compose, regExpCollection } from 'utils';
function Register({ function Register({ requestSubmitRegister }) {
requestSubmitRegister,
}) {
const intl = useIntl(); const intl = useIntl();
const Country = useMemo(() => [ const Country = useMemo(
{ value: null, label: 'Select Country' }, () => [
{ value: 'libya', label: 'Libya' }, { value: null, label: 'Select Country' },
], []); { value: 'libya', label: 'Libya' },
],
[]
);
const ValidationSchema = Yup.object().shape({ const ValidationSchema = Yup.object().shape({
organization_name: Yup.string().required(), organization_name: Yup.string().required(),
@@ -29,32 +36,28 @@ function Register({
last_name: Yup.string().required(), last_name: Yup.string().required(),
email: Yup.string().email().required(), email: Yup.string().email().required(),
phone_number: Yup.string() phone_number: Yup.string()
.matches(regExpCollection.phoneNumber) .matches()
.required(intl.formatMessage({ id: 'required' })), .required(intl.formatMessage({ id: 'required' })),
password: Yup.string() password: Yup.string()
.min(4, 'Password has to be longer than 8 characters!') .min(4, 'Password has to be longer than 8 characters!')
.required('Password is required!'), .required('Password is required!'),
country: Yup.string().required(intl.formatMessage({ id: 'required' })), // country: Yup.string().required(intl.formatMessage({ id: 'required' })),
}); });
const initialValues = useMemo(() => ({ const initialValues = useMemo(
organization_name: '', () => ({
first_name: '', organization_name: '',
last_name: '', first_name: '',
email: '', last_name: '',
phone_number: '', email: '',
password: '', phone_number: '',
country: '', password: '',
}), []); // country: '',
}),
[]
);
const { const formik = useFormik({
getFieldProps,
getFieldMeta,
errors,
values,
touched,
handleSubmit,
} = useFormik({
enableReinitialize: true, enableReinitialize: true,
validationSchema: ValidationSchema, validationSchema: ValidationSchema,
initialValues: { initialValues: {
@@ -76,120 +79,173 @@ function Register({
const requiredSpan = useMemo(() => <span class='required'>*</span>, []); const requiredSpan = useMemo(() => <span class='required'>*</span>, []);
const [shown, setShown] = useState(false);
const passwordRevealer = () => {
setShown(!shown);
};
return ( return (
<div className={'register-form'}> <div className={'register-form'}>
<form onSubmit={handleSubmit}> <IconLog
<FormGroup className={'register-form__icon-section'}
label={'Organization Name'} icon='bigcapital'
labelInfo={requiredSpan} iconSize={150}
className={'form-group--name'} />
intent={ <form onSubmit={formik.handleSubmit}>
(errors.organization_name && touched.organization_name) && <div className={'register-form__label-section'}>
Intent.DANGER <h3>
} Register a New <br />
helperText={<ErrorMessage name={'organization_name'} />} Organization.
> </h3>
<InputGroup You have a bigcapital account ?<Link to='/auth/login'> Login</Link>
</div>
<div>
<FormGroup
label={'Organization Name'}
className={'form-group--name'}
intent={ intent={
(errors.organization_name && formik.errors.organization_name &&
touched.organization_name) && formik.touched.organization_name &&
Intent.DANGER Intent.DANGER
} }
{...getFieldProps('organization_name')} helperText={<ErrorMessage {...formik} name={'organization_name'} />}
/> >
</FormGroup> <InputGroup
intent={
formik.errors.organization_name &&
formik.touched.organization_name &&
formik.Intent.DANGER
}
{...formik.getFieldProps('organization_name')}
/>
</FormGroup>
</div>
<Row className={'name-section'}>
<Col md={6}>
<FormGroup
label={'First Name'}
intent={
formik.errors.first_name &&
formik.touched.first_name &&
Intent.DANGER
}
helperText={<ErrorMessage name={'first_name'} {...formik} />}
>
<InputGroup
intent={
formik.errors.first_name &&
formik.touched.first_name &&
Intent.DANGER
}
{...formik.getFieldProps('first_name')}
/>
</FormGroup>
</Col>
<Col md={6}>
<FormGroup
label={'Last Name'}
intent={
formik.errors.last_name &&
formik.touched.last_name &&
Intent.DANGER
}
helperText={<ErrorMessage name={'last_name'} {...formik} />}
>
<InputGroup
intent={
formik.errors.last_name &&
formik.touched.last_name &&
Intent.DANGER
}
{...formik.getFieldProps('last_name')}
/>
</FormGroup>
</Col>
</Row>
<FormGroup <div>
label={'First Name'} <FormGroup
labelInfo={requiredSpan} label={'Phone Number'}
className={'form-group--first_name'} intent={
intent={(errors.first_name && touched.first_name) && Intent.DANGER} formik.errors.phone_number &&
helperText={<ErrorMessage name={'first_name'} />} formik.touched.phone_number &&
> Intent.DANGER
<InputGroup }
intent={errors.first_name && touched.first_name && Intent.DANGER} helperText={<ErrorMessage name={'phone_number'} {...formik} />}
{...getFieldProps('first_name')} >
/> <InputGroup
</FormGroup> intent={
formik.errors.phone_number &&
<FormGroup formik.touched.phone_number &&
label={'Last Name'} Intent.DANGER
labelInfo={requiredSpan} }
className={'form-group--last_name'} {...formik.getFieldProps('phone_number')}
intent={errors.last_name && touched.last_name && Intent.DANGER} />
helperText={<ErrorMessage name={'last_name'} />} </FormGroup>
> </div>
<InputGroup <div>
intent={errors.last_name && touched.last_name && Intent.DANGER} <FormGroup
{...getFieldProps('last_name')} label={'Email'}
/> intent={
</FormGroup> formik.errors.email && formik.touched.email && Intent.DANGER
}
<FormGroup helperText={<ErrorMessage name={'email'} {...formik} />}
label={'Country'} >
labelInfo={requiredSpan} <InputGroup
className={'form-group--country'} intent={
intent={(errors.country && touched.country) && Intent.DANGER} formik.errors.email && formik.touched.email && Intent.DANGER
helperText={<ErrorMessage name={'country'} />} }
> {...formik.getFieldProps('email')}
<HTMLSelect />
fill={true} </FormGroup>
options={Country} </div>
{...getFieldProps('country')} <div>
/> <FormGroup
</FormGroup> label={'Password'}
labelInfo={
<FormGroup <span onClick={() => passwordRevealer()}>
label={'Phone Number'} <Icon icon='eye-open' />
labelInfo={requiredSpan} Show
className={'form-group--phone_number'} </span>
intent={(errors.phone_number && touched.phone_number) && Intent.DANGER} }
helperText={<ErrorMessage name={'phone_number'} />} className={'form-group--password'}
> intent={
<InputGroup formik.errors.password && formik.touched.password && Intent.DANGER
intent={(errors.phone_number && touched.phone_number) && Intent.DANGER} }
{...getFieldProps('phone_number')} helperText={<ErrorMessage name={'password'} {...formik} />}
/> >
</FormGroup> <InputGroup
lang={true}
<FormGroup type={shown ? 'text' : 'password'}
label={'Email'} intent={
labelInfo={requiredSpan} formik.errors.password &&
className={'form-group--email'} formik.touched.password &&
intent={(errors.email && touched.email) && Intent.DANGER} Intent.DANGER
helperText={<ErrorMessage name={'email'} />} }
> {...formik.getFieldProps('password')}
<InputGroup />
intent={errors.email && touched.email && Intent.DANGER} </FormGroup>
{...getFieldProps('email')} </div>
/> <div className={'register-form__statement-section'}>
</FormGroup> <p>
By signing in or creating an account, you agree with our <br />
<FormGroup <Link>Terms & Conditions</Link> and <Link> Privacy Statement</Link>
label={'Password'} </p>
labelInfo={requiredSpan} </div>
className={'form-group--password'} <div className={'register-form__floating-footer-section'}>
intent={(errors.password && touched.password) && Intent.DANGER} <Button
helperText={<ErrorMessage name={'password'} />} className={'btn-register'}
> intent={Intent.PRIMARY}
<InputGroup type='submit'
lang={true} >
type={'password'}
intent={(errors.password && touched.password) && Intent.DANGER}
{...getFieldProps('password')}
/>
</FormGroup>
<div class='form__floating-footer'>
<Button intent={Intent.PRIMARY} type='submit'>
Register Register
</Button> </Button>
</div> </div>
</form> </form>
<Copyright />
</div> </div>
); );
} }
export default compose( export default compose(RegisterFromConnect)(Register);
RegisterFromConnect,
)(Register);

View File

@@ -12,11 +12,12 @@ import {
import ErrorMessage from 'components/ErrorMessage'; import ErrorMessage from 'components/ErrorMessage';
import AppToaster from 'components/AppToaster'; import AppToaster from 'components/AppToaster';
import { compose } from 'utils'; import { compose } from 'utils';
import SendResetPasswordConnect from 'connectors/ResetPassword.connect'; import ResetPasswordConnect from 'connectors/ResetPassword.connect';
import Icon from 'components/Icon';
import Copyright from './copyright';
import { Link } from 'react-router-dom';
function ResetPassword({ function ResetPassword({ requestSendResetPassword }) {
requestSendResetPassword,
}) {
const intl = useIntl(); const intl = useIntl();
const ValidationSchema = Yup.object().shape({ const ValidationSchema = Yup.object().shape({
password: Yup.string() password: Yup.string()
@@ -27,19 +28,15 @@ function ResetPassword({
.required('Confirm Password is required'), .required('Confirm Password is required'),
}); });
const initialValues = useMemo(() => ({ const initialValues = useMemo(
password: '', () => ({
confirm_password: '', password: '',
}), []); confirm_password: '',
}),
[]
);
const { const formik= useFormik({
errors,
values,
touched,
getFieldMeta,
getFieldProps,
handleSubmit,
} = useFormik({
enableReinitialize: true, enableReinitialize: true,
validationSchema: ValidationSchema, validationSchema: ValidationSchema,
initialValues: { initialValues: {
@@ -62,52 +59,66 @@ function ResetPassword({
const requiredSpan = useMemo(() => <span class='required'>*</span>, []); const requiredSpan = useMemo(() => <span class='required'>*</span>, []);
return ( return (
<div className={'sendRestPassword-form'}> <div className={'submit-np-form'}>
<form onSubmit={handleSubmit}> <div>
<FormGroup <Icon
label={'Password'} className={'submit-np-form__icon-section'}
labelInfo={requiredSpan} icon='bigcapital'
className={'form-group--password'} iconSize={150}
intent={errors.password && touched.password && Intent.DANGER} />
helperText={<ErrorMessage name={'password'} />} </div>
> <form onSubmit={formik.handleSubmit}>
<InputGroup <div className={'submit-np-form__label-section'}>
lang={true} <h3>Choose a new password</h3>
type={'password'} You remembered your password ? <Link to='/auth/login'>Login</Link>
intent={errors.password && touched.password && Intent.DANGER} </div>
{...getFieldProps('password')} <div>
/> <FormGroup
</FormGroup> label={'Password'}
intent={formik.errors.password && formik.touched.password && Intent.DANGER}
<FormGroup helperText={<ErrorMessage name={'password'} {...formik} />}
label={'Confirm Password'} >
labelInfo={requiredSpan} <InputGroup
className={'form-group--confirm_password'} lang={true}
intent={ type={'password'}
errors.confirm_password && touched.confirm_password && Intent.DANGER intent={formik.errors.password && formik.touched.password && Intent.DANGER}
} {...formik.getFieldProps('password')}
helperText={<ErrorMessage name={'confirm_password'} />} />
> </FormGroup>
<InputGroup </div>
lang={true} <div>
type={'password'} <FormGroup
label={'New Password'}
labelInfo={'(again):'}
intent={ intent={
errors.confirm_password && formik.errors.confirm_password &&
touched.confirm_password && formik.touched.confirm_password &&
Intent.DANGER Intent.DANGER
} }
{...getFieldProps('confirm_password')} helperText={<ErrorMessage name={'confirm_password'} {...formik} />}
/> >
</FormGroup> <InputGroup
lang={true}
type={'password'}
intent={
formik.errors.confirm_password &&
formik.touched.confirm_password &&
Intent.DANGER
}
{...formik.getFieldProps('confirm_password')}
/>
</FormGroup>
</div>
<div class='form__floating-footer'> <div class='submit-np-form__floating-footer-section'>
<Button intent={Intent.PRIMARY} type='submit'> <Button className={'btn-new'} intent={Intent.PRIMARY} type='submit'>
Reset Password Submit
</Button> </Button>
</div> </div>
</form> </form>
<Copyright />
</div> </div>
); );
} }
export default compose(SendResetPasswordConnect)(ResetPassword); export default compose(ResetPasswordConnect)(ResetPassword);

View File

@@ -1,29 +1,114 @@
import * as React from 'react'; import React, { useEffect, useMemo, useState } from 'react';
import { Link } from 'react-router-dom'; import * as Yup from 'yup';
import { Button, InputGroup } from '@blueprintjs/core'; import { useFormik } from 'formik';
import { useIntl } from 'react-intl';
import { Link, useHistory } from 'react-router-dom';
import { Button, InputGroup, Intent, FormGroup } from '@blueprintjs/core';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import ErrorMessage from 'components/ErrorMessage';
import Icon from 'components/Icon';
import Copyright from './copyright';
import SendResetPasswordConnect from 'connectors/SendResetPassword.connect';
import { compose } from 'utils';
import AppToaster from 'components/AppToaster';
export default function SendResetPassword() { function SendResetPassword({ requestSendResetPassword }) {
const intl = useIntl();
const phoneRegExp = /^((\\+[1-9]{1,4}[ \\-]*)|(\\([0-9]{2,3}\\)[ \\-]*)|([0-9]{2,4})[ \\-]*)*?[0-9]{3,4}?[ \\-]*[0-9]{3,4}?$/;
const history = useHistory();
// Validation schema.
const ValidationSchema = Yup.object().shape({
crediential: Yup.string('')
.required(intl.formatMessage({ id: 'required' }))
.email(intl.formatMessage({ id: 'invalid_email_or_phone_numner' })),
});
const initialValues = useMemo(
() => ({
crediential: '',
}),
[]
);
// Formik validation
const formik = useFormik({
enableReinitialize: true,
validationSchema: ValidationSchema,
initialValues: {
...initialValues,
},
onSubmit: (values, { setSubmitting }) => {
requestSendResetPassword(values.crediential)
.then((response) => {
AppToaster.show({
message:
'Check your email for a link to reset your password. If it doesnt appear within a few minutes, check your spam folder.',
});
history.push('/auth/login');
setSubmitting(false);
})
.catch((error) => {
setSubmitting(false);
});
},
});
return ( return (
<div class='login-page'> <div class='reset-form'>
<form> <Icon
<InputGroup className={'reset-form__icon-section'}
leftIcon='user' icon='bigcapital'
placeholder={<FormattedMessage id='email_or_phone_number' />} iconSize={150}
large={true} />
className='input-group--email' <form onSubmit={formik.handleSubmit}>
/> <div className={'reset-form__label-section'}>
<h3>You Can't login ?</h3>
<Button fill={true} large={true}> <p>We'll send a recovery link to your email</p>
<FormattedMessage id='send_reset_password' /> </div>
</Button> <div>
<FormGroup
label={'Email or Phone Number'}
intent={
formik.errors.crediential &&
formik.touched.crediential &&
Intent.DANGER
}
helperText={<ErrorMessage name={'crediential'} {...formik} />}
>
<InputGroup
intent={
formik.errors.crediential &&
formik.touched.crediential &&
Intent.DANGER
}
large={true}
{...formik.getFieldProps('crediential')}
/>
</FormGroup>
</div>
<div className={'reset-form__floating-section'}>
<Button
className={'btn-send'}
type={'submit'}
intent={Intent.PRIMARY}
fill={true}
>
{intl.formatMessage({ id: 'Send Reset Password Mail' })}
</Button>
</div>
</form> </form>
<div class='authentication-page__footer'> <div class='reset-form__footer-section'>
<Link to='/auth/login'> <Link to='/auth/login'>
<FormattedMessage id='login' /> <FormattedMessage id='Return to log in' />
</Link> </Link>
</div> </div>
<Copyright />
</div> </div>
); );
} }
export default compose(SendResetPasswordConnect)(SendResetPassword);

View File

@@ -0,0 +1,79 @@
import React from 'react';
import { Row, Col } from 'react-grid-system';
function Copyright() {
return (
<Row className={'copyright-page'}>
<Col md={12}>
<svg
xmlns='http://www.w3.org/2000/svg'
width='180'
height='18'
viewBox='0 0 195 18'
>
<text
data-name='© 20012020 All Rights Reserved. '
transform='translate(0 13)'
fill='#666'
font-size='13'
font-family='SegoeUI, Segoe UI'
>
<tspan x='0' y='2'>
© 20012020 All Rights Reserved.
</tspan>
</text>
</svg>
</Col>
<Col md={12}>
<svg
xmlns='http://www.w3.org/2000/svg'
width='121'
height='20'
viewBox='0 0 121.499 26'
>
<g
id='Component_2_5'
data-name='Component 2 5'
transform='translate(29.499 3)'
>
<text
id='Bigcapital.'
transform='translate(0 18)'
fill='#a3a3a3'
font-size='18'
font-family='SegoeUI-Bold, Segoe UI'
font-weight='700'
// letter-spacing='-0.005em'
line-height='14'
>
<tspan x='0' y='0'>
Bigcapital
</tspan>
<tspan y='0' font-size='14'>
.
</tspan>
</text>
</g>
<rect
id='Rectangle_107'
data-name='Rectangle 107'
width='2.919'
height='16.053'
transform='translate(21.566 10.444) rotate(45)'
fill='#a3a3a3'
/>
<rect
id='Rectangle_108'
data-name='Rectangle 108'
width='2.919'
height='16.053'
transform='translate(11.351 11.903) rotate(45)'
fill='#a3a3a3'
/>
</svg>
</Col>
</Row>
);
}
export default Copyright;

View File

@@ -4,34 +4,49 @@ import t from 'store/types';
export default function login({ form }) { export default function login({ form }) {
return (dispatch) => { return (dispatch) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
ApiService.post('auth/login', form).then(response => { ApiService.post('auth/login', form)
const { data } = response; .then((response) => {
const { data } = response;
dispatch({ type: t.LOGIN_CLEAR_ERRORS }); dispatch({ type: t.LOGIN_CLEAR_ERRORS });
if (data.token && data.user) { if (data.token && data.user) {
dispatch({ dispatch({
type: t.LOGIN_SUCCESS, type: t.LOGIN_SUCCESS,
payload: { payload: {
user: data.user, user: data.user,
token: data.token, token: data.token,
}, },
}); });
} }
resolve(response); resolve(response);
}).catch((error) => { })
const { response } = error; .catch((error) => {
const { data } = response; const { response } = error;
const { errors } = data; const { data } = response;
const { errors } = data;
dispatch({type: t.LOGIN_CLEAR_ERRORS}); dispatch({ type: t.LOGIN_CLEAR_ERRORS });
if (errors){ if (errors) {
dispatch({ dispatch({
type: t.LOGIN_FAILURE, errors, type: t.LOGIN_FAILURE,
}); errors,
} });
reject(error); }
}); reject(error);
});
}); });
} };
} }
export const submitResetPassword = (password) => {
return (dispatch) => {
return ApiService.post('auth/reset_password', password);
};
};
export const submitSendResetPassword = (email) => {
return (dispatch) => {
return ApiService.post('auth/send_reset_password', email);
};
};

View File

@@ -1,9 +1,9 @@
@import './normalize.scss';
@import "./normalize.scss"; $pt-popover-box-shadow: 0 0 0 1px rgba(16, 22, 26, 0.02),
0 2px 4px rgba(16, 22, 26, 0.1), 0 8px 24px rgba(16, 22, 26, 0.1);
$pt-popover-box-shadow: 0 0 0 1px rgba(16, 22, 26, 0.02), 0 2px 4px rgba(16, 22, 26, 0.1), 0 8px 24px rgba(16, 22, 26, 0.1); @import '@blueprintjs/core/src/common/_variables.scss';
@import "@blueprintjs/core/src/common/_variables.scss";
// @import "@blueprintjs/core/src/common/colors.scss"; // @import "@blueprintjs/core/src/common/colors.scss";
$menu-item-color-hover: $light-gray4; $menu-item-color-hover: $light-gray4;
@@ -12,52 +12,62 @@ $menu-item-color-active: $light-gray3;
$breadcrumbs-collapsed-icon: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='#6B8193' enable-background='new 0 0 16 16' xml:space='preserve'><g><circle cx='2' cy='8.03' r='2'/><circle cx='14' cy='8.03' r='2'/><circle cx='8' cy='8.03' r='2'/></g></svg>"); $breadcrumbs-collapsed-icon: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='#6B8193' enable-background='new 0 0 16 16' xml:space='preserve'><g><circle cx='2' cy='8.03' r='2'/><circle cx='14' cy='8.03' r='2'/><circle cx='8' cy='8.03' r='2'/></g></svg>");
// Blueprint framework. // Blueprint framework.
@import "@blueprintjs/core/src/blueprint.scss"; @import '@blueprintjs/core/src/blueprint.scss';
@import "@blueprintjs/datetime/src/blueprint-datetime.scss"; @import '@blueprintjs/datetime/src/blueprint-datetime.scss';
@import "basscss"; @import 'basscss';
$pt-font-family: Noto Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Open Sans, Helvetica Neue, Icons16, sans-serif; $pt-font-family: Noto Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
Oxygen, Ubuntu, Cantarell, Open Sans, Helvetica Neue, Icons16, sans-serif;
@import "functions"; @import 'functions';
// Objects // Objects
@import "objects/form"; @import 'objects/form';
@import "objects/typography"; @import 'objects/typography';
@import "objects/buttons"; @import 'objects/buttons';
// Components // Components
@import "components/data-table"; @import 'components/data-table';
@import "components/dialog"; @import 'components/dialog';
// Pages // Pages
@import "pages/dashboard"; @import 'pages/dashboard';
@import "pages/accounts-chart"; @import 'pages/accounts-chart';
@import "pages/authentication"; @import 'pages/authentication';
@import "pages/expense-form"; @import 'pages/expense-form';
@import "pages/financial-statements"; @import 'pages/financial-statements';
@import "pages/make-journal-entries"; @import 'pages/make-journal-entries';
@import "pages/preferences"; @import 'pages/preferences';
@import "pages/view-form"; @import 'pages/view-form';
@import "pages/manual-journals"; @import 'pages/manual-journals';
@import "pages/item-category"; @import 'pages/item-category';
@import "pages/items"; @import 'pages/items';
@import "pages/currency"; @import 'pages/currency';
// Views // Views
@import "views/filter-dropdown"; @import 'views/filter-dropdown';
@import "views/sidebar"; @import 'views/sidebar';
.#{$ns}-tooltip{ .#{$ns}-tooltip {
box-shadow: none; box-shadow: none;
} }
[data-icon='bigcapital'] { [data-icon='bigcapital'] {
path{ path {
fill: #004dd0; fill: #004dd0;
} }
.path-1, .path-1,
.path-13{ .path-13 {
fill: #2d95fd; fill: #2d95fd;
} }
} }
.App {
background-color: #fcfdff;
min-height: 100vh;
display: flex;
// flex-direction: column;
// align-items: center;
justify-content: center;
// font-size: calc(10px + 2vmin);
// color: white;
}

View File

@@ -1,26 +1,266 @@
.authentication-page {
&__form-wrapper {
.authentication-page{
&__form-wrapper{
width: 100%; width: 100%;
max-width: 350px; max-width: 450px;
padding: 15px; padding: 15px;
margin: 0 auto; margin: 0 auto;
} }
.login-page{ // Login Form
.login-form {
width: 385px;
.input-group{ &__icon-section {
&--email-phone-number{ margin: 0 auto;
margin-bottom: 1rem; }
&__label-section {
h3 {
font-weight: normal;
margin: 0;
font-size: 24px;
margin-bottom: 14px;
} }
&--password{ a {
margin-bottom: 1rem; text-decoration: underline;
color: #0040bd;
}
margin-bottom: 30px;
}
.bp3-form-group {
.bp3-form-content {
width: 384px;
min-height: 40px;
}
}
.bp3-icon-eye-open {
display: inline-block;
justify-content: space-between;
flex: 0 0 auto;
vertical-align: text-bottom;
margin: 0px 5px 0px 265px;
}
&__checkbox-section {
margin: -10px 0 30px 0px;
}
&__floating-footer-section {
margin: 0px 0px 24px 0px;
.btn-login {
display: inline-flex;
align-items: center;
justify-content: center;
background-color: #0052cc;
min-height: 45px;
} }
} }
} }
&__footer{
margin-top: 1rem; //Register Form
text-align: center;
.register-form {
width: 384px;
&__icon-section {
margin: 0 auto;
}
&__label-section {
h3 {
font-weight: normal;
margin: 0;
font-size: 24px;
margin-bottom: 14px;
}
margin-bottom: 35px;
a {
text-decoration: underline;
color: #0040bd;
}
}
.bp3-input {
min-height: 40px;
}
&__statement-section {
margin-top: -10px;
p {
font-size: 13px;
margin-top: -10px;
margin-bottom: 20px;
line-height: 25px;
}
}
.bp3-icon-eye-open {
display: inline-block;
justify-content: space-between;
flex: 0 0 auto;
vertical-align: text-bottom;
margin: 0px 5px 0px 265px;
}
&__floating-footer-section {
margin: 25px 0px 25px 0px;
.btn-register {
display: inline-flex;
align-items: center;
justify-content: center;
background-color: #0052cc;
min-height: 45px;
width: 100%;
}
}
}
// Send Reset Form
.reset-form {
width: 385px;
&__icon-section {
margin: 0 auto;
}
&__label-section {
h3 {
font-weight: normal;
margin: 0;
font-size: 24px;
margin-bottom: 14px;
}
margin-bottom: 30px;
}
&__footer-section {
padding: 14px 16px;
border-top: 1px solid #ececec;
margin: 0px 0px 25px auto;
border-bottom: 1px solid #ececec;
text-align: center;
a {
text-decoration: none;
color: #0040bd;
}
}
&__floating-section {
margin: 30px 0px 25px auto;
.btn-send {
display: inline-flex;
align-items: center;
justify-content: center;
background-color: #0052cc;
min-height: 45px;
width: 100%;
}
}
}
// Reset New Password Form
.submit-np-form {
width: 384px;
&__icon-section {
margin: 0 auto;
}
&__label-section {
h3 {
font-weight: normal;
margin: 0;
font-size: 24px;
margin-bottom: 14px;
}
a {
text-decoration: underline;
color: #0040bd;
}
margin-bottom: 30px;
}
.bp3-input {
min-height: 40px;
}
&__floating-footer-section {
margin: 20px 0px 30px auto;
.btn-new {
display: inline-flex;
align-items: center;
justify-content: center;
background-color: #0052cc;
min-height: 45px;
width: 100%;
}
}
}
// Invite Accept Form
.invite-form {
width: 384px;
&__icon-section {
margin: 0 auto;
}
&__label-section {
h3 {
font-weight: normal;
margin: 0;
font-size: 24px;
margin-bottom: 14px;
}
margin-bottom: 30px;
}
.bp3-input {
min-height: 40px;
}
.bp3-icon-eye-open {
display: inline-block;
justify-content: space-between;
flex: 0 0 auto;
vertical-align: text-bottom;
margin: 0px 5px 0px 265px;
}
&__statement-section {
margin-top: -10px;
p {
font-size: 13px;
margin-bottom: 20px;
line-height: 25px;
}
}
&__floating-footer {
margin: 20px 0px 20px auto;
.btn-invite {
display: inline-flex;
align-items: center;
justify-content: center;
background-color: #0052cc;
min-height: 45px;
width: 100%;
}
}
}
.bp3-icon-bigcapital {
display: inline-block;
flex: 0 0 auto;
vertical-align: text-bottom;
margin-left: 100px;
} }
} }
// Copyright Page
.copyright-page {
text-align: center;
line-height: 14px;
}