diff --git a/app/app.js b/app/app.js
index 99fe5e9..530a4de 100644
--- a/app/app.js
+++ b/app/app.js
@@ -7,6 +7,7 @@
// Needed for redux-saga es6 generator support
import '@babel/polyfill';
+import './web.config';
// Import all the third party stuff
import React from 'react';
diff --git a/app/components/DialogImage/index.js b/app/components/DialogImage/index.js
new file mode 100644
index 0000000..a45879d
--- /dev/null
+++ b/app/components/DialogImage/index.js
@@ -0,0 +1,96 @@
+import React, { Component } from 'react';
+import {
+ withStyles,
+ Dialog,
+ CardMedia,
+ Paper,
+ CardHeader,
+ Avatar,
+ Divider,
+ IconButton,
+ InputBase,
+ Button,
+ Typography,
+} from '@material-ui/core';
+import MoreHoriz from '@material-ui/icons/MoreHoriz';
+import ava from 'containers/AccountPage/images/3.jpg';
+
+const styles = () => ({
+ media: {
+ width: 600,
+ height: 600,
+ },
+ paper: {
+ height: 600,
+ width: 335,
+ zIndex: 100,
+ },
+ input: {
+ padding: '8px 16px',
+ width: 202,
+ fontSize: 14,
+ },
+ button: {
+ fontSize: 14,
+ },
+});
+
+class DialogImage extends Component {
+ render() {
+ const { classes, closed, handleClose, image, nickname, comments } = this.props;
+
+ return (
+
+ );
+ }
+}
+
+export default withStyles(styles)(DialogImage);
diff --git a/app/components/Header/index.js b/app/components/Header/index.js
index aa92996..f587257 100644
--- a/app/components/Header/index.js
+++ b/app/components/Header/index.js
@@ -1,13 +1,23 @@
import React, { Component } from 'react';
-import { NavLink } from 'react-router-dom';
+import { NavLink, withRouter } from 'react-router-dom';
import AppBar from '@material-ui/core/AppBar';
import Toolbar from '@material-ui/core/Toolbar';
+import IconButton from '@material-ui/core/IconButton';
import Typography from '@material-ui/core/Typography';
-import SearchInput from 'components/SearchInput';
+import jwt from 'jsonwebtoken';
import { withStyles } from '@material-ui/core/styles';
-import AccountCircle from '@material-ui/icons/AccountCircle';
-import FavoriteBorder from '@material-ui/icons/FavoriteBorder';
-import { IconButton } from '@material-ui/core';
+import MenuIcon from '@material-ui/icons/Menu';
+import Popper from '@material-ui/core/Popper';
+import ava from 'containers/AccountPage/images/3.jpg';
+import {
+ Avatar,
+ Button,
+ MenuItem,
+ MenuList,
+ Grow,
+ ClickAwayListener,
+ Paper,
+} from '@material-ui/core';
const styles = () => ({
root: {
@@ -28,8 +38,28 @@ const styles = () => ({
});
class Header extends Component {
+ state = {
+ open: false,
+ };
+
+ handleToggle = () => {
+ this.setState(state => ({ open: !state.open }));
+ };
+
+ handleClose = event => {
+ if (this.anchorEl.contains(event.target)) {
+ return;
+ }
+
+ this.setState({ open: false });
+ };
+
render() {
const { classes } = this.props;
+ const { open } = this.state;
+
+ const fromToken = jwt.decode(localStorage.token, { complete: true });
+
return (
@@ -40,6 +70,7 @@ class Header extends Component {
display: 'flex',
justifyContent: 'space-between',
padding: 0,
+ zIndex: 1000,
}}
>
@@ -47,10 +78,141 @@ class Header extends Component {
Consensus
-
-
-
-
+ {localStorage.token ? (
+
+
+
+ {fromToken.payload.name[0]}
+
+
+
{
+ this.anchorEl = node;
+ }}
+ aria-owns={open ? 'menu-list-grow' : null}
+ aria-haspopup="true"
+ onClick={this.handleToggle}
+ >
+
+
+
+ ) : (
+
+
+ {
+ this.anchorEl = node;
+ }}
+ aria-owns={open ? 'menu-list-grow' : null}
+ aria-haspopup="true"
+ onClick={this.handleToggle}
+ >
+
+
+
+ )}
+
+
+ {({ TransitionProps, placement }) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )}
+
@@ -58,4 +220,6 @@ class Header extends Component {
}
}
-export default withStyles(styles)(Header);
+const Head = withRouter(Header);
+
+export default withStyles(styles)(Head);
diff --git a/app/components/InputText/index.js b/app/components/InputText/index.js
new file mode 100644
index 0000000..14eb74f
--- /dev/null
+++ b/app/components/InputText/index.js
@@ -0,0 +1,45 @@
+import React, { Component } from 'react';
+import { withStyles, InputBase } from '@material-ui/core';
+import { Field } from 'redux-form';
+
+const styles = () => ({
+ input: {
+ padding: '8px 16px',
+ width: '100%',
+ background: '#fafafa',
+ marginTop: '8px',
+ marginBottom: '8px',
+ borderRadius: '5px',
+ border: '#efefef 1px solid',
+ height: '36px',
+ },
+});
+
+const renderTextField = ({ input, placeholder, classes, type }) => (
+ input.onChange(value)}
+ />
+);
+
+class InputText extends Component {
+ render() {
+ const { classes, placeholder, name, type } = this.props;
+ return (
+
+ );
+ }
+}
+export default withStyles(styles)(InputText);
diff --git a/app/components/PostBlock/index.js b/app/components/PostBlock/index.js
index cc60b16..f8a5d3e 100644
--- a/app/components/PostBlock/index.js
+++ b/app/components/PostBlock/index.js
@@ -5,6 +5,7 @@ import {
Typography,
InputBase,
Button,
+ Fade,
} from '@material-ui/core';
import Avatar from '@material-ui/core/Avatar';
import Card from '@material-ui/core/Card';
@@ -16,6 +17,7 @@ import MoreHoriz from '@material-ui/icons/MoreHoriz';
import IconButton from '@material-ui/core/IconButton';
import FavoriteIcon from '@material-ui/icons/Favorite';
+
const styles = theme => ({
root: {
width: 616,
@@ -24,6 +26,7 @@ const styles = theme => ({
avatar: {
width: 28,
height: 28,
+ backgroundColor: '#212121',
},
input: {
padding: '8px 16px',
@@ -47,13 +50,17 @@ const styles = theme => ({
});
class PostBlock extends Component {
+ state = {
+ checked: false,
+ }
render() {
const { classes, nickname, comments, likes, ava, image } = this.props;
+ const {checked} = this.state;
return (
+ K
}
action={
-
-
-
-
- {likes} отметок "Нравится"
-
+
+
+
+
+ { this.setState({checked: !checked}); setTimeout(() => this.setState({checked: false}), 2000)}
+ }/>
+
+
+
+
+
+ {likes} отметок "Нравится"
+
+
{comments ? (
-
+
{comments
? comments.map(item => (
diff --git a/app/components/PostBlockText/index.js b/app/components/PostBlockText/index.js
new file mode 100644
index 0000000..300738b
--- /dev/null
+++ b/app/components/PostBlockText/index.js
@@ -0,0 +1,146 @@
+import React, { Component } from 'react';
+import {
+ withStyles,
+ Divider,
+ Typography,
+ InputBase,
+ Button,
+} from '@material-ui/core';
+import Avatar from '@material-ui/core/Avatar';
+import Card from '@material-ui/core/Card';
+import CardHeader from '@material-ui/core/CardHeader';
+import Paper from '@material-ui/core/Paper';
+import CardActions from '@material-ui/core/CardActions';
+import CardContent from '@material-ui/core/CardContent';
+import MoreHoriz from '@material-ui/icons/MoreHoriz';
+import IconButton from '@material-ui/core/IconButton';
+import FavoriteIcon from '@material-ui/icons/Favorite';
+
+const styles = theme => ({
+ root: {
+ width: 616,
+ marginBottom: '2rem',
+ },
+ avatar: {
+ width: 28,
+ height: 28,
+ backgroundColor: '#212121',
+ },
+ input: {
+ padding: '8px 16px',
+ width: 482,
+ },
+ media: {
+ width: '100%',
+ height: 614,
+ margin: 0,
+ borderRadius: '0',
+ boxShadow: 'none',
+ padding: '1rem',
+ overflowY: 'scroll',
+ },
+ expand: {
+ transform: 'rotate(0deg)',
+ marginLeft: 'auto',
+ transition: theme.transitions.create('transform', {
+ duration: theme.transitions.duration.shortest,
+ }),
+ },
+ expandOpen: {
+ transform: 'rotate(180deg)',
+ },
+});
+
+class PostBlock extends Component {
+ state = {
+ checked: false,
+ };
+
+ render() {
+ const {
+ classes,
+ nickname,
+ comments,
+ likes,
+ title,
+ text,
+ image,
+ } = this.props;
+ const { checked } = this.state;
+ return (
+
+
+ K
+
+ }
+ action={
+
+
+
+ }
+ title={nickname}
+ />
+
+
+
+
+ {title}
+
+ {text}
+
+
+
+
+
+
+
+ {likes} отметок "Нравится"
+
+
+
+ {comments ? (
+
+ {comments
+ ? comments.map(item => (
+
+
+ {item.name}
+ {' '}
+ {item.comment}
+
+ ))
+ : ''}
+
+ ) : (
+ ''
+ )}
+
+
+
+
+ );
+ }
+}
+
+export default withStyles(styles)(PostBlock);
diff --git a/app/containers/AccountPage/actions.js b/app/containers/AccountPage/actions.js
new file mode 100644
index 0000000..0ac9dea
--- /dev/null
+++ b/app/containers/AccountPage/actions.js
@@ -0,0 +1,3 @@
+import { FetchAction } from 'utils/api';
+
+export const fetchUser = new FetchAction('FETCH_USER');
diff --git a/app/containers/AccountPage/components/Tab.js b/app/containers/AccountPage/components/Tab.js
new file mode 100644
index 0000000..d8b7db5
--- /dev/null
+++ b/app/containers/AccountPage/components/Tab.js
@@ -0,0 +1,322 @@
+import React, { Component } from 'react';
+import {
+ withStyles,
+ Avatar,
+ Grid,
+ Typography,
+ Button,
+ IconButton,
+ Dialog,
+ Divider,
+ TextField,
+ Paper,
+} from '@material-ui/core';
+import jwt from 'jsonwebtoken';
+import { connect } from 'react-redux';
+import Settings from '@material-ui/icons/Settings';
+import { fetchLogout } from 'containers/RegisterPage/actions';
+
+const styles = () => ({
+ root: {
+ width: '100%',
+ display: 'flex',
+ justifyContent: 'center',
+ marginBottom: '60px',
+ },
+ avatar: {
+ width: 150,
+ height: 150,
+ backgroundColor: '#212121',
+ },
+ profile: {
+ display: 'box',
+ },
+});
+
+class Tab extends Component {
+ state = {
+ dialoged: false,
+ addNew: false,
+ edited: false,
+ };
+
+ render() {
+ const { classes, ava, name, publication, nickname } = this.props;
+ const fromToken = jwt.decode(localStorage.token, { complete: true })
+ .payload;
+
+ return (
+
+
+ {nickname[0]}
+
+
+
+
+ {nickname}
+
+ {fromToken.name === name ? (
+
+
+
+ this.setState({ dialoged: !this.state.dialoged })
+ }
+ >
+
+
+
+ ) : (
+ ''
+ )}
+
+
+
+ {publication}
+
+ публикаци{publication === 0 ? 'й' : 'я'}
+
+
+
+
+
+ {name}
+
+
+
+
+
+
+
+
+ );
+ }
+}
+
+const styled = withStyles(styles)(Tab);
+
+export default connect(
+ () => ({}),
+ dispatch => ({ fetchLogout: fetchLogout.bindTo(dispatch) }),
+)(styled);
diff --git a/app/containers/AccountPage/images/1.jpg b/app/containers/AccountPage/images/1.jpg
new file mode 100755
index 0000000..61b02b4
Binary files /dev/null and b/app/containers/AccountPage/images/1.jpg differ
diff --git a/app/containers/AccountPage/images/13.jpg b/app/containers/AccountPage/images/13.jpg
new file mode 100644
index 0000000..5b876e1
Binary files /dev/null and b/app/containers/AccountPage/images/13.jpg differ
diff --git a/app/containers/AccountPage/images/2.jpg b/app/containers/AccountPage/images/2.jpg
new file mode 100755
index 0000000..6a8c209
Binary files /dev/null and b/app/containers/AccountPage/images/2.jpg differ
diff --git a/app/containers/AccountPage/images/3.jpg b/app/containers/AccountPage/images/3.jpg
new file mode 100755
index 0000000..2292f8d
Binary files /dev/null and b/app/containers/AccountPage/images/3.jpg differ
diff --git a/app/containers/AccountPage/images/4.png b/app/containers/AccountPage/images/4.png
new file mode 100755
index 0000000..59b4143
Binary files /dev/null and b/app/containers/AccountPage/images/4.png differ
diff --git a/app/containers/AccountPage/index.js b/app/containers/AccountPage/index.js
index e69de29..832b301 100644
--- a/app/containers/AccountPage/index.js
+++ b/app/containers/AccountPage/index.js
@@ -0,0 +1,180 @@
+import React, { Component } from 'react';
+import {
+ Grid,
+ CardMedia,
+ withStyles,
+ CardActionArea,
+ Typography,
+} from '@material-ui/core';
+import { withRouter } from 'react-router';
+import Favorite from '@material-ui/icons/Favorite';
+import Fade from '@material-ui/core/Fade';
+import { connect } from 'react-redux';
+import CircularProgress from '@material-ui/core/CircularProgress';
+import { fetchUser } from 'containers/AccountPage/actions';
+import DialogImage from 'components/DialogImage';
+import { createStructuredSelector } from 'reselect';
+import Tab from './components/Tab';
+import placeholder from './images/1.jpg';
+import placeholder2 from './images/13.jpg';
+import { makeUserSelector } from './selectors';
+
+const styles = () => ({
+ media: {
+ width: 293,
+ height: 293,
+ },
+});
+
+const images = [
+ {
+ img: placeholder,
+ nickname: 'Колористика и цветоведение',
+ likes: 8,
+ comments: [
+ {
+ name: 'Faith',
+ comment: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit.',
+ },
+ {
+ name: 'Faith',
+ comment: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit.',
+ },
+ ],
+ },
+ {
+ img: placeholder2,
+ nickname: 'Колористика и цветоведение',
+ likes: 12930,
+ },
+];
+
+class AccountPage extends Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ hovered: false,
+ closed: false,
+ image: '',
+ nickname: '',
+ comments: [],
+ };
+ this.handleClose = this.handleClose.bind(this);
+ }
+
+ componentWillMount() {
+ this.props.fetchUser.start(this.props.location.pathname.split(/\//)[2]);
+ }
+
+ handleClose(image, nickname, comments) {
+ this.setState({ closed: !this.state.closed });
+ this.setState({ image, nickname, comments });
+ }
+
+ componentDidUpdate(prevProps) {
+ if (this.props.location.pathname !== prevProps.location.pathname) {
+ this.props.fetchUser.start(this.props.location.pathname.split(/\//)[2]);
+ }
+ }
+
+ render() {
+ const { classes, user } = this.props;
+ const { hovered, closed, image, nickname, comments } = this.state;
+
+ return (
+
+ {user !== null ? (
+
+
+
+
+
+ {images.map(item => (
+
+
+ this.handleClose(item.img, item.nickname, item.comments)
+ }
+ onMouseEnter={() => this.setState({ hovered: true })}
+ onMouseLeave={() => this.setState({ hovered: false })}
+ >
+
+
+
+ {' '}
+ {item.likes}
+
+
+
+
+
+
+ ))}
+
+
+
+ ) : (
+
+
+
+ )}
+
+ );
+ }
+}
+
+const styled = withStyles(styles)(AccountPage);
+
+const mapStateToProps = () =>
+ createStructuredSelector({
+ user: makeUserSelector(),
+ });
+
+export default connect(
+ mapStateToProps,
+ dispatch => ({ fetchUser: fetchUser.bindTo(dispatch) }),
+)(withRouter(styled));
diff --git a/app/containers/AccountPage/reducers.js b/app/containers/AccountPage/reducers.js
new file mode 100644
index 0000000..2df5bc8
--- /dev/null
+++ b/app/containers/AccountPage/reducers.js
@@ -0,0 +1,7 @@
+import { combineReducers } from 'redux-immutable';
+import { fetchReducerFactory } from 'utils/api';
+import { fetchUser } from './actions';
+
+export default combineReducers({
+ user: fetchReducerFactory(fetchUser),
+});
diff --git a/app/containers/AccountPage/sagas.js b/app/containers/AccountPage/sagas.js
new file mode 100644
index 0000000..2be584e
--- /dev/null
+++ b/app/containers/AccountPage/sagas.js
@@ -0,0 +1,24 @@
+import { take, call, put } from 'redux-saga/effects';
+import axios from 'axios';
+import config from 'utils/config';
+import { fetchUser } from './actions';
+
+export function* user() {
+ while (true) {
+ try {
+ const action = yield take(fetchUser.types.start);
+ const data = yield call(sendLogin, action.payload);
+ yield put(fetchUser.success(data));
+ } catch (e) {
+ yield put(fetchUser.failed(e));
+ }
+ }
+}
+
+function sendLogin(credentials) {
+ return axios
+ .get(`${config.API_ADDRESS}/users/user`, { params: { id: credentials } })
+ .then(res => res.data);
+}
+
+export default [user];
diff --git a/app/containers/AccountPage/selectors.js b/app/containers/AccountPage/selectors.js
new file mode 100644
index 0000000..b02389d
--- /dev/null
+++ b/app/containers/AccountPage/selectors.js
@@ -0,0 +1,9 @@
+import { createSelector } from 'reselect';
+
+const selectUser = () => state => state.reducer.get('user');
+
+export const makeUserSelector = () =>
+ createSelector(
+ selectUser(),
+ substate => JSON.parse(JSON.stringify(substate.get('data'))),
+ );
diff --git a/app/containers/App/index.js b/app/containers/App/index.js
index 5310d24..627a2fe 100644
--- a/app/containers/App/index.js
+++ b/app/containers/App/index.js
@@ -1,6 +1,10 @@
import React, { Component } from 'react';
import { Switch, Route } from 'react-router-dom';
-
+import { withRouter } from 'react-router';
+import { compose } from 'redux';
+import injectSaga from 'utils/injectSaga';
+import authProviderSaga from 'containers/RegisterPage/sagas';
+import userProviderSaga from 'containers/AccountPage/sagas';
import HomePage from 'containers/HomePage/Loadable';
import NotFoundPage from 'containers/NotFoundPage/Loadable';
@@ -10,10 +14,24 @@ import GlobalStyle from '../../global-styles';
import RegisterPage from '../RegisterPage';
import LoginPage from '../LoginPage';
+import AccountPage from '../AccountPage';
+
const theme = createMuiTheme({
typography: {
useNextVariants: true,
},
+ palette: {
+ primary: {
+ main: '#212121',
+ },
+ },
+ overrides: {
+ MuiDialog: {
+ paper: {
+ maxWidth: '100% !important',
+ },
+ },
+ },
});
class App extends Component {
@@ -38,6 +56,7 @@ class App extends Component {
>
+
@@ -50,4 +69,15 @@ class App extends Component {
}
}
-export default App;
+const withSaga = authProviderSaga.map(saga =>
+ injectSaga({ key: saga.name, saga }),
+);
+
+const AppI = compose(
+ withSaga[0],
+ withSaga[1],
+ withSaga[2],
+ userProviderSaga.map(saga => injectSaga({ key: saga.name, saga }))[0],
+)(App);
+
+export default withRouter(AppI);
diff --git a/app/containers/HomePage/index.js b/app/containers/HomePage/index.js
index fb48db5..98b04c6 100644
--- a/app/containers/HomePage/index.js
+++ b/app/containers/HomePage/index.js
@@ -1,12 +1,13 @@
import React, { Component } from 'react';
import PostBlock from 'components/PostBlock';
-import avatar from './images/ава.jpg';
+import ava from 'containers/AccountPage/images/3.jpg';
+import image2 from 'containers/AccountPage/images/13.jpg';
+import PostBlockText from 'components/PostBlockText';
import image from './images/1.jpg';
-import image2 from './images/2.jpg';
const postArray = [
{
- ava: avatar,
+ ava,
img: image,
nickname: 'Колористика и цветоведение',
likes: 8,
@@ -18,7 +19,14 @@ const postArray = [
],
},
{
- ava: avatar,
+ ava,
+ img: image2,
+ nickname: 'Колористика и цветоведение',
+ likes: 3,
+ },
+ {
+ ava,
+ text: 'aopksdo',
img: image2,
nickname: 'Колористика и цветоведение',
likes: 3,
@@ -29,15 +37,26 @@ class HomePage extends Component {
render() {
return (
- {postArray.map(item => (
-
- ))}
+ {postArray.map(item =>
+ !item.text ? (
+
+ ) : (
+
+ ),
+ )}
);
}
diff --git a/app/containers/LoginPage/index.js b/app/containers/LoginPage/index.js
index 7949d61..8f69ea4 100644
--- a/app/containers/LoginPage/index.js
+++ b/app/containers/LoginPage/index.js
@@ -1,12 +1,11 @@
import React, { Component } from 'react';
-import {
- Paper,
- withStyles,
- Typography,
- InputBase,
- Grid,
- Button,
-} from '@material-ui/core';
+import { reduxForm, formValueSelector } from 'redux-form';
+import { connect } from 'react-redux';
+import axios from 'axios';
+import config from 'utils/config';
+import { fetchLogin } from 'containers/RegisterPage/actions';
+import { Paper, withStyles, Typography, Grid, Button } from '@material-ui/core';
+import InputText from 'components/InputText';
import { NavLink } from 'react-router-dom';
const styles = () => ({
@@ -35,6 +34,21 @@ const styles = () => ({
});
class LoginPage extends Component {
+ constructor(props) {
+ super(props);
+
+ this.onSubmit = this.onSubmit.bind(this);
+ }
+
+ onSubmit() {
+ const data = {
+ email: this.props.email,
+ password: this.props.password,
+ };
+
+ this.props.fetchLogin.start(data);
+ }
+
render() {
const { classes } = this.props;
return (
@@ -60,27 +74,13 @@ class LoginPage extends Component {
alignItems="center"
style={{ marginBottom: '2rem' }}
>
-
-
+
+
@@ -115,4 +115,21 @@ class LoginPage extends Component {
}
}
-export default withStyles(styles)(LoginPage);
+const mapStateToProps = state => {
+ const selector = formValueSelector('signIn', states => states.form);
+ const email = selector(state, 'email');
+ const password = selector(state, 'password');
+ return { email, password };
+};
+
+const Login = reduxForm({
+ form: 'signIn',
+ getFormState: state => state.form,
+})(LoginPage);
+
+const LoginRedux = connect(
+ mapStateToProps,
+ dispatch => ({ fetchLogin: fetchLogin.bindTo(dispatch) }),
+)(Login);
+
+export default withStyles(styles)(LoginRedux);
diff --git a/app/containers/RegisterPage/actions.js b/app/containers/RegisterPage/actions.js
new file mode 100644
index 0000000..a11fb83
--- /dev/null
+++ b/app/containers/RegisterPage/actions.js
@@ -0,0 +1,7 @@
+import { FetchAction } from 'utils/api';
+
+export const fetchRegistration = new FetchAction('FETCH_USER_REGISTRATION');
+
+export const fetchLogin = new FetchAction('LOGIN');
+
+export const fetchLogout = new FetchAction('LOGOUT');
diff --git a/app/containers/RegisterPage/index.js b/app/containers/RegisterPage/index.js
index 555e14f..e63c2ce 100644
--- a/app/containers/RegisterPage/index.js
+++ b/app/containers/RegisterPage/index.js
@@ -1,13 +1,12 @@
import React, { Component } from 'react';
import { NavLink } from 'react-router-dom';
-import {
- Paper,
- withStyles,
- Typography,
- InputBase,
- Grid,
- Button,
-} from '@material-ui/core';
+import { reduxForm, formValueSelector } from 'redux-form';
+import { connect } from 'react-redux';
+import axios from 'axios';
+import config from 'utils/config';
+import { Paper, withStyles, Typography, Grid, Button } from '@material-ui/core';
+import InputText from 'components/InputText';
+import { fetchRegistration } from 'containers/RegisterPage/actions';
const styles = () => ({
root: {
@@ -35,6 +34,23 @@ const styles = () => ({
});
class RegisterPage extends Component {
+ constructor(props) {
+ super(props);
+
+ this.onSubmit = this.onSubmit.bind(this);
+ }
+
+ onSubmit() {
+ const data = {
+ name: this.props.name,
+ nickname: this.props.nickname,
+ email: this.props.email,
+ password: this.props.password,
+ };
+
+ this.props.fetchRegistration.start(data);
+ }
+
render() {
const { classes } = this.props;
return (
@@ -60,40 +76,16 @@ class RegisterPage extends Component {
alignItems="center"
style={{ marginBottom: '2rem' }}
>
-
-
-
-
+
+
+
+
@@ -127,4 +119,23 @@ class RegisterPage extends Component {
}
}
-export default withStyles(styles)(RegisterPage);
+const mapStateToProps = state => {
+ const selector = formValueSelector('register', states => states.form);
+ const name = selector(state, 'name');
+ const nickname = selector(state, 'nickname');
+ const email = selector(state, 'email');
+ const password = selector(state, 'password');
+ return { name, nickname, email, password };
+};
+
+const Register = reduxForm({
+ form: 'register',
+ getFormState: state => state.form,
+})(RegisterPage);
+
+const RegisterRedux = connect(
+ mapStateToProps,
+ dispatch => ({ fetchRegistration: fetchRegistration.bindTo(dispatch) }),
+)(Register);
+
+export default withStyles(styles)(RegisterRedux);
diff --git a/app/containers/RegisterPage/reducers.js b/app/containers/RegisterPage/reducers.js
new file mode 100644
index 0000000..fd65102
--- /dev/null
+++ b/app/containers/RegisterPage/reducers.js
@@ -0,0 +1,9 @@
+import { combineReducers } from 'redux-immutable';
+import { fetchReducerFactory } from 'utils/api';
+import { fetchLogin, fetchRegistration, fetchLogout } from './actions';
+
+export default combineReducers({
+ login: fetchReducerFactory(fetchLogin),
+ logout: fetchReducerFactory(fetchLogout),
+ registration: fetchReducerFactory(fetchRegistration),
+});
diff --git a/app/containers/RegisterPage/sagas.js b/app/containers/RegisterPage/sagas.js
new file mode 100644
index 0000000..76dd926
--- /dev/null
+++ b/app/containers/RegisterPage/sagas.js
@@ -0,0 +1,68 @@
+import { take, call, put } from 'redux-saga/effects';
+import axios from 'axios';
+import config from 'utils/config';
+import { push } from 'react-router-redux';
+import { fetchRegistration, fetchLogin, fetchLogout } from './actions';
+
+export function* login() {
+ while (true) {
+ try {
+ const action = yield take(fetchLogin.types.start);
+ const data = yield call(sendLogin, action.payload);
+ yield call(saveLocal, data);
+ yield put(fetchLogin.success(data));
+ yield put(push(`/account/${data._id}`));
+ } catch (e) {
+ yield put(fetchLogin.failed(e));
+ }
+ }
+}
+
+export function* logout() {
+ while (true) {
+ try {
+ yield take(fetchLogout.types.start);
+ yield call(deleteLocal);
+ yield put(fetchLogout.success());
+ yield put(push(`/`));
+ } catch (e) {
+ yield put(fetchLogout.failed(e));
+ }
+ }
+}
+
+export function* registration() {
+ while (true) {
+ try {
+ const action = yield take(fetchRegistration.types.start);
+ const id = yield call(sendRegistrationData, action.payload);
+ yield put(push(`/login`));
+ yield put(fetchRegistration.success(id));
+ } catch (e) {
+ yield put(fetchRegistration.failed(e));
+ }
+ }
+}
+
+function deleteLocal() {
+ localStorage.removeItem('token');
+}
+
+function saveLocal(data) {
+ localStorage.setItem('token', data.token);
+}
+
+function sendLogin(credentials) {
+ return axios
+ .get(`${config.API_ADDRESS}/users/login`, { params: credentials })
+ .then(res => res.data);
+}
+
+function sendRegistrationData(data) {
+ return axios
+ .post(`${config.API_ADDRESS}/users/register`, data)
+ .then(res => res.data);
+}
+
+// // All sagas to be loaded
+export default [login, logout, registration];
diff --git a/app/containers/RegisterPage/selectors.js b/app/containers/RegisterPage/selectors.js
new file mode 100644
index 0000000..e69de29
diff --git a/app/reducers.js b/app/reducers.js
index 7ba4220..4762bc8 100644
--- a/app/reducers.js
+++ b/app/reducers.js
@@ -4,6 +4,9 @@
import { combineReducers } from 'redux';
import { connectRouter } from 'connected-react-router';
+import { reducer as formReducer } from 'redux-form';
+import authProviderReducer from 'containers/RegisterPage/reducers';
+import userReducer from 'containers/AccountPage/reducers';
import history from 'utils/history';
import languageProviderReducer from 'containers/LanguageProvider/reducer';
@@ -15,7 +18,10 @@ export default function createReducer(injectedReducers = {}) {
const rootReducer = combineReducers({
language: languageProviderReducer,
router: connectRouter(history),
+ auth: authProviderReducer,
+ reducer: userReducer,
...injectedReducers,
+ form: formReducer,
});
return rootReducer;
diff --git a/app/utils/api.js b/app/utils/api.js
new file mode 100644
index 0000000..9692b88
--- /dev/null
+++ b/app/utils/api.js
@@ -0,0 +1,61 @@
+import { fromJS } from 'immutable';
+import { createAction, createReducer } from 'redux-act';
+
+export class FetchAction {
+ constructor(resource, method, prePare = data => data) {
+ this.resource = resource;
+ this.method = method;
+ this.prePare = prePare;
+ this.types = {
+ start: `${this.resource}_START`,
+ success: `${this.resource}_SUCCESS`,
+ failed: `${this.resource}_FAILED`,
+ };
+ this.start = createAction(this.types.start);
+ this.success = createAction(this.types.success, this.prePare);
+ this.failed = createAction(this.types.failed, this.convertError);
+ }
+
+ convertError({ data, status }) {
+ return {
+ data: data
+ ? data.error || data.message || data.errors || data
+ : 'no data',
+ status: status || 0,
+ };
+ }
+
+ bindTo(dispatch) {
+ this.success = this.success.bindTo(dispatch);
+ this.start = this.start.bindTo(dispatch);
+ this.failed = this.failed.bindTo(dispatch);
+ return this;
+ }
+}
+
+export function fetchReducerFactory(Action, initState) {
+ const initialState = fromJS(
+ Object.assign({ data: null, pending: false, error: false }, initState),
+ );
+ return createReducer(
+ {
+ [Action.start]: (state, payload) =>
+ state
+ .set('pending', true)
+ .set('req', fromJS(payload))
+ .set('status', 'pending'),
+ [Action.success]: (state, payload) =>
+ state
+ .set('pending', false)
+ .set('data', fromJS(payload))
+ .set('status', 'success')
+ .set('error', null),
+ [Action.failed]: (state, payload) =>
+ state
+ .set('pending', false)
+ .set('error', fromJS(payload))
+ .set('status', 'failed'),
+ },
+ initialState,
+ );
+}
diff --git a/app/utils/config.js b/app/utils/config.js
new file mode 100644
index 0000000..a998153
--- /dev/null
+++ b/app/utils/config.js
@@ -0,0 +1,8 @@
+let config = null; //eslint-disable-line
+
+config = {
+ API_ADDRESS: 'http://localhost:3000',
+ HOST_ADDRESS: 'http://localhost:3000',
+};
+
+export default config;
diff --git a/app/web.config b/app/web.config
new file mode 100644
index 0000000..a20eac2
--- /dev/null
+++ b/app/web.config
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/internals/webpack/webpack.base.babel.js b/internals/webpack/webpack.base.babel.js
index 5dfd5ec..3db113b 100644
--- a/internals/webpack/webpack.base.babel.js
+++ b/internals/webpack/webpack.base.babel.js
@@ -27,6 +27,10 @@ module.exports = options => ({
options: options.babelQuery,
},
},
+ {
+ test: /\.(config)$/,
+ loader: 'file-loader?name=[name].[ext]',
+ },
{
// Preprocess our own .css files
// This is the place to add your own loaders (e.g. sass/less etc.)
diff --git a/package-lock.json b/package-lock.json
index 8c131ac..e36ec0f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2863,6 +2863,22 @@
"integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==",
"dev": true
},
+ "axios": {
+ "version": "0.19.0",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.0.tgz",
+ "integrity": "sha512-1uvKqKQta3KBxIz14F2v06AEHZ/dIoeKfbTRkK1E5oqjDnuEerLmYTgJB5AiQZHJcljpg1TuRzdjDR06qNk0DQ==",
+ "requires": {
+ "follow-redirects": "1.5.10",
+ "is-buffer": "^2.0.2"
+ },
+ "dependencies": {
+ "is-buffer": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz",
+ "integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw=="
+ }
+ }
+ },
"axobject-query": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.0.2.tgz",
@@ -3387,7 +3403,7 @@
},
"p-cancelable": {
"version": "0.4.1",
- "resolved": "http://registry.npmjs.org/p-cancelable/-/p-cancelable-0.4.1.tgz",
+ "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.4.1.tgz",
"integrity": "sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ==",
"dev": true
},
@@ -3715,6 +3731,11 @@
"integrity": "sha1-kbx0sR6kBbyRa8aqkI+q+ltKrEs=",
"dev": true
},
+ "buffer-equal-constant-time": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
+ "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk="
+ },
"buffer-fill": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz",
@@ -4457,6 +4478,13 @@
"immutable": "^3.8.1",
"prop-types": "^15.7.2",
"seamless-immutable": "^7.1.3"
+ },
+ "dependencies": {
+ "immutable": {
+ "version": "3.8.2",
+ "resolved": "https://registry.npmjs.org/immutable/-/immutable-3.8.2.tgz",
+ "integrity": "sha1-wkOZUUVbs5kT2vKBN28VMOEErfM="
+ }
}
},
"console-browserify": {
@@ -5122,7 +5150,7 @@
"dependencies": {
"pify": {
"version": "2.3.0",
- "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
"dev": true
}
@@ -5210,13 +5238,13 @@
"dependencies": {
"file-type": {
"version": "3.9.0",
- "resolved": "http://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz",
+ "resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz",
"integrity": "sha1-JXoHg4TR24CHvESdEH1SpSZyuek=",
"dev": true
},
"get-stream": {
"version": "2.3.1",
- "resolved": "http://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz",
"integrity": "sha1-Xzj5PzRgCWZu4BUKBUFn+Rvdld4=",
"dev": true,
"requires": {
@@ -5226,7 +5254,7 @@
},
"pify": {
"version": "2.3.0",
- "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
"dev": true
}
@@ -5368,7 +5396,7 @@
"dependencies": {
"globby": {
"version": "6.1.0",
- "resolved": "http://registry.npmjs.org/globby/-/globby-6.1.0.tgz",
+ "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz",
"integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=",
"dev": true,
"requires": {
@@ -5381,7 +5409,7 @@
"dependencies": {
"pify": {
"version": "2.3.0",
- "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
"dev": true
}
@@ -5653,6 +5681,14 @@
"safer-buffer": "^2.1.0"
}
},
+ "ecdsa-sig-formatter": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
+ "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
+ "requires": {
+ "safe-buffer": "^5.0.1"
+ }
+ },
"ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@@ -5784,6 +5820,11 @@
"is-symbol": "^1.0.1"
}
},
+ "es6-error": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz",
+ "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg=="
+ },
"es6-promise": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz",
@@ -6294,7 +6335,7 @@
"dependencies": {
"pify": {
"version": "2.3.0",
- "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
"dev": true
}
@@ -6996,6 +7037,24 @@
"integrity": "sha1-UhTXU3pNBqSjAcDMJi/rhBiAAuc=",
"dev": true
},
+ "follow-redirects": {
+ "version": "1.5.10",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz",
+ "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==",
+ "requires": {
+ "debug": "=3.1.0"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+ "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+ "requires": {
+ "ms": "2.0.0"
+ }
+ }
+ }
+ },
"fontfaceobserver": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fontfaceobserver/-/fontfaceobserver-2.1.0.tgz",
@@ -7121,7 +7180,8 @@
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"aproba": {
"version": "1.2.0",
@@ -7142,12 +7202,14 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -7162,17 +7224,20 @@
"code-point-at": {
"version": "1.1.0",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"core-util-is": {
"version": "1.0.2",
@@ -7289,7 +7354,8 @@
"inherits": {
"version": "2.0.3",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"ini": {
"version": "1.3.5",
@@ -7301,6 +7367,7 @@
"version": "1.0.0",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@@ -7315,6 +7382,7 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@@ -7322,12 +7390,14 @@
"minimist": {
"version": "0.0.8",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"minipass": {
"version": "2.3.5",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@@ -7346,6 +7416,7 @@
"version": "0.5.1",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"minimist": "0.0.8"
}
@@ -7426,7 +7497,8 @@
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"object-assign": {
"version": "4.1.1",
@@ -7438,6 +7510,7 @@
"version": "1.4.0",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"wrappy": "1"
}
@@ -7523,7 +7596,8 @@
"safe-buffer": {
"version": "5.1.2",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"safer-buffer": {
"version": "2.1.2",
@@ -7559,6 +7633,7 @@
"version": "1.0.2",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@@ -7578,6 +7653,7 @@
"version": "3.0.1",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@@ -7621,12 +7697,14 @@
"wrappy": {
"version": "1.0.2",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"yallist": {
"version": "3.0.3",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
}
}
},
@@ -8526,9 +8604,9 @@
"integrity": "sha512-dqLrAUJz/G7vfcuQG6GdjOGm+5Mw9YJEvDRbEwHwzBPBRziZYevOq2P1pF//B07CG1l28ZsjSqLz+RziObkaDQ=="
},
"immutable": {
- "version": "3.8.2",
- "resolved": "https://registry.npmjs.org/immutable/-/immutable-3.8.2.tgz",
- "integrity": "sha1-wkOZUUVbs5kT2vKBN28VMOEErfM="
+ "version": "4.0.0-rc.12",
+ "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.0.0-rc.12.tgz",
+ "integrity": "sha512-0M2XxkZLx/mi3t8NVwIm1g8nHoEmM9p9UBl/G9k4+hm0kBgOVdMV/B3CY5dQ8qG8qc80NN4gDV4HQv6FTJ5q7A=="
},
"import-fresh": {
"version": "3.0.0",
@@ -9131,8 +9209,7 @@
"is-promise": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz",
- "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=",
- "dev": true
+ "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o="
},
"is-regex": {
"version": "1.0.4",
@@ -10454,6 +10531,11 @@
}
}
},
+ "jose-jwe-jws": {
+ "version": "0.1.7",
+ "resolved": "https://registry.npmjs.org/jose-jwe-jws/-/jose-jwe-jws-0.1.7.tgz",
+ "integrity": "sha512-R2Kppec2vbCcGblCAbshca3Ib0W+x2ZmRppD/IgjcAMEkuBJif2by8oTSFz5GZXp+fWAQ3l9AGMFGmMMR4ff1w=="
+ },
"jpeg-js": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.2.0.tgz",
@@ -10577,6 +10659,35 @@
"integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=",
"dev": true
},
+ "jsonwebtoken": {
+ "version": "8.5.1",
+ "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz",
+ "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==",
+ "requires": {
+ "jws": "^3.2.2",
+ "lodash.includes": "^4.3.0",
+ "lodash.isboolean": "^3.0.3",
+ "lodash.isinteger": "^4.0.4",
+ "lodash.isnumber": "^3.0.3",
+ "lodash.isplainobject": "^4.0.6",
+ "lodash.isstring": "^4.0.1",
+ "lodash.once": "^4.0.0",
+ "ms": "^2.1.1",
+ "semver": "^5.6.0"
+ },
+ "dependencies": {
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+ },
+ "semver": {
+ "version": "5.7.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz",
+ "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA=="
+ }
+ }
+ },
"jsprim": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
@@ -10794,6 +10905,25 @@
"array-includes": "^3.0.3"
}
},
+ "jwa": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
+ "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
+ "requires": {
+ "buffer-equal-constant-time": "1.0.1",
+ "ecdsa-sig-formatter": "1.0.11",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "jws": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
+ "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
+ "requires": {
+ "jwa": "^1.4.1",
+ "safe-buffer": "^5.0.1"
+ }
+ },
"keyv": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-3.0.0.tgz",
@@ -11003,7 +11133,7 @@
},
"chalk": {
"version": "1.1.3",
- "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
"dev": true,
"requires": {
@@ -11123,9 +11253,14 @@
}
},
"lodash": {
+ "version": "4.17.13",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.13.tgz",
+ "integrity": "sha512-vm3/XWXfWtRua0FkUyEHBZy8kCPjErNBT9fJx8Zvs+U6zjqPbTUOpkaoum3O5uiA8sm+yNMHXfYkTUHFoMxFNA=="
+ },
+ "lodash-es": {
"version": "4.17.11",
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
- "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg=="
+ "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.11.tgz",
+ "integrity": "sha512-DHb1ub+rMjjrxqlB3H56/6MXtm1lSksDp2rA2cNWjG8mlDUYFhUj3Di2Zn5IwSU87xLv8tNIQ7sSwE/YOX/D/Q=="
},
"lodash.get": {
"version": "4.4.2",
@@ -11133,6 +11268,41 @@
"integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=",
"dev": true
},
+ "lodash.includes": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
+ "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8="
+ },
+ "lodash.isboolean": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
+ "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY="
+ },
+ "lodash.isinteger": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
+ "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M="
+ },
+ "lodash.isnumber": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
+ "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w="
+ },
+ "lodash.isplainobject": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
+ "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs="
+ },
+ "lodash.isstring": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
+ "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE="
+ },
+ "lodash.once": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
+ "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w="
+ },
"lodash.sortby": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz",
@@ -12590,7 +12760,7 @@
},
"p-is-promise": {
"version": "1.1.0",
- "resolved": "http://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz",
+ "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz",
"integrity": "sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4=",
"dev": true
},
@@ -13003,7 +13173,7 @@
},
"chalk": {
"version": "1.1.3",
- "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
"dev": true,
"requires": {
@@ -13843,6 +14013,11 @@
"tiny-warning": "^1.0.0"
}
},
+ "react-router-redux": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/react-router-redux/-/react-router-redux-4.0.8.tgz",
+ "integrity": "sha1-InQDWWtRUeGCN32rg1tdRfD4BU4="
+ },
"react-side-effect": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/react-side-effect/-/react-side-effect-1.1.5.tgz",
@@ -14072,6 +14247,49 @@
"symbol-observable": "^1.2.0"
}
},
+ "redux-act": {
+ "version": "1.7.6",
+ "resolved": "https://registry.npmjs.org/redux-act/-/redux-act-1.7.6.tgz",
+ "integrity": "sha512-JF6p/UaiG93y52hW5Df1ttYnETb8tYZPVNcbAXJshkRzTqrTdtOzke17xQNHlFf+V5YrqBYH3gAJDwv8hB47nQ=="
+ },
+ "redux-form": {
+ "version": "8.2.4",
+ "resolved": "https://registry.npmjs.org/redux-form/-/redux-form-8.2.4.tgz",
+ "integrity": "sha512-+MMD5XWVUgrtgXBuQiIYtHstPT7Lz0Izuc6vqBr1S9+7HbGgvJg4V5qDr2nigE1V5hKgsqnoAmWOWtzXc9ErZA==",
+ "requires": {
+ "@babel/runtime": "^7.2.0",
+ "es6-error": "^4.1.1",
+ "hoist-non-react-statics": "^3.2.1",
+ "immutable": "*",
+ "invariant": "^2.2.4",
+ "is-promise": "^2.1.0",
+ "lodash": "^4.17.11",
+ "lodash-es": "^4.17.11",
+ "prop-types": "^15.6.1",
+ "react-is": "^16.7.0",
+ "react-lifecycles-compat": "^3.0.4"
+ },
+ "dependencies": {
+ "@babel/runtime": {
+ "version": "7.4.5",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.4.5.tgz",
+ "integrity": "sha512-TuI4qpWZP6lGOGIuGWtp9sPluqYICmbk8T/1vpSysqJxRPkudh/ofFWyqdcMsDf2s7KvDL4/YHgKyvcS3g9CJQ==",
+ "requires": {
+ "regenerator-runtime": "^0.13.2"
+ }
+ },
+ "regenerator-runtime": {
+ "version": "0.13.2",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz",
+ "integrity": "sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA=="
+ }
+ }
+ },
+ "redux-immutable": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/redux-immutable/-/redux-immutable-4.0.0.tgz",
+ "integrity": "sha1-Ohoy32Y2ZGK2NpHw4dw15HK7yfM="
+ },
"redux-saga": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/redux-saga/-/redux-saga-1.0.2.tgz",
@@ -15152,7 +15370,7 @@
},
"chalk": {
"version": "1.1.3",
- "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
"dev": true,
"requires": {
diff --git a/package.json b/package.json
index 779a576..169fa3a 100644
--- a/package.json
+++ b/package.json
@@ -62,6 +62,7 @@
"@babel/polyfill": "7.4.3",
"@material-ui/core": "^4.1.1",
"@material-ui/icons": "^4.2.0",
+ "axios": "^0.19.0",
"chalk": "^2.4.2",
"compression": "1.7.4",
"connected-react-router": "6.4.0",
@@ -72,10 +73,13 @@
"history": "4.9.0",
"hoist-non-react-statics": "3.3.0",
"immer": "3.0.0",
+ "immutable": "^4.0.0-rc.12",
"intl": "1.2.5",
"invariant": "2.2.4",
"ip": "1.1.5",
- "lodash": "4.17.11",
+ "jose-jwe-jws": "^0.1.7",
+ "jsonwebtoken": "^8.5.1",
+ "lodash": "4.17.13",
"minimist": "1.2.0",
"prop-types": "15.7.2",
"react": "16.8.6",
@@ -84,7 +88,11 @@
"react-intl": "2.8.0",
"react-redux": "7.0.2",
"react-router-dom": "5.0.0",
+ "react-router-redux": "^4.0.8",
"redux": "4.0.1",
+ "redux-act": "^1.7.6",
+ "redux-form": "^8.2.4",
+ "redux-immutable": "^4.0.0",
"redux-saga": "1.0.2",
"reselect": "4.0.0",
"sanitize.css": "8.0.0",
diff --git a/server/port.js b/server/port.js
index a5f1179..449901a 100644
--- a/server/port.js
+++ b/server/port.js
@@ -1,3 +1,3 @@
const argv = require('./argv');
-module.exports = parseInt(argv.port || process.env.PORT || '3000', 10);
+module.exports = parseInt(argv.port || process.env.PORT || '3001', 10);