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 ( + +
+ + + {nickname[0]} + } + action={ + + + + } + title={nickname} + /> + + + {comments + ? comments.map(item => ( + + + {item.name} + {' '} + {item.comment} + + )) + : ''} + {comments ? : ""} + +
+ + +
+
+
+
+ ); + } +} + +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} + +
+
+ this.setState({ dialoged: !this.state.dialoged })} + open={this.state.dialoged} + > + + + + + + + + + this.setState({ + addNew: !this.state.addNew, + dialoged: !this.state.dialoged, + }) + } + open={this.state.addNew} + > + + + + + + + this.setState({ + edited: !this.state.edited, + }) + } + > + + + Редактирование профиля + + + + + + + + + + + + +
+ ); + } +} + +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);