From 225a1b741cd96b3f5a45ec2336ff3c74036c3f45 Mon Sep 17 00:00:00 2001 From: elforjani3 Date: Wed, 22 Apr 2020 22:12:02 +0200 Subject: [PATCH 1/2] WIP feature/Register --- client/src/connectors/RegisterForm.connect.js | 12 ++ .../src/containers/Authentication/Register.js | 201 ++++++++++++++++++ client/src/routes/authentication.js | 15 +- client/src/store/registers/register.action.js | 7 + .../src/store/registers/register.reducer.js | 20 ++ client/src/store/registers/register.type.js | 4 + client/src/store/types.js | 2 + 7 files changed, 257 insertions(+), 4 deletions(-) create mode 100644 client/src/connectors/RegisterForm.connect.js create mode 100644 client/src/containers/Authentication/Register.js create mode 100644 client/src/store/registers/register.action.js create mode 100644 client/src/store/registers/register.reducer.js create mode 100644 client/src/store/registers/register.type.js diff --git a/client/src/connectors/RegisterForm.connect.js b/client/src/connectors/RegisterForm.connect.js new file mode 100644 index 000000000..a82e601ac --- /dev/null +++ b/client/src/connectors/RegisterForm.connect.js @@ -0,0 +1,12 @@ +import { connect } from 'react-redux'; +import { submitRegister } from 'store/registers/register.action'; + +export const mapStateToProps = (state, props) => { + return {}; +}; + +export const mapDispatchToProps = (dispatch) => ({ + requestSubmitRegister: (form) => dispatch(submitRegister({ form })), +}); + +export default connect(mapStateToProps, mapDispatchToProps); diff --git a/client/src/containers/Authentication/Register.js b/client/src/containers/Authentication/Register.js new file mode 100644 index 000000000..526b59975 --- /dev/null +++ b/client/src/containers/Authentication/Register.js @@ -0,0 +1,201 @@ +import React, { useEffect, useMemo } from 'react'; +import * as Yup from 'yup'; +import { useFormik } from 'formik'; +import { useIntl } from 'react-intl'; +import { + Button, + InputGroup, + Intent, + FormGroup, + HTMLSelect, +} from '@blueprintjs/core'; + +import RegisterFromConnect from 'connectors/RegisterForm.connect'; +import ErrorMessage from 'components/ErrorMessage'; +import AppToaster from 'components/AppToaster'; +import { compose } from 'utils'; + +function Register({ requestSubmitRegister }) { + 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 Country = useMemo( + () => [ + { value: null, label: 'Select Country' }, + { value: 'libya', label: 'Libya' }, + ], + [] + ); + + const ValidationSchema = Yup.object().shape({ + organization_name: Yup.string().required( + intl.formatMessage({ id: 'required' }) + ), + first_name: Yup.string().required(intl.formatMessage({ id: 'required' })), + + last_name: Yup.string().required(intl.formatMessage({ id: 'required' })), + email: Yup.string() + .email() + .required(intl.formatMessage({ id: 'required' })), + phone_number: Yup.string().matches(phoneRegExp, 'required'), + password: Yup.string() + .min(4, 'Password has to be longer than 8 characters!') + .required('Password is required!'), + country: Yup.string().required(intl.formatMessage({ id: 'required' })), + }); + + const initialValues = useMemo( + () => ({ + organization_name: '', + first_name: '', + last_name: '', + email: '', + phone_number: '', + password: '', + country: '', + }), + [] + ); + + const formik = useFormik({ + enableReinitialize: true, + validationSchema: ValidationSchema, + initialValues: { + ...initialValues, + }, + onSubmit: (values, { setSubmitting }) => { + requestSubmitRegister(values) + .then((response) => { + AppToaster.show({ + message: 'success', + }); + setSubmitting(false); + }) + .catch((error) => { + setSubmitting(false); + }); + }, + }); + + console.log(formik.values); + + const { errors, values, touched } = useMemo(() => formik, [formik]); + const requiredSpan = useMemo(() => *, []); + + return ( +
+
+ } + > + + + + } + > + + + } + > + + + + } + > + + + + } + > + + + } + > + + + + } + > + + + + +
+
+ ); +} + +export default compose(RegisterFromConnect)(Register); diff --git a/client/src/routes/authentication.js b/client/src/routes/authentication.js index eb8d2a367..41196aaa9 100644 --- a/client/src/routes/authentication.js +++ b/client/src/routes/authentication.js @@ -7,14 +7,21 @@ export default [ path: `${BASE_URL}/login`, name: 'auth.login', component: LazyLoader({ - loader: () => import('containers/Authentication/Login') + loader: () => import('containers/Authentication/Login'), + }), + }, + { + path: `${BASE_URL}/register`, + name: 'auth.register', + component: LazyLoader({ + loader: () => import('containers/Authentication/Register'), }), }, { path: `${BASE_URL}/reset_password`, name: 'auth.reset_password', component: LazyLoader({ - loader: () => import('containers/Authentication/ResetPassword') + loader: () => import('containers/Authentication/ResetPassword'), }), - } -]; \ No newline at end of file + }, +]; diff --git a/client/src/store/registers/register.action.js b/client/src/store/registers/register.action.js new file mode 100644 index 000000000..251ab02bd --- /dev/null +++ b/client/src/store/registers/register.action.js @@ -0,0 +1,7 @@ +import ApiService from 'services/ApiService'; + +export const submitRegister = ({ form }) => { + return (dispatch) => { + return ApiService.post('auth/register', { ...form }); + }; +}; diff --git a/client/src/store/registers/register.reducer.js b/client/src/store/registers/register.reducer.js new file mode 100644 index 000000000..89e08bca3 --- /dev/null +++ b/client/src/store/registers/register.reducer.js @@ -0,0 +1,20 @@ +import { createReducer } from '@reduxjs/toolkit'; +import t from 'store/types'; + +const initialState = { + registers: {}, +}; + +export default createReducer(initialState, { + [t.REGISTER_SET]: (state, action) => { + const _registers = {}; + + action.registers.forEach((register) => { + _registers[register.id] = register; + }); + state.registers = { + ...state.registers, + ..._registers, + }; + }, +}); diff --git a/client/src/store/registers/register.type.js b/client/src/store/registers/register.type.js new file mode 100644 index 000000000..17a53e1c0 --- /dev/null +++ b/client/src/store/registers/register.type.js @@ -0,0 +1,4 @@ +export default { + REGISTER_SET: 'REGISTER_SUCCESS', + REGISTER_CLEAR_ERRORS: 'REGISTER_CLEAR_ERRORS', +}; diff --git a/client/src/store/types.js b/client/src/store/types.js index ef00f5c29..ed4bbad1b 100644 --- a/client/src/store/types.js +++ b/client/src/store/types.js @@ -14,6 +14,7 @@ import financialStatements from './financialStatement/financialStatements.types' import itemCategories from './itemCategories/itemsCategory.type'; import settings from './settings/settings.type'; import search from './search/search.type'; +import register from './registers/register.type'; export default { ...authentication, @@ -32,4 +33,5 @@ export default { ...settings, ...accounting, ...search, + ...register, }; From 7e6b66fb7476f0c4ab335c3796e0ce5f481b8dcb Mon Sep 17 00:00:00 2001 From: elforjani3 Date: Thu, 23 Apr 2020 21:11:44 +0200 Subject: [PATCH 2/2] WIP feature/Register --- client/src/connectors/InviteForm.connect.js | 13 ++ .../src/connectors/ResetPassword.connect.js | 13 ++ .../src/containers/Authentication/Invite.js | 183 ++++++++++++++++++ .../src/containers/Authentication/Register.js | 8 +- .../Authentication/ResetPassword.js | 127 +++++++++--- .../containers/Authentication/SendInvite.js | 81 ++++++++ .../Authentication/SendResetPassword.js | 29 +++ client/src/routes/authentication.js | 25 ++- client/src/store/Invite/invite.action.js | 14 ++ .../resetPassword/resetPassword.action.js | 7 + 10 files changed, 470 insertions(+), 30 deletions(-) create mode 100644 client/src/connectors/InviteForm.connect.js create mode 100644 client/src/connectors/ResetPassword.connect.js create mode 100644 client/src/containers/Authentication/Invite.js create mode 100644 client/src/containers/Authentication/SendInvite.js create mode 100644 client/src/containers/Authentication/SendResetPassword.js create mode 100644 client/src/store/Invite/invite.action.js create mode 100644 client/src/store/resetPassword/resetPassword.action.js diff --git a/client/src/connectors/InviteForm.connect.js b/client/src/connectors/InviteForm.connect.js new file mode 100644 index 000000000..83c5b7699 --- /dev/null +++ b/client/src/connectors/InviteForm.connect.js @@ -0,0 +1,13 @@ +import { connect } from 'react-redux'; +import { submitInvite, submitSendInvite } from 'store/Invite/invite.action'; + +export const mapStateToProps = (state, props) => { + return {}; +}; + +export const mapDispatchToProps = (dispatch) => ({ + requestSubmitInvite: (form, token) => dispatch(submitInvite({ form, token })), + requestSendInvite: (form) => dispatch(submitSendInvite({ form })), +}); + +export default connect(mapStateToProps, mapDispatchToProps); diff --git a/client/src/connectors/ResetPassword.connect.js b/client/src/connectors/ResetPassword.connect.js new file mode 100644 index 000000000..5dc209556 --- /dev/null +++ b/client/src/connectors/ResetPassword.connect.js @@ -0,0 +1,13 @@ +import { connect } from 'react-redux'; +import { submitResetPassword } from 'store/resetPassword/resetPassword.action'; + +export const mapStateToProps = (state, props) => { + return {}; +}; + +export const mapDispatchToProps = (dispatch) => ({ + requestResetPassword: (password) => + dispatch(submitResetPassword({password})), +}); + +export default connect(mapStateToProps, mapDispatchToProps); diff --git a/client/src/containers/Authentication/Invite.js b/client/src/containers/Authentication/Invite.js new file mode 100644 index 000000000..7837881e9 --- /dev/null +++ b/client/src/containers/Authentication/Invite.js @@ -0,0 +1,183 @@ +import React, { useEffect, useMemo } from 'react'; +import * as Yup from 'yup'; +import { useFormik } from 'formik'; +import { useIntl } from 'react-intl'; +import ErrorMessage from 'components/ErrorMessage'; +import AppToaster from 'components/AppToaster'; +import { compose } from 'utils'; +import InviteFormConnect from 'connectors/InviteForm.connect'; +import { useParams } from 'react-router-dom'; +import { + Button, + InputGroup, + Intent, + FormGroup, + HTMLSelect, +} from '@blueprintjs/core'; + +function Invite({ requestSubmitInvite }) { + const intl = useIntl(); + let params = useParams('accept/:token'); + + const { token } = params; + + 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({ + first_name: Yup.string().required(intl.formatMessage({ id: 'required' })), + + last_name: Yup.string().required(intl.formatMessage({ id: 'required' })), + + email: Yup.string() + .email() + .required(intl.formatMessage({ id: 'required' })), + phone_number: Yup.string() + .matches(phoneRegExp) + .required(intl.formatMessage({ id: 'required' })), + language: Yup.string().required( + intl.formatMessage({ + id: 'required', + }) + ), + password: Yup.string() + .min(4, 'Password has to be longer than 4 characters!') + .required('Password is required!'), + }); + + const initialValues = useMemo( + () => ({ + first_name: '', + last_name: '', + email: '', + phone_number: '', + language: '', + password: '', + }), + [] + ); + const formik = useFormik({ + enableReinitialize: true, + validationSchema: ValidationSchema, + initialValues: { + ...initialValues, + }, + onSubmit: (values, { setSubmitting }) => { + requestSubmitInvite(values, token) + .then((response) => { + AppToaster.show({ + message: 'success', + }); + setSubmitting(false); + }) + .catch((error) => { + setSubmitting(false); + }); + }, + }); + + const { errors, values, touched } = useMemo(() => formik, [formik]); + const requiredSpan = useMemo(() => *, []); + + return ( +
+
+ } + > + + + } + > + + + + } + > + + + + } + > + + + + } + > + + + } + > + + + + +
+
+ ); +} + +export default compose(InviteFormConnect)(Invite); diff --git a/client/src/containers/Authentication/Register.js b/client/src/containers/Authentication/Register.js index 526b59975..3189db91a 100644 --- a/client/src/containers/Authentication/Register.js +++ b/client/src/containers/Authentication/Register.js @@ -38,7 +38,9 @@ function Register({ requestSubmitRegister }) { email: Yup.string() .email() .required(intl.formatMessage({ id: 'required' })), - phone_number: Yup.string().matches(phoneRegExp, 'required'), + phone_number: Yup.string() + .matches(phoneRegExp) + .required(intl.formatMessage({ id: 'required' })), password: Yup.string() .min(4, 'Password has to be longer than 8 characters!') .required('Password is required!'), @@ -78,8 +80,6 @@ function Register({ requestSubmitRegister }) { }, }); - console.log(formik.values); - const { errors, values, touched } = useMemo(() => formik, [formik]); const requiredSpan = useMemo(() => *, []); @@ -168,7 +168,7 @@ function Register({ requestSubmitRegister }) { helperText={} > diff --git a/client/src/containers/Authentication/ResetPassword.js b/client/src/containers/Authentication/ResetPassword.js index 7abe691b6..421143528 100644 --- a/client/src/containers/Authentication/ResetPassword.js +++ b/client/src/containers/Authentication/ResetPassword.js @@ -1,29 +1,106 @@ -import * as React from "react"; -import { Link } from 'react-router-dom'; -import {Button, InputGroup} from "@blueprintjs/core"; -import { FormattedMessage } from 'react-intl'; +import React, { useEffect, useMemo } from 'react'; +import * as Yup from 'yup'; +import { useFormik } from 'formik'; +import { useIntl } from 'react-intl'; +import { + Button, + InputGroup, + Intent, + FormGroup, + HTMLSelect, +} from '@blueprintjs/core'; +import ErrorMessage from 'components/ErrorMessage'; +import AppToaster from 'components/AppToaster'; +import { compose } from 'utils'; +import SendResetPasswordConnect from 'connectors/ResetPassword.connect'; + +function ResetPassword({ requestSendResetPassword }) { + const intl = useIntl(); + const ValidationSchema = Yup.object().shape({ + password: Yup.string() + .min(4, 'Password has to be longer than 4 characters!') + .required('Password is required!'), + + confirm_password: Yup.string() + .oneOf([Yup.ref('password'), null], 'Passwords must match') + .required('Confirm Password is required'), + }); + + const initialValues = useMemo( + () => ({ + password: '', + confirm_password: '', + }), + [] + ); + const formik = useFormik({ + enableReinitialize: true, + validationSchema: ValidationSchema, + initialValues: { + ...initialValues, + }, + onSubmit: (values, { setSubmitting }) => { + requestSendResetPassword(values.password) + .then((response) => { + AppToaster.show({ + message: 'success', + }); + setSubmitting(false); + }) + .catch((error) => { + setSubmitting(false); + }); + }, + }); + + const { errors, values, touched } = useMemo(() => formik, [formik]); + const requiredSpan = useMemo(() => *, []); -export default function Login() { return ( -