diff --git a/src/actions/actions.js b/src/actions/actions.js index 821e726..7ab6eeb 100644 --- a/src/actions/actions.js +++ b/src/actions/actions.js @@ -1,5 +1,6 @@ import fetch from 'isomorphic-fetch'; import { config } from '../config.js'; +import {browserHistory} from 'react-router'; /* * action creators */ @@ -25,9 +26,10 @@ export function setPosts(posts) { }; } -export function postsFetchData(url) { +export function postsFetchData() { return (dispatch) => { dispatch(postsIsLoading(true)); + const url = config.url.posts; fetch(url) .then((response) => { @@ -67,7 +69,8 @@ export function getFilteredPosts() { const title = post.title.toLowerCase(); const body = post.body.toLowerCase(); return (title.indexOf(searchedPhrase) >= 0 || body.indexOf(searchedPhrase) >= 0); - } + } else + return false; }); } @@ -84,10 +87,12 @@ export function changeSearchedPhrase(searchedPhrase) { export function deletePostAction(postId) { return (dispatch, getState) => { - const url = `${config.url}/${postId}`; + const url = `${config.url.posts}/${postId}`; fetch(url, {method: 'DELETE'}) .then( () => { + postId = getState().postToDelete; + const posts = getState().posts; posts.forEach((item, index) => { @@ -98,13 +103,14 @@ export function deletePostAction(postId) { dispatch(postsFilter(posts)); dispatch(setPosts(posts)); + browserHistory.push('/'); }); } } -export function choosePostToDelete(postToDelete) { +export function choosePostToDelete(postToDelete) { return { type: 'CHOOSE_POST_TO_DELETE', postToDelete }; -} \ No newline at end of file +} diff --git a/src/actions/auth.js b/src/actions/auth.js new file mode 100644 index 0000000..27855d4 --- /dev/null +++ b/src/actions/auth.js @@ -0,0 +1,121 @@ +import {config} from "../config"; + +export function setIsLogged(bool) { + + return { + type: 'SET_IS_LOGGED', + isLogged: bool + }; +} + +export function setToken(token) { + return { + type: 'SET_TOKEN', + token + }; +} + +export function setUserData(userData) { + return { + type: 'SET_USER_DATA', + userData + }; +} + +export function signIn(login, password) { + return (dispatch) => { + dispatch(fetchSignIn(login,password)); + } +} + +export function fetchSignIn(login, password) { + return (dispatch, getState) => { + const data = { + login, + password + } + + const myParams = Object.keys(data).map((key) => { + return `${encodeURIComponent(key)}=${encodeURIComponent(data[key])}`; + }).join('&'); + + const fetchData = { + method: 'POST', + body: myParams, + //credentials: 'include', + headers: { + //"Content-Type": "application/json" + 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8', + 'Access-Control-Allow-Origin': 'http://localhost:3003' + } + } + + const url = config.url.login; + + fetch(url, fetchData) + .then((response) => { + + if (!response.ok) { + throw Error(response.statusText); + } + return response; + }) + .then( (response) => response.json() ) + .then( (json) => { + dispatch(setToken(json.token)); + dispatch(setIsLogged(true)); + dispatch(fetchUserData(json.token)); + localStorage.setItem('reactAppToken', json.token); + }); + + } +} + +export function checkIfLogged() { + return (dispatch) => { + const token = localStorage.getItem('reactAppToken'); + + if (token !== 'null') { + dispatch(setIsLogged(true)); + dispatch(setToken(token)); + dispatch(fetchUserData(token)) + } else { + dispatch(setIsLogged(false)); + } + } +} + +export function fetchUserData(token) { + return (dispatch) => { + const url = config.url.auth; + + const fetchData = { + method: 'GET', + headers: { + authorization: token, + }, + } + + fetch(url, fetchData) + .then((response) => { + if (!response.ok) { + + throw Error(response.statusText); + } + return response; + }) + .then((response) => response.json()) + .then((json) => { + dispatch(setUserData(json)) + }); + }; +} + +export function logOut() { + return (dispatch) => { + localStorage.removeItem('reactAppToken'); + dispatch(setToken('')); + dispatch(setIsLogged(false)); + dispatch(setUserData({})); + } +} diff --git a/src/actions/postPage.js b/src/actions/postPage.js index 4c34f18..d11369c 100644 --- a/src/actions/postPage.js +++ b/src/actions/postPage.js @@ -1,5 +1,6 @@ import fetch from 'isomorphic-fetch'; import { config } from '../config.js'; +import {browserHistory} from 'react-router'; export function setPostTitle(title) { return { @@ -24,7 +25,7 @@ export function setPostUser(user) { export function getPostData(postId) { return (dispatch) => { - const url = `${config.url}/${postId}`; + const url = `${config.url.posts}/${postId}`; fetch(url) .then( (response) => response.json() ) .then( (json) => @@ -46,7 +47,7 @@ export function setPostComments(comments) { export function getPostComments(postId) { return (dispatch) => { - const url = `${config.url}/${postId}/comments`; + const url = `${config.url.posts}/${postId}/comments`; fetch(url) .then( (response) => response.json() ) .then( (json) => @@ -90,7 +91,7 @@ export function addPost() { } } - fetch(config.url, fetchData) + fetch(config.url.posts, fetchData) .then( (response) => response.json() ) .then( (json) => { const posts = getState().posts; @@ -101,7 +102,7 @@ export function addPost() { dispatch({ type: 'INCREMENT_LAST_POST_ID' }) clearForm(dispatch); - + browserHistory.push('/'); //this.setState({info: `Post #${json.id} was saved.`}) }); @@ -132,22 +133,57 @@ export function updatePost(postId) { } } - const url = `${config.url}/${postId}`; + const url = `${config.url.posts}/${postId}`; fetch(url, fetchData) .then( (response) => { let posts = getState().posts; - posts = posts.filter( (post) => { - if (post.id == postId) { + posts.filter( (post) => { + if (post.id === postId) { post.title = fetchData.body.title; post.body = fetchData.body.body; post.userId = fetchData.body.userId return true; } + else return false; }); + browserHistory.push('/'); //const info = (response.ok) ? 'Changes in post was saved.' : 'Error!' //this.setState({info}) }); } } + +export function setUsers(users) { + return { + type: 'SET_USERS', + users + } +} + +export function fetchUsers() { + return (dispatch, getState) => { + const url = config.url.users; + const token = getState().token; + + const fetchData = { + method: 'GET', + headers: { + authorization: token, + } + } + + fetch(url, fetchData) + .then((response) => { + if (!response.ok) { + throw Error(response.statusText); + } + return response; + }) + .then((response) => response.json()) + .then((json) => { + dispatch(setUsers(json)); + }); + }; +} diff --git a/src/actions/validation.js b/src/actions/validation.js new file mode 100644 index 0000000..158ef02 --- /dev/null +++ b/src/actions/validation.js @@ -0,0 +1,38 @@ +import { config } from "../config"; +import { + +} from './actions.js'; + +export function setLoginFormValidation(formLoginIsValid, error) { + return { + type: 'SET_LOGIN_FORM_VALID', + formLoginIsValid + } +} + +export function validateLoginForm(login, password) { + return (dispatch) => { + const errors = []; + let result = true; + + const loginIsValid = validateInputText(login); + if (!loginIsValid) { + result = false; + errors.push(config.messages.empty_login); + } + + const passwordIsValid = validateInputText(password); + if (!passwordIsValid) { + dispatch(setLoginFormValidation(passwordIsValid)); + result = false; + errors.push(config.messages.empty_password); + } + //dispatch(setInfo(errors)); + dispatch(setLoginFormValidation(true)); + return result; + } +} + +export function validateInputText(value) { + return !(value.length === 0); +} diff --git a/src/components/App/logo.svg b/src/components/App/logo.svg deleted file mode 100755 index 6b60c10..0000000 --- a/src/components/App/logo.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/components/AuthComponent/AuthComponent.js b/src/components/AuthComponent/AuthComponent.js new file mode 100644 index 0000000..1c67535 --- /dev/null +++ b/src/components/AuthComponent/AuthComponent.js @@ -0,0 +1,42 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import { browserHistory } from 'react-router'; +import PropTypes from 'prop-types'; +import { checkIfLogged } from '../../actions/auth'; + +export function requireAuthentication(Component) { + + class AuthComponent extends React.Component { + static propTypes = { + isLogged: PropTypes.bool.isRequired, + checkIfLogged: PropTypes.func.isRequired + } + + componentWillMount() { + this.props.checkIfLogged(); + if (!this.props.isLogged) { + browserHistory.push('/login'); + } + } + + render() { + return ( + this.props.isLogged && + ) + } + } + + const mapStateToProps = + (state) => ({ + token: state.token, + isLogged: state.isLogged + }); + + const mapDispatchToProps = (dispatch) => { + return { + checkIfLogged: () => dispatch(checkIfLogged()) + }; + } + + return connect(mapStateToProps, mapDispatchToProps)(AuthComponent); +} diff --git a/src/components/App/App.scss b/src/components/AuthComponent/AuthComponent.scss similarity index 100% rename from src/components/App/App.scss rename to src/components/AuthComponent/AuthComponent.scss diff --git a/src/components/Header/Header.js b/src/components/Header/Header.js index f776590..c197127 100755 --- a/src/components/Header/Header.js +++ b/src/components/Header/Header.js @@ -4,19 +4,44 @@ import Logo from '../Logo/Logo'; import PropTypes from 'prop-types'; import logo from './logo.svg'; import classNames from 'classnames'; +import { connect } from "react-redux"; +import UserPanel from '../UserPanel/UserPanel'; +import { + signIn +} from '../../actions/auth'; class Header extends React.Component { static propTypes = { - title: PropTypes.string.isRequired + title: PropTypes.string.isRequired, + isLogged: PropTypes.bool.isRequired, } + render() { return (
-

{this.props.title}

+

{this.props.title}

+
); } } -export default Header; +//export default Header; + +const mapStateToProps = (state) => { + return { + isLogged: state.isLogged, + }; +} + +const mapDispatchToProps = (dispatch) => { + return { + signIn: (login) => dispatch(signIn(login)) + }; +} + +export default connect( + mapStateToProps, + mapDispatchToProps +)(Header); \ No newline at end of file diff --git a/src/components/App/App.js b/src/components/Layout/Layout.js similarity index 57% rename from src/components/App/App.js rename to src/components/Layout/Layout.js index f315898..cde84ff 100644 --- a/src/components/App/App.js +++ b/src/components/Layout/Layout.js @@ -1,17 +1,21 @@ import React from 'react'; +import PropTypes from 'prop-types'; import Header from '../Header/Header'; import Footer from '../Footer/Footer'; -import getRoutes from '../../routes'; -class App extends React.Component { +class Layout extends React.Component { + static propTypes = { + children: PropTypes.node + } + render() { return (
- { getRoutes() } + { this.props.children }
); } } -export default App; +export default Layout; diff --git a/src/components/Layout/Layout.scss b/src/components/Layout/Layout.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/components/LoginPage/LoginPage.js b/src/components/LoginPage/LoginPage.js new file mode 100644 index 0000000..c49bf50 --- /dev/null +++ b/src/components/LoginPage/LoginPage.js @@ -0,0 +1,73 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; +import './LoginPage.css'; +import { connect } from "react-redux"; +import { + signIn +} from '../../actions/auth'; +import { browserHistory } from 'react-router'; +import Layout from "../Layout/Layout"; + + +class LoginPage extends React.Component { + constructor(props){ + super(props); + this.state={ + login: '', + password: '' + } + } + + static propTypes = { + isLogged: PropTypes.bool.isRequired, + login: PropTypes.string, + signIn: PropTypes.func.isRequired + } + + onHandleButton() { + this.props.signIn(this.state.login, this.state.password); + browserHistory.push('/'); + } + + render() { + return ( + +
+

Log in

+ this.setState({ login: event.target.value }) } + /> + + this.setState({ password: event.target.value }) } + /> + + +
+
+ ); + } +} + +const mapStateToProps = (state) => { + return { + isLogged: state.isLogged, + login: state.login + }; +} + +const mapDispatchToProps = (dispatch) => { + return { + signIn: (login, password) => dispatch(signIn(login, password)) + }; +} + +export default connect( + mapStateToProps, + mapDispatchToProps +)(LoginPage); diff --git a/src/components/LoginPage/LoginPage.scss b/src/components/LoginPage/LoginPage.scss new file mode 100644 index 0000000..6b64cd4 --- /dev/null +++ b/src/components/LoginPage/LoginPage.scss @@ -0,0 +1,7 @@ +.LoginPage { + max-width: 350px; + margin: auto; + input, button { + margin: 20px; + } +} \ No newline at end of file diff --git a/src/components/Page/Page.js b/src/components/Page/Page.js index 84d33f7..f9261a8 100755 --- a/src/components/Page/Page.js +++ b/src/components/Page/Page.js @@ -3,8 +3,8 @@ import { Link } from 'react-router'; import './Page.css'; import Post from '../Post/Post'; import Modal from '../Modal/Modal'; +import Layout from '../Layout/Layout'; import Search from '../Search/Search'; -import { config } from '../../config.js'; import classNames from 'classnames'; import { debounce } from 'throttle-debounce'; import PropTypes from 'prop-types'; @@ -32,7 +32,9 @@ class Page extends React.Component { changePhrase: PropTypes.func.isRequired, getSearchedPosts: PropTypes.func.isRequired, setPostToDelete: PropTypes.func.isRequired, - deletePost: PropTypes.func.isRequired + deletePost: PropTypes.func.isRequired, + + isLogged: PropTypes.bool.isRequired } constructor(props) { @@ -44,14 +46,14 @@ class Page extends React.Component { componentDidMount() { if (this.props.posts.length === 0) { - this.props.fetchData(config.url); + this.props.fetchData(); } } openModal(postId) { this.props.setPostToDelete(postId); - this.setState({ + this.setState({ isModalOpen: true }) } @@ -66,14 +68,6 @@ class Page extends React.Component { } __renderPosts() { - return( - this.props.filteredPosts.map( - post => this.openModal(post.id)}/> - ) - ); - } - - __renderPostsContainer() { if (this.props.hasErrored) { return

Sorry! There was an error loading the items

; } @@ -85,7 +79,9 @@ class Page extends React.Component { return(
    - { this.__renderPosts() } + { this.props.filteredPosts.map( + post => this.openModal(post.id)}/> + )}
); @@ -93,11 +89,13 @@ class Page extends React.Component { __renderAddPostButton() { return( + this.props.isLogged &&
- Add new post + Add new post
+ ); } @@ -105,38 +103,34 @@ class Page extends React.Component { const { searchedPhrase, getSearchedPosts, changePhrase } = this.props; return ( -
- - - {this.__renderAddPostButton()} -
post to delete: {this.props.postToDelete} , number of posts: {this.props.posts.length}
- - searhced phrase:{ searchedPhrase } - debounce(500, changePhrase(e)) } - onFilterTextButton={ (e) => getSearchedPosts(e) } - /> - - this.closeModal() } - onConfirm={ () => this.onConfirmDelete(this.props.postToDelete) } - buttonCloseLabel="No" - buttonConfirmLabel="Yes" - > -

Are you sure to delete post #{this.props.postToDelete}?

-
- - {this.__renderPostsContainer()} -
+ +
+ {this.__renderAddPostButton()} + + debounce(500, changePhrase(e)) } + onFilterTextButton={ (e) => getSearchedPosts(e) } + /> + + this.closeModal() } + onConfirm={ () => this.onConfirmDelete(this.props.postToDelete) } + buttonCloseLabel="No" + buttonConfirmLabel="Yes" + > +

Are you sure to delete post #{this.props.postToDelete}?

+
+ + {this.__renderPosts()} +
+
); } } -//export default Page; - const mapStateToProps = (state) => { return { posts: state.posts, @@ -147,6 +141,8 @@ const mapStateToProps = (state) => { hasErrored: state.postsHasErrored, isLoading: state.postsIsLoading, postToDelete: state.postToDelete, + + isLogged: state.isLogged }; } diff --git a/src/components/Post/Post.js b/src/components/Post/Post.js index 77f9f91..3c71f0d 100755 --- a/src/components/Post/Post.js +++ b/src/components/Post/Post.js @@ -3,13 +3,17 @@ import { Link } from 'react-router'; import './Post.css'; import PropTypes from 'prop-types'; import classNames from 'classnames'; +import { connect } from "react-redux"; class Post extends React.Component { static propTypes = { id: PropTypes.number, title: PropTypes.string, body: PropTypes.string, - handleDelete: PropTypes.func.isRequired + handleDelete: PropTypes.func.isRequired, + isLogged: PropTypes.bool.isRequired, + myId: PropTypes.number, + userId: PropTypes.number } constructor(props) { @@ -19,25 +23,48 @@ class Post extends React.Component { render() { return ( -
  • -
    +
  • +

    {this.props.title}

    {this.props.body}
    -
    - - Edit - - -
    + { + this.props.isLogged && +
    + + Edit + + +
    + }
  • ); } } -export default Post; +//export default Post; + +const mapStateToProps = (state) => { + return { + isLogged: state.isLogged, + myId: state.userData.id + }; +} + +const mapDispatchToProps = (dispatch) => { + return { + + }; +} + +export default connect( + mapStateToProps, + mapDispatchToProps +)(Post); diff --git a/src/components/Post/Post.scss b/src/components/Post/Post.scss index f47aed4..6a99872 100755 --- a/src/components/Post/Post.scss +++ b/src/components/Post/Post.scss @@ -20,4 +20,7 @@ .Post_buttons{ } + &.Post--my-post{ + background: lightgrey; + } } \ No newline at end of file diff --git a/src/components/PostPage/PostPage.js b/src/components/PostPage/PostPage.js index 579d39e..d9eeb77 100644 --- a/src/components/PostPage/PostPage.js +++ b/src/components/PostPage/PostPage.js @@ -2,11 +2,9 @@ import React from 'react'; import { Link } from 'react-router' import './PostPage.css'; import PropTypes from 'prop-types'; -import { config } from '../../config.js'; import Comment from '../Comment/Comment'; import User from '../User/User'; import './PostPage.css'; -import fetch from 'isomorphic-fetch'; import { IndexLink } from 'react-router' import {connect} from "react-redux"; import { @@ -16,8 +14,10 @@ import { getPostData, getPostComments, addPost, - updatePost + updatePost, + fetchUsers } from '../../actions/postPage'; +import Layout from "../Layout/Layout"; class PostPage extends React.Component { static propTypes = { @@ -27,14 +27,16 @@ class PostPage extends React.Component { inputTitleValue: PropTypes.string, textareaBodyValue: PropTypes.string, - // userValue: PropTypes.number, + userValue: PropTypes.number, setBody: PropTypes.func.isRequired, setTitle: PropTypes.func.isRequired, setUser: PropTypes.func.isRequired, getPostData: PropTypes.func.isRequired, getPostComments: PropTypes.func.isRequired, addPost: PropTypes.func.isRequired, - updatePost: PropTypes.func.isRequired + updatePost: PropTypes.func.isRequired, + getUsers: PropTypes.func.isRequired, + users: PropTypes.array.isRequired } constructor(props) { @@ -47,6 +49,7 @@ class PostPage extends React.Component { componentDidMount() { const postId = this.props.params.postId; + this.props.getUsers(); if (postId) { this.props.getPostData(postId); @@ -57,26 +60,26 @@ class PostPage extends React.Component { } clearForm() { - this.setTitle(''); - this.setBody(''); - this.setUser(''); + this.props.setTitle(''); + this.props.setBody(''); + this.props.setUser(null); } handleSubmit() { - const postId = this.props.params.postId; + const postId = Number(this.props.params.postId); postId ? this.props.updatePost(postId) : this.props.addPost(); } handleTitleChange(event) { - this.setTitle(event.target.value); + this.props.setTitle(event.target.value); } handleBodyChange(event) { - this.setBody(event.target.value); + this.props.setBody(event.target.value); } handleUserChange(event) { - this.setUser(event.target.value); + this.props.setUser(Number(event.target.value)); } __validateForm() { @@ -95,8 +98,8 @@ class PostPage extends React.Component { return(
    User
    -
      this.handleUserChange(e)} > - {config.users.map( +
        this.handleUserChange(e)} > + {this.props.users.map( user => )}
      @@ -176,13 +179,15 @@ class PostPage extends React.Component { render() { return ( -
      - {this.__renderBreadcrumbs()} - {this.__renderTitle()} - {this.__renderForm()} - {this.__renderInfo()} - {this.__renderComments()} -
      + +
      + {this.__renderBreadcrumbs()} + {this.__renderTitle()} + {this.__renderForm()} + {this.__renderInfo()} + {this.__renderComments()} +
      +
      ); } } @@ -191,7 +196,8 @@ const mapStateToProps = (state) => { inputTitleValue: state.inputTitleValue, textareaBodyValue: state.textareaBodyValue, userValue: state.userValue, - comments: state.comments + comments: state.comments, + users: state.users }; } @@ -203,7 +209,8 @@ const mapDispatchToProps = (dispatch) => { getPostData: (postId) => dispatch(getPostData(postId)), getPostComments: (postId) => dispatch(getPostComments(postId)), addPost: () => dispatch(addPost()), - updatePost: (postId) => dispatch(updatePost(postId)) + updatePost: (postId) => dispatch(updatePost(postId)), + getUsers: () => dispatch(fetchUsers()) }; } diff --git a/src/components/PostPage/PostPage.scss b/src/components/PostPage/PostPage.scss index 6bac736..d881a84 100644 --- a/src/components/PostPage/PostPage.scss +++ b/src/components/PostPage/PostPage.scss @@ -31,6 +31,7 @@ &.users_container{ ul{ + margin: 0; padding: $padding; border: $baseBorder; list-style-type: none; diff --git a/src/components/User/User.js b/src/components/User/User.js index f44c645..1358d20 100644 --- a/src/components/User/User.js +++ b/src/components/User/User.js @@ -1,20 +1,48 @@ import React from 'react'; import './User.css'; import PropTypes from 'prop-types'; +import { connect } from "react-redux"; +import classNames from 'classnames'; class User extends React.Component { static propTypes = { name: PropTypes.string, - id: PropTypes.number.isRequired + id: PropTypes.number.isRequired, + myId: PropTypes.node, + firstName: PropTypes.string, + lastName: PropTypes.string } render() { return ( -
    • - - +
    • + +
    • ); } } -export default User; + +const mapStateToProps = (state) => { + return { + myId: state.userData.id + }; +} + +const mapDispatchToProps = (dispatch) => { + return { + + }; +} + +export default connect( + mapStateToProps, + mapDispatchToProps +)(User); diff --git a/src/components/User/User.scss b/src/components/User/User.scss index d8b54c7..eb338a3 100644 --- a/src/components/User/User.scss +++ b/src/components/User/User.scss @@ -6,5 +6,6 @@ } label{ padding-left: $padding; + margin: 5px 0; } } \ No newline at end of file diff --git a/src/components/UserPanel/UserPanel.js b/src/components/UserPanel/UserPanel.js new file mode 100644 index 0000000..cafd5ec --- /dev/null +++ b/src/components/UserPanel/UserPanel.js @@ -0,0 +1,72 @@ +import React from 'react'; +import { Link } from 'react-router'; +import './UserPanel.css'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; +import { connect } from "react-redux"; +import { + logOut +} from '../../actions/auth'; + +class UserPanel extends React.Component { + static propTypes = { + isLogged: PropTypes.bool.isRequired, + userData: PropTypes.object, + logOut: PropTypes.func + } + + constructor(props) { + super(props) + this.state = { isModalOpen: false } + } + + __renderUserData() { + return( +
      + + { this.props.userData.firstName } { this.props.userData.lastName } + ({ this.props.userData.email }) +
      + + this.props.logOut()} + className={classNames('btn btn-default')}> + Log out + +
      + ) + } + + render() { + return ( +
      + { + this.props.isLogged + ? this.__renderUserData() + : + Sign in + + } +
      + ); + } +} + +const mapStateToProps = (state) => { + return { + isLogged: state.isLogged, + userData: state.userData + }; +} + +const mapDispatchToProps = (dispatch) => { + return { + logOut: () => dispatch(logOut()) + }; +} + +export default connect( + mapStateToProps, + mapDispatchToProps +)(UserPanel); diff --git a/src/components/UserPanel/UserPanel.scss b/src/components/UserPanel/UserPanel.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/config.js b/src/config.js index a0c6097..1996245 100755 --- a/src/config.js +++ b/src/config.js @@ -1,6 +1,12 @@ -export const config = { - url: 'http://jsonplaceholder.typicode.com/posts', - commentsUrl: 'http://jsonplaceholder.typicode.com', +const baseApiUrl = 'http://localhost:3003'; + +export const config = { + url: { + posts: `${baseApiUrl}/posts`, + users: `${baseApiUrl}/authors`, + login: `${baseApiUrl}/auth/login`, + auth: `${baseApiUrl}/auth/me` + }, users: [ { id: 0, diff --git a/src/index.js b/src/index.js index 68d620f..f1304f9 100755 --- a/src/index.js +++ b/src/index.js @@ -2,11 +2,10 @@ import 'bootstrap/dist/css/bootstrap.css'; import 'bootstrap/dist/css/bootstrap-theme.css'; import React from 'react'; import ReactDOM from 'react-dom'; -import App from './components/App/App'; import './style/index.css'; import {Provider} from "react-redux"; import configureStore from './store/configureStore'; - +import getRoutes from './routes'; const initialState = { posts: [], @@ -19,9 +18,8 @@ const store = configureStore(initialState); ReactDOM.render(( -
      - - +
      + { getRoutes() }
      ), document.getElementById('root')); diff --git a/src/reducers/auth.js b/src/reducers/auth.js new file mode 100644 index 0000000..69b89b8 --- /dev/null +++ b/src/reducers/auth.js @@ -0,0 +1,49 @@ +export function isLogged(state = false, action) { + switch (action.type) { + case 'SET_IS_LOGGED': + return action.isLogged; + + default: + return state; + } +} + +export function token(state = '', action) { + switch (action.type) { + case 'SET_TOKEN': + return action.token; + + default: + return state; + } +} + +export function userData(state = {}, action) { + switch (action.type) { + case 'SET_USER_DATA': + return action.userData; + + default: + return state; + } +} + +export function formLoginIsValid(state = false, action) { + switch (action.type) { + case 'SET_LOGIN_FORM_VALID': + return action.formLoginIsValid; + + default: + return state; + } +} + +export function info(state = [], action) { + switch (action.type) { + case 'SET_INFO': + return action.info; + + default: + return state; + } +} \ No newline at end of file diff --git a/src/reducers/index.js b/src/reducers/index.js index 6c35819..55b42dc 100644 --- a/src/reducers/index.js +++ b/src/reducers/index.js @@ -1,6 +1,7 @@ import { combineReducers } from 'redux'; import { posts, postsHasErrored, postsIsLoading, searchedPhrase, filteredPosts, postToDelete } from './posts'; -import { inputTitleValue, textareaBodyValue, userValue, comments, lastPostId } from './postPage'; +import { inputTitleValue, textareaBodyValue, userValue, comments, lastPostId, users } from './postPage'; +import { isLogged, token, userData, formLoginIsValid, info } from './auth'; export default combineReducers({ posts, @@ -13,5 +14,13 @@ export default combineReducers({ textareaBodyValue, userValue, comments, - lastPostId + lastPostId, + + isLogged, + token, + userData, + users, + + formLoginIsValid, + info }); diff --git a/src/reducers/postPage.js b/src/reducers/postPage.js index 14325fb..0770a59 100644 --- a/src/reducers/postPage.js +++ b/src/reducers/postPage.js @@ -55,3 +55,13 @@ export function lastPostId(state = -1, action) { } } +export function users(state = [], action) { + switch (action.type) { + case 'SET_USERS': + return action.users; + + default: + return state; + } +} + diff --git a/src/routes.js b/src/routes.js index 97955aa..851c684 100644 --- a/src/routes.js +++ b/src/routes.js @@ -2,13 +2,16 @@ import React from 'react'; import { Router, Route, browserHistory } from 'react-router'; import Page from "./components/Page/Page"; import PostPage from "./components/PostPage/PostPage"; +import LoginPage from "./components/LoginPage/LoginPage"; +import {requireAuthentication} from "./components/AuthComponent/AuthComponent"; export default () => { return ( - - + + + ); -}; +}; \ No newline at end of file diff --git a/src/store/configureStore.js b/src/store/configureStore.js index b048480..a2e448f 100644 --- a/src/store/configureStore.js +++ b/src/store/configureStore.js @@ -1,11 +1,15 @@ -import { createStore, applyMiddleware } from 'redux'; +import {createStore, applyMiddleware, compose} from 'redux'; import thunk from 'redux-thunk'; import rootReducer from '../reducers/index'; -export default function configureStore(initialState) { - return createStore( - rootReducer, - initialState, - applyMiddleware(thunk) - ); +export default function configureStore(initialState){ + return createStore( + rootReducer, + initialState, + compose( + applyMiddleware(thunk), + window.devToolsExtension ? window.devToolsExtension() : f => f + ) + ); } +