Commit d424c215 authored by Viacheslav Pavlov's avatar Viacheslav Pavlov Committed by Matija Obreza

User registration page

parent ab2d9ab7
......@@ -43,7 +43,9 @@
"Partners": "Partners",
"Subsets": "Subsets",
"Institutes": "Institutes",
"Admin": "Administration"
"Admin": "Administration",
"login": "Login",
"register": "Registration"
},
"p": {
"crop": {
......
......@@ -13619,6 +13619,25 @@
"prop-types": "^15.6.0"
}
},
"react-async-script": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/react-async-script/-/react-async-script-1.0.0.tgz",
"integrity": "sha512-KNbqPgaOrb7sxEr3qLuyxswJfveCGSGsxj/jYbUT0esTD2p5u5kmnt6huOOEcL5UwU4Zmbw561gUC45xPjB+MA==",
"requires": {
"hoist-non-react-statics": "^3.0.1",
"prop-types": "^15.5.0"
},
"dependencies": {
"hoist-non-react-statics": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.0.1.tgz",
"integrity": "sha512-1kXwPsOi0OGQIZNVMPvgWJ9tSnGMiMfJdihqEzrPEXlHOBh9AAHXX/QYmAJTXztnz/K+PQ8ryCb4eGaN6HlGbQ==",
"requires": {
"react-is": "^16.3.2"
}
}
}
},
"react-autosuggest": {
"version": "9.3.4",
"resolved": "https://registry.npmjs.org/react-autosuggest/-/react-autosuggest-9.3.4.tgz",
......@@ -13719,6 +13738,15 @@
"prop-types": "^15.6.0"
}
},
"react-google-recaptcha": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/react-google-recaptcha/-/react-google-recaptcha-1.0.2.tgz",
"integrity": "sha512-YBiCB1Um6f+Jq7kfcoOKlOcHxeSEW/zC0+iN75CKoVw54vOz5c7NLNkTSc5cYRs4ymWNsMrye2qSVaENpB4vhA==",
"requires": {
"prop-types": "^15.5.0",
"react-async-script": "^1.0.0"
}
},
"react-hot-loader": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/react-hot-loader/-/react-hot-loader-4.3.3.tgz",
......
import {checkTokenRequest, loginAppRequest} from 'actions/login';
import {loginAppRequest} from 'actions/login';
import {checkTokenRequest} from 'user/actions/root';
function checkAuthTokenRequest(req, dispatch) {
const token = req.cookies.access_token;
......
......@@ -4,6 +4,7 @@ import * as cookies from 'es-cookie';
import { clearCookies, saveCookies } from 'utilities';
import { ROLE_CLIENT } from 'constants/userRoles';
import {log} from 'utilities/debug';
import {loginUser} from 'user/actions/root';
export function checkAccessTokens(dispatch) {
const cookieToken: string = typeof window !== 'undefined' && cookies.get('access_token');
......@@ -34,16 +35,6 @@ export function checkAccessTokens(dispatch) {
}
}
function loginRequest(username, password) {
return (dispatch) => {
return LoginService.login(username, password)
.then((data) => {
saveCookies(data);
return dispatch(loginUser(data));
});
};
}
function loginAppRequest() {
return (dispatch) => {
return LoginService.loginApp()
......@@ -53,47 +44,6 @@ function loginAppRequest() {
};
}
function logoutRequest() {
return (dispatch, getState) => {
const token = getState().login.access_token;
return LoginService.logout(token)
.then(() => {
clearCookies();
return dispatch(logout());
});
};
}
function checkTokenRequest(token) {
return (dispatch) => {
log('checkTokenRequest verifying ', token);
return LoginService.checkToken(token)
.then((data) => {
log('checkTokenRequest got', data);
return dispatch(checkToken({ access_token: token, ...data }));
});
};
}
function verifyGoogleTokenRequest(accessToken) {
return (dispatch, getState) => {
const token = getState().login.access_token;
return LoginService.verifyGoogleToken(token, accessToken)
.then((data) => {
saveCookies(data);
return dispatch(loginApp(data));
});
};
}
function loginUser(d) {
return {
type: Constants.LOGIN_USER,
...d,
};
}
function loginApp(d) {
return {
type: Constants.LOGIN_APP,
......@@ -102,27 +52,7 @@ function loginApp(d) {
};
}
function logout() {
return {
type: Constants.LOGOUT,
};
}
function checkToken(d) {
return {
type: Constants.CHECK_TOKEN,
...d,
};
}
function verifyGoogleToken(d) {
return {
type: Constants.VERIFY_GOOGLE_TOKEN,
...d,
};
}
export {
loginRequest, loginUser, loginAppRequest, loginApp, logoutRequest, logout,
checkTokenRequest, checkToken, verifyGoogleTokenRequest, verifyGoogleToken,
loginAppRequest, loginApp,
};
import {MY_LIST_ACCESSION_ADD, MY_LIST_ACCESSION_INIT, MY_LIST_ACCESSION_REMOVE, RECEIVE_USER_PROFILE} from 'constants/user';
import {User} from 'model/User';
import {log} from 'utilities/debug';
import {UserProfileService} from 'service/UserProfileService';
import {showSnackbar} from './snackbar';
import {SubmissionError} from 'redux-form';
import * as _ from 'lodash';
const initAccessions = (accessions: string[]) => ({
type: MY_LIST_ACCESSION_INIT,
payload: accessions,
});
const addAccession = (accUUID: string) => ({
type: MY_LIST_ACCESSION_ADD,
payload: accUUID,
});
const removeAccession = (accUUID: string) => ({
type: MY_LIST_ACCESSION_REMOVE,
payload: accUUID,
});
const receiveUserProfile = (user: User) => ({
type: RECEIVE_USER_PROFILE, payload: user,
});
export const addAccessionToMyList = (accUUID: string) => (dispatch, getState) => {
const myList = getState().user.myList;
if (!myList.accessions.includes(accUUID)) {
dispatch(addAccession(accUUID));
window.localStorage.setItem('myList', JSON.stringify(getState().user.myList));
} else {
console.log(`Can't add accession ${accUUID}. Already in myList`);
}
};
export const removeAccessionFromMyList = (accUUID: string) => (dispatch, getState) => {
const myList = getState().user.myList;
if (myList.accessions.includes(accUUID)) {
dispatch(removeAccession(accUUID));
window.localStorage.setItem('myList', JSON.stringify(getState().user.myList));
} else {
console.log(`Can't remove accession ${accUUID}. Not in myList`);
}
};
export const initMyList = () => (dispatch) => {
if (typeof window !== 'undefined') {
const myList = JSON.parse(window.localStorage.getItem('myList'));
if (myList && myList.accessions) {
return dispatch(initAccessions(myList.accessions));
}
}
};
export const loadUserProfile = () => (dispatch, getState) => {
const token = getState().login.access_token;
return UserProfileService.getProfile(token)
// receive the current user profile
.then((userProfile) => {
return dispatch(receiveUserProfile(userProfile));
})
.catch((error) => {
log('Error', error);
});
};
export const changePassword = (newPassword: string, oldPassword: string) => (dispatch, getState) => {
return UserProfileService.changePassword(getState().login.access_token, newPassword, oldPassword)
.then(() => {
dispatch(showSnackbar('Password was changed successfully'));
}).catch((error) => {
const data = _.get(error, 'response.data');
log('Save error', data.error);
dispatch(showSnackbar(data.error || 'Password change error'));
throw new SubmissionError({ title: 'Password change error', _error: data.error });
});
};
......@@ -4,4 +4,7 @@ const LOGOUT = 'LOGOUT';
const CHECK_TOKEN = 'CHECK_TOKEN';
const VERIFY_GOOGLE_TOKEN = 'VERIFY_GOOGLE_TOKEN';
export {LOGIN_USER, LOGIN_APP, LOGOUT, CHECK_TOKEN, VERIFY_GOOGLE_TOKEN};
const REGISTRATION_FORM = 'Form/Login/REGISTRATION_FORM';
export {LOGIN_USER, LOGIN_APP, LOGOUT, CHECK_TOKEN, VERIFY_GOOGLE_TOKEN, REGISTRATION_FORM};
......@@ -13,7 +13,7 @@ import accessions from './accessions';
import institutes from './institute';
import applicationConfig from './applicationConfig';
import crop from './crop';
import user from './user';
import user from 'user/reducers';
import requests from './requests';
const rootReducer = combineReducers({
......
import {MY_LIST_ACCESSION_ADD, MY_LIST_ACCESSION_INIT, MY_LIST_ACCESSION_REMOVE, RECEIVE_USER_PROFILE} from 'constants/user';
import {COLLAPSE_SIDEBAR} from 'constants/layout';
import update from 'immutability-helper';
import {User} from 'model/User';
const INITIAL_STATE: {
myList: any;
sidebarOpen: boolean;
userProfile: User;
} = {
myList: {
count: 0,
accessions: [],
},
sidebarOpen: true,
userProfile: null,
};
export default function user(state = INITIAL_STATE, action: { type?: string, payload?: any } = {type: '', payload: {}}) {
switch (action.type) {
case MY_LIST_ACCESSION_INIT: {
return update(state, {
myList: {
count: {$set: action.payload.length},
accessions: {$push: action.payload},
},
});
}
case MY_LIST_ACCESSION_ADD: {
return update(state, {
myList: {
count: {$apply: (oldCount) => oldCount + 1},
accessions: {$push: [action.payload]},
},
});
}
case MY_LIST_ACCESSION_REMOVE: {
const index = state.myList.accessions.indexOf(action.payload);
return update(state, {
myList: {
count: {$apply: (oldCount) => oldCount - 1},
accessions: {$splice: [[index, 1]]},
},
});
}
case COLLAPSE_SIDEBAR: {
return update(state, {
sidebarOpen: {$set: action.payload},
});
}
case RECEIVE_USER_PROFILE: {
return update(state, {
userProfile: { $set: action.payload },
});
}
default:
return state;
}
}
import { GET_USER_PROFILE_URL, CHANGE_USER_PASSWORD_URL } from 'constants/apiURLS';
import {API_ROOT, CHANGE_USER_PASSWORD_URL, GET_USER_PROFILE_URL} from 'constants/apiURLS';
import {log} from 'utilities/debug';
import authenticatedRequest from 'utilities/requestUtils';
import {User} from 'model/User';
import axios from 'axios';
const REGISTER_URL = `${API_ROOT}/api/v1/user/register`;
export class UserProfileService {
......@@ -30,4 +33,13 @@ export class UserProfileService {
});
}
public static register(email: string, password: string, fullName: string, captcha: any) {
const form = new FormData();
form.append('email', email);
form.append('pass', password);
form.append('fullName', fullName);
form.append('g-recaptcha-response', captcha);
return axios.post(REGISTER_URL, form)
.then(({data}) => data);
}
}
......@@ -4,7 +4,7 @@ import {bindActionCreators} from 'redux';
import {updateHistory} from 'actions/history';
import {loadCrops} from 'actions/crop';
import {initMyList} from 'actions/user';
import {initMyList} from 'user/actions/root';
import { serverInfoRequest } from 'actions/serverInfo';
import { withRouter } from 'react-router-dom';
......
......@@ -9,7 +9,7 @@ import Card, {CardContent, CardActions} from 'ui/common/Card';
import DOI from 'ui/common/DOI';
import SciName from 'ui/genesys/SciName';
// import CropChips from 'ui/genesys/crop/CropChips';
import {addAccessionToMyList, removeAccessionFromMyList} from 'actions/user';
import {addAccessionToMyList, removeAccessionFromMyList} from 'user/actions/root';
const styles = (theme) => ({
firstRow: {
......@@ -81,7 +81,7 @@ const AccessionCard = ({ accession, classes, index, addAccessionToMyList, remove
};
const mapStateToProps = (state, ownProps) => ({
accessions: state.user.myList.accessions,
accessions: state.user.root.myList.accessions,
});
const mapDispatchToProps = (dispatch) => bindActionCreators({
......
import * as React from 'react';
import {withStyles} from '@material-ui/core/styles';
import {translate} from 'react-i18next';
import {Link} from 'react-router-dom';
import Button from '@material-ui/core/Button';
import Menu from '@material-ui/core/Menu';
import MenuItem from '@material-ui/core/MenuItem';
import ArrowDropDown from '@material-ui/icons/ArrowDropDown';
interface IUserLoginMenuComponentProps extends React.ClassAttributes<any> {
classes: any;
t: any;
}
const styles = {
/* tslint:disable */
userMenuBtn: {
padding: 0,
minWidth: '48px',
'&:hover': {
background: 'inherit',
},
},
linkLogin: {
fontSize: '16px',
color: '#ffffff',
cursor: 'pointer',
'&:hover': {
color: '#88ba42',
textDecoration: 'none',
},
},
menu: {
'& > div+div': {
top: '3.57rem !important',
'html[dir="ltr"] &': {
left: 'auto !important',
right: '1.43rem',
},
'html[dir="rtl"] &': {
right: 'auto !important',
left: '1.43rem',
},
},
'& ul': {
padding: '0.71rem 0',
'& a:focus': {
outline: 'none',
textDecoration: 'none',
},
'& li': {
paddingLeft: '1.43rem',
fontFamily: 'Roboto-Regular',
fontSize: '1rem',
color: '#2B2924',
'&:hover, &:focus': {
background: '#E7E5DF',
},
},
},
},
};
class UserLoginMenuComponent extends React.Component<IUserLoginMenuComponentProps, any> {
public handleClick = (event) => {
this.setState({open: true, anchorEl: event.currentTarget});
};
public handleRequestClose = () => {
this.setState({open: false});
};
public constructor(props: any) {
super(props);
this.state = {
anchorEl: null,
open: false,
};
}
public render() {
const {classes, t} = this.props;
const arrow = {
verticalAlign: 'middle',
};
return (
<div>
<Button
className={ `${classes.userMenuBtn} ${classes.linkLogin} mr-10 ml-10` }
color="secondary"
aria-owns={this.state.open ? 'user-menu' : null}
aria-haspopup="true"
onClick={this.handleClick}
>
Login
<ArrowDropDown style={arrow} viewBox="0 0 16 24"/>
</Button>
<Menu
className={classes.menu}
id="user-menu"
anchorEl={this.state.anchorEl}
open={this.state.open}
onClose={this.handleRequestClose}
onClick={this.handleRequestClose}
>
<Link to="/login"><MenuItem>{t('menu.login')}</MenuItem></Link>
<Link to="/registration"><MenuItem>{t('menu.register')}</MenuItem></Link>
</Menu>
</div>
);
}
}
export default translate()(withStyles(styles)(UserLoginMenuComponent));
......@@ -4,7 +4,8 @@ import {bindActionCreators} from 'redux';
import { NavLink, Link, withRouter } from 'react-router-dom';
import { translate } from 'react-i18next';
import {logoutRequest, loginAppRequest} from 'actions/login';
import {loginAppRequest} from 'actions/login';
import {logoutRequest} from 'user/actions/root';
import AppBar from '@material-ui/core/AppBar';
import { withStyles } from '@material-ui/core/styles';
......@@ -14,6 +15,7 @@ import IconButton from '@material-ui/core/IconButton';
import {ROLE_ADMINISTRATOR, ROLE_CLIENT, ROLE_USER} from 'constants/userRoles';
import {saveCookies} from 'utilities';
import UserLoginMenuComponent from './UserLoginMenu';
import UserMenuComponent from './UserMenuComponent';
import LeftMenu from './LeftMenu';
import LangListComponent from './LangListComponent';
......@@ -138,8 +140,7 @@ class Header extends React.Component<IHeaderProps | any, any> {
if (this.hasRole(roles)) {
return <UserMenuComponent userName={ this.props.login.user_name } logoutRequest={ this.logout }/>;
} else {
const { t } = this.props;
return <Link className={ `${this.props.classes.linkLogin} mr-10 ml-10` } to="/login">{ t('common:action.login') }</Link>;
return <UserLoginMenuComponent />;
}
}
......
......@@ -203,7 +203,7 @@ class SidebarWrapper extends React.Component<ISidebarProps, any> {
}
const mapStateToProps = (state, ownProps) => ({
isOpen: state.user.sidebarOpen,
isOpen: state.user.root.sidebarOpen,
});
const mapDispatchToProps = (dispatch) => bindActionCreators({
......
......@@ -5,7 +5,7 @@ import { translate } from 'react-i18next';
// Actions
import { loadAccession } from 'actions/accessions';
import {addAccessionToMyList, removeAccessionFromMyList} from 'actions/user';
import {addAccessionToMyList, removeAccessionFromMyList} from 'user/actions/root';
// Models
import AccessionDetails from 'model/AccessionDetails';
......@@ -271,7 +271,7 @@ const mapStateToProps = (state, ownProps) => ({
error: state.accessions.accessionError,
uuid: ownProps.match.params.uuid,
doi: ownProps.match.params.doi,
accessions: state.user.myList.accessions,
accessions: state.user.root.myList.accessions,
});
const mapDispatchToProps = (dispatch) => bindActionCreators({
......
......@@ -4,15 +4,15 @@ import App from './App';
import DashboardLayout from 'ui/layout/DashboardLayout';
import AdminLayout from 'ui/layout/AdminLayout';
// User
import {userAdminRoutes, userRoutes, userDashboardRoutes} from 'user/routes';
import DashboardPage from 'ui/pages/dashboard/DashboardPage';
import LoginPage from 'ui/pages/login/LoginPage';
// import AccessionsBrowsePage from 'ui/pages/genesys/BrowsePage';
// Subsets
import SubsetBrowsePage from 'ui/pages/subsets/BrowsePage';
import SubsetDisplayPage from 'ui/pages/subsets/DisplayPage';
import SubsetDashboardPage from 'ui/pages/dashboard/subsets/DashboardPage';
import UserProfilePage from 'ui/pages/dashboard/profile';
import ChangePasswordPage from 'ui/pages/dashboard/profile/ChangePasswordPage';
import SubsetStepper from 'ui/pages/dashboard/subsets/StepperPage';
import BasicInfoStep from 'ui/pages/dashboard/subsets/subset-stepper/steps/basic-info';
import AccessionsListStep from 'ui/pages/dashboard/subsets/subset-stepper/steps/accessions-list';
......@@ -44,16 +44,12 @@ const routes = [
{
component: App,
routes: [
...userRoutes,
{
path: '/',
exact: true,
component: WelcomePage,
},
{
path: '/login',
component: LoginPage,
exact: true,
},
// {