From 40d28282d508890fdb4d6912e197cca47ff3998a Mon Sep 17 00:00:00 2001 From: "Kent C. Dodds" Date: Thu, 9 Mar 2017 11:26:33 -0700 Subject: [PATCH] big refactor of frontend to be awesome --- api/.gitignore | 12 ++ client/.gitignore | 12 ++ client/package-scripts.js | 4 +- client/package.json | 2 +- client/src/components/Profile.js | 172 ---------------- client/src/components/ProfileFavorites.js | 48 ----- client/src/index.js | 34 ++-- client/src/middleware.js | 2 +- client/src/reducer.js | 4 +- .../{articleList.js => article-list.js} | 0 ...ofileFavorites.js => profile-favorites.js} | 0 .../src/{components/App.js => screens/app.js} | 4 +- .../article/article-actions.js} | 2 +- .../article/article-meta.js} | 2 +- .../article/comment-container.js} | 4 +- .../article/comment-input.js} | 2 +- .../article/comment-list.js} | 2 +- .../Comment.js => screens/article/comment.js} | 2 +- .../article/delete-button.js} | 2 +- .../Article => screens/article}/index.js | 6 +- .../Editor.js => screens/editor.js} | 4 +- .../Home/Banner.js => screens/home/banner.js} | 0 .../Home => screens/home}/index.js | 8 +- .../MainView.js => screens/home/main-view.js} | 4 +- .../Home/Tags.js => screens/home/tags.js} | 2 +- .../{components/Login.js => screens/login.js} | 4 +- client/src/screens/profile-favorites.js | 43 ++++ client/src/screens/profile.js | 38 ++++ .../Register.js => screens/register.js} | 4 +- .../Settings.js => screens/settings.js} | 4 +- client/src/{ => shared}/agent.js | 10 +- client/src/{ => shared}/agent.test.js | 0 .../components/article-list.js} | 4 +- .../components/article-preview.js} | 0 .../Header.js => shared/components/header.js} | 4 +- .../components/list-errors.js} | 0 .../components/list-pagination.js} | 0 client/src/shared/containers/profile.js | 190 ++++++++++++++++++ .../{components => shared}/smiley-cyrus.jpg | Bin client/tests/integration/app.js | 0 client/yarn.lock | 70 ++----- 41 files changed, 383 insertions(+), 322 deletions(-) create mode 100644 api/.gitignore create mode 100644 client/.gitignore delete mode 100644 client/src/components/Profile.js delete mode 100644 client/src/components/ProfileFavorites.js rename client/src/reducers/{articleList.js => article-list.js} (100%) rename client/src/reducers/{profileFavorites.js => profile-favorites.js} (100%) rename client/src/{components/App.js => screens/app.js} (94%) rename client/src/{components/Article/ArticleActions.js => screens/article/article-actions.js} (95%) rename client/src/{components/Article/ArticleMeta.js => screens/article/article-meta.js} (93%) rename client/src/{components/Article/CommentContainer.js => screens/article/comment-container.js} (92%) rename client/src/{components/Article/CommentInput.js => screens/article/comment-input.js} (97%) rename client/src/{components/Article/CommentList.js => screens/article/comment-list.js} (91%) rename client/src/{components/Article/Comment.js => screens/article/comment.js} (95%) rename client/src/{components/Article/DeleteButton.js => screens/article/delete-button.js} (94%) rename client/src/{components/Article => screens/article}/index.js (94%) rename client/src/{components/Editor.js => screens/editor.js} (98%) rename client/src/{components/Home/Banner.js => screens/home/banner.js} (100%) rename client/src/{components/Home => screens/home}/index.js (92%) rename client/src/{components/Home/MainView.js => screens/home/main-view.js} (95%) rename client/src/{components/Home/Tags.js => screens/home/tags.js} (94%) rename client/src/{components/Login.js => screens/login.js} (96%) create mode 100644 client/src/screens/profile-favorites.js create mode 100644 client/src/screens/profile.js rename client/src/{components/Register.js => screens/register.js} (97%) rename client/src/{components/Settings.js => screens/settings.js} (97%) rename client/src/{ => shared}/agent.js (92%) rename client/src/{ => shared}/agent.test.js (100%) rename client/src/{components/ArticleList.js => shared/components/article-list.js} (86%) rename client/src/{components/ArticlePreview.js => shared/components/article-preview.js} (100%) rename client/src/{components/Header.js => shared/components/header.js} (95%) rename client/src/{components/ListErrors.js => shared/components/list-errors.js} (100%) rename client/src/{components/ListPagination.js => shared/components/list-pagination.js} (100%) create mode 100644 client/src/shared/containers/profile.js rename client/src/{components => shared}/smiley-cyrus.jpg (100%) create mode 100644 client/tests/integration/app.js diff --git a/api/.gitignore b/api/.gitignore new file mode 100644 index 00000000..589f3266 --- /dev/null +++ b/api/.gitignore @@ -0,0 +1,12 @@ +.DS_Store +node_modules +coverage +build +.DS_Store +.env +*.log +npm-debug.log* +.mongo-db +.e2e +cypress/videos +dist diff --git a/client/.gitignore b/client/.gitignore new file mode 100644 index 00000000..589f3266 --- /dev/null +++ b/client/.gitignore @@ -0,0 +1,12 @@ +.DS_Store +node_modules +coverage +build +.DS_Store +.env +*.log +npm-debug.log* +.mongo-db +.e2e +cypress/videos +dist diff --git a/client/package-scripts.js b/client/package-scripts.js index cddc7256..16afae39 100644 --- a/client/package-scripts.js +++ b/client/package-scripts.js @@ -1,7 +1,7 @@ -const {series, rimraf, commonTags} = require('nps-utils') +const {series, rimraf, commonTags, crossEnv} = require('nps-utils') module.exports = { scripts: { - dev: 'react-scripts start', + dev: crossEnv('PORT=8080 react-scripts start'), build: 'react-scripts build', default: 'pushstate-server build', test: { diff --git a/client/package.json b/client/package.json index 8631da62..a111c9d9 100644 --- a/client/package.json +++ b/client/package.json @@ -10,7 +10,7 @@ "nps": "^5.0.4", "nps-utils": "^1.2.0", "pushstate-server": "^2.2.1", - "react-scripts": "^0.9.0" + "react-scripts": "^0.9.4" }, "license": "MIT", "repository": "git@github.com:kentcdodds/testing-workshop.git", diff --git a/client/src/components/Profile.js b/client/src/components/Profile.js deleted file mode 100644 index 882f8c0d..00000000 --- a/client/src/components/Profile.js +++ /dev/null @@ -1,172 +0,0 @@ -import React from 'react' -import {Link} from 'react-router' -import {connect} from 'react-redux' -import agent from '../agent' -import ArticleList from './ArticleList' -import smiley from './smiley-cyrus.jpg' - -const EditProfileSettings = props => { - if (props.isUser) { - return ( - - Edit Profile Settings - - ) - } - return null -} - -const FollowUserButton = props => { - if (props.isUser) { - return null - } - - let classes = 'btn btn-sm action-btn' - if (props.user.following) { - classes += ' btn-secondary' - } else { - classes += ' btn-outline-secondary' - } - - const handleClick = ev => { - ev.preventDefault() - if (props.user.following) { - props.unfollow(props.user.username) - } else { - props.follow(props.user.username) - } - } - - return ( - - ) -} - -const mapStateToProps = state => ({ - ...state.articleList, - currentUser: state.common.currentUser, - profile: state.profile, -}) - -const mapDispatchToProps = dispatch => ({ - onFollow: username => dispatch({ - type: 'FOLLOW_USER', - payload: agent.Profile.follow(username), - }), - onLoad: payload => dispatch({type: 'PROFILE_PAGE_LOADED', payload}), - onUnfollow: username => dispatch({ - type: 'UNFOLLOW_USER', - payload: agent.Profile.unfollow(username), - }), - onUnload: () => dispatch({type: 'PROFILE_PAGE_UNLOADED'}), -}) - -class Profile extends React.Component { - componentWillMount() { - this.props.onLoad( - Promise.all([ - agent.Profile.get(this.props.params.username), - agent.Articles.byAuthor(this.props.params.username), - ]), - ) - } - - componentWillUnmount() { - this.props.onUnload() - } - - renderTabs() { - return ( -
    -
  • - - My Articles - -
  • - -
  • - - Favorited Articles - -
  • -
- ) - } - - render() { - const profile = this.props.profile - if (!profile) { - return null - } - - const isUser = this.props.currentUser && - this.props.profile.username === this.props.currentUser.username - - return ( -
- -
-
-
-
- - user -

{profile.username}

-

{profile.bio}

- - - - -
-
-
-
- -
-
- -
- -
- {this.renderTabs()} -
- - -
- -
-
- -
- ) - } -} - -export default connect(mapStateToProps, mapDispatchToProps)(Profile) -export {Profile, mapStateToProps} diff --git a/client/src/components/ProfileFavorites.js b/client/src/components/ProfileFavorites.js deleted file mode 100644 index f3d68951..00000000 --- a/client/src/components/ProfileFavorites.js +++ /dev/null @@ -1,48 +0,0 @@ -import React from 'react' -import {Link} from 'react-router' -import {connect} from 'react-redux' -import agent from '../agent' -import {Profile, mapStateToProps} from './Profile' - -const mapDispatchToProps = dispatch => ({ - onLoad: payload => dispatch({type: 'PROFILE_FAVORITES_PAGE_LOADED', payload}), - onUnload: () => dispatch({type: 'PROFILE_FAVORITES_PAGE_UNLOADED'}), -}) - -class ProfileFavorites extends Profile { - componentWillMount() { - this.props.onLoad( - Promise.all([ - agent.Profile.get(this.props.params.username), - agent.Articles.favoritedBy(this.props.params.username), - ]), - ) - } - - componentWillUnmount() { - this.props.onUnload() - } - - renderTabs() { - return ( -
    -
  • - - My Articles - -
  • - -
  • - - Favorited Articles - -
  • -
- ) - } -} - -export default connect(mapStateToProps, mapDispatchToProps)(ProfileFavorites) diff --git a/client/src/index.js b/client/src/index.js index e4e63b40..2f68a055 100644 --- a/client/src/index.js +++ b/client/src/index.js @@ -1,22 +1,32 @@ import ReactDOM from 'react-dom' import {Provider} from 'react-redux' import React from 'react' -import {Router, Route, IndexRoute, browserHistory} from 'react-router' -import store from './store' +import { + Router, + Route, + IndexRoute, + browserHistory, + hashHistory, +} from 'react-router' -import App from './components/App' -import Article from './components/Article' -import Editor from './components/Editor' -import Home from './components/Home' -import Login from './components/Login' -import ConnectedProfile from './components/Profile' -import ProfileFavorites from './components/ProfileFavorites' -import Register from './components/Register' -import Settings from './components/Settings' +import store from './store' +import App from './screens/app' +import Article from './screens/article' +import Editor from './screens/editor' +import Home from './screens/home' +import Login from './screens/login' +import ConnectedProfile from './screens/profile' +import ProfileFavorites from './screens/profile-favorites' +import Register from './screens/register' +import Settings from './screens/settings' ReactDOM.render( - + diff --git a/client/src/middleware.js b/client/src/middleware.js index 59a18f11..295529ee 100644 --- a/client/src/middleware.js +++ b/client/src/middleware.js @@ -1,4 +1,4 @@ -import agent from './agent' +import agent from './shared/agent' const promiseMiddleware = store => next => action => { if (isPromise(action.payload)) { diff --git a/client/src/reducer.js b/client/src/reducer.js index f20de33b..e7da3797 100644 --- a/client/src/reducer.js +++ b/client/src/reducer.js @@ -1,12 +1,12 @@ import {combineReducers} from 'redux' import article from './reducers/article' -import articleList from './reducers/articleList' +import articleList from './reducers/article-list' import auth from './reducers/auth' import common from './reducers/common' import editor from './reducers/editor' import home from './reducers/home' import profile from './reducers/profile' -import profileFavorites from './reducers/profileFavorites' +import profileFavorites from './reducers/profile-favorites' import settings from './reducers/settings' export default combineReducers({ diff --git a/client/src/reducers/articleList.js b/client/src/reducers/article-list.js similarity index 100% rename from client/src/reducers/articleList.js rename to client/src/reducers/article-list.js diff --git a/client/src/reducers/profileFavorites.js b/client/src/reducers/profile-favorites.js similarity index 100% rename from client/src/reducers/profileFavorites.js rename to client/src/reducers/profile-favorites.js diff --git a/client/src/components/App.js b/client/src/screens/app.js similarity index 94% rename from client/src/components/App.js rename to client/src/screens/app.js index 6bbbb795..53433321 100644 --- a/client/src/components/App.js +++ b/client/src/screens/app.js @@ -1,7 +1,7 @@ import React from 'react' import {connect} from 'react-redux' -import agent from '../agent' -import Header from './Header' +import agent from '../shared/agent' +import Header from '../shared/components/header' const mapStateToProps = state => ({ appLoaded: state.common.appLoaded, diff --git a/client/src/components/Article/ArticleActions.js b/client/src/screens/article/article-actions.js similarity index 95% rename from client/src/components/Article/ArticleActions.js rename to client/src/screens/article/article-actions.js index 3d2aa0b3..09741b46 100644 --- a/client/src/components/Article/ArticleActions.js +++ b/client/src/screens/article/article-actions.js @@ -1,7 +1,7 @@ import React from 'react' import {Link} from 'react-router' import {connect} from 'react-redux' -import agent from '../../agent' +import agent from '../../shared/agent' const mapDispatchToProps = dispatch => ({ onClickDelete: payload => dispatch({type: 'DELETE_ARTICLE', payload}), diff --git a/client/src/components/Article/ArticleMeta.js b/client/src/screens/article/article-meta.js similarity index 93% rename from client/src/components/Article/ArticleMeta.js rename to client/src/screens/article/article-meta.js index 20795bd3..ba884e76 100644 --- a/client/src/components/Article/ArticleMeta.js +++ b/client/src/screens/article/article-meta.js @@ -1,6 +1,6 @@ import React from 'react' import {Link} from 'react-router' -import ArticleActions from './ArticleActions' +import ArticleActions from './article-actions' const ArticleMeta = props => { const article = props.article diff --git a/client/src/components/Article/CommentContainer.js b/client/src/screens/article/comment-container.js similarity index 92% rename from client/src/components/Article/CommentContainer.js rename to client/src/screens/article/comment-container.js index 6c54e814..a50430f4 100644 --- a/client/src/components/Article/CommentContainer.js +++ b/client/src/screens/article/comment-container.js @@ -1,7 +1,7 @@ import React from 'react' import {Link} from 'react-router' -import CommentInput from './CommentInput' -import CommentList from './CommentList' +import CommentInput from './comment-input' +import CommentList from './comment-list' const CommentContainer = props => { if (props.currentUser) { diff --git a/client/src/components/Article/CommentInput.js b/client/src/screens/article/comment-input.js similarity index 97% rename from client/src/components/Article/CommentInput.js rename to client/src/screens/article/comment-input.js index d5f20699..0e482b3b 100644 --- a/client/src/components/Article/CommentInput.js +++ b/client/src/screens/article/comment-input.js @@ -1,6 +1,6 @@ import React from 'react' import {connect} from 'react-redux' -import agent from '../../agent' +import agent from '../../shared/agent' const mapDispatchToProps = dispatch => ({ onSubmit: payload => dispatch({type: 'ADD_COMMENT', payload}), diff --git a/client/src/components/Article/CommentList.js b/client/src/screens/article/comment-list.js similarity index 91% rename from client/src/components/Article/CommentList.js rename to client/src/screens/article/comment-list.js index 88eb399e..3208fce0 100644 --- a/client/src/components/Article/CommentList.js +++ b/client/src/screens/article/comment-list.js @@ -1,5 +1,5 @@ import React from 'react' -import Comment from './Comment' +import Comment from './comment' const CommentList = props => { return ( diff --git a/client/src/components/Article/Comment.js b/client/src/screens/article/comment.js similarity index 95% rename from client/src/components/Article/Comment.js rename to client/src/screens/article/comment.js index 42a7d2fa..e46fd6cd 100644 --- a/client/src/components/Article/Comment.js +++ b/client/src/screens/article/comment.js @@ -1,6 +1,6 @@ import React from 'react' import {Link} from 'react-router' -import DeleteButton from './DeleteButton' +import DeleteButton from './delete-button' const Comment = props => { const comment = props.comment diff --git a/client/src/components/Article/DeleteButton.js b/client/src/screens/article/delete-button.js similarity index 94% rename from client/src/components/Article/DeleteButton.js rename to client/src/screens/article/delete-button.js index f447c104..6ced10f2 100644 --- a/client/src/components/Article/DeleteButton.js +++ b/client/src/screens/article/delete-button.js @@ -1,6 +1,6 @@ import React from 'react' import {connect} from 'react-redux' -import agent from '../../agent' +import agent from '../../shared/agent' const mapDispatchToProps = dispatch => ({ onClick: (payload, commentId) => diff --git a/client/src/components/Article/index.js b/client/src/screens/article/index.js similarity index 94% rename from client/src/components/Article/index.js rename to client/src/screens/article/index.js index ca590502..b89cf74e 100644 --- a/client/src/components/Article/index.js +++ b/client/src/screens/article/index.js @@ -1,9 +1,9 @@ import React from 'react' import {connect} from 'react-redux' import marked from 'marked' -import agent from '../../agent' -import ArticleMeta from './ArticleMeta' -import CommentContainer from './CommentContainer' +import agent from '../../shared/agent' +import ArticleMeta from './article-meta' +import CommentContainer from './comment-container' const mapStateToProps = state => ({ ...state.article, diff --git a/client/src/components/Editor.js b/client/src/screens/editor.js similarity index 98% rename from client/src/components/Editor.js rename to client/src/screens/editor.js index 0bc74ec8..ccf6244f 100644 --- a/client/src/components/Editor.js +++ b/client/src/screens/editor.js @@ -1,7 +1,7 @@ import React from 'react' import {connect} from 'react-redux' -import agent from '../agent' -import ListErrors from './ListErrors' +import agent from '../shared/agent' +import ListErrors from '../shared/components/list-errors' const mapStateToProps = state => ({ ...state.editor, diff --git a/client/src/components/Home/Banner.js b/client/src/screens/home/banner.js similarity index 100% rename from client/src/components/Home/Banner.js rename to client/src/screens/home/banner.js diff --git a/client/src/components/Home/index.js b/client/src/screens/home/index.js similarity index 92% rename from client/src/components/Home/index.js rename to client/src/screens/home/index.js index d946e2c8..a09f7b94 100644 --- a/client/src/components/Home/index.js +++ b/client/src/screens/home/index.js @@ -1,9 +1,9 @@ import React from 'react' import {connect} from 'react-redux' -import agent from '../../agent' -import Banner from './Banner' -import MainView from './MainView' -import Tags from './Tags' +import agent from '../../shared/agent' +import Banner from './banner' +import MainView from './main-view' +import Tags from './tags' const Promise = global.Promise diff --git a/client/src/components/Home/MainView.js b/client/src/screens/home/main-view.js similarity index 95% rename from client/src/components/Home/MainView.js rename to client/src/screens/home/main-view.js index dc6ad675..521deca1 100644 --- a/client/src/components/Home/MainView.js +++ b/client/src/screens/home/main-view.js @@ -1,7 +1,7 @@ import React from 'react' import {connect} from 'react-redux' -import ArticleList from '../ArticleList' -import agent from '../../agent' +import ArticleList from '../../shared/components/article-list' +import agent from '../../shared/agent' const YourFeedTab = props => { if (props.token) { diff --git a/client/src/components/Home/Tags.js b/client/src/screens/home/tags.js similarity index 94% rename from client/src/components/Home/Tags.js rename to client/src/screens/home/tags.js index d9b3f82a..c0f5d19a 100644 --- a/client/src/components/Home/Tags.js +++ b/client/src/screens/home/tags.js @@ -1,5 +1,5 @@ import React from 'react' -import agent from '../../agent' +import agent from '../../shared/agent' const Tags = props => { const tags = props.tags diff --git a/client/src/components/Login.js b/client/src/screens/login.js similarity index 96% rename from client/src/components/Login.js rename to client/src/screens/login.js index 56263c06..ab441207 100644 --- a/client/src/components/Login.js +++ b/client/src/screens/login.js @@ -1,8 +1,8 @@ import React from 'react' import {Link} from 'react-router' import {connect} from 'react-redux' -import agent from '../agent' -import ListErrors from './ListErrors' +import agent from '../shared/agent' +import ListErrors from '../shared/components/list-errors' const mapStateToProps = state => ({...state.auth}) diff --git a/client/src/screens/profile-favorites.js b/client/src/screens/profile-favorites.js new file mode 100644 index 00000000..0c485f3e --- /dev/null +++ b/client/src/screens/profile-favorites.js @@ -0,0 +1,43 @@ +import React from 'react' +import {connect} from 'react-redux' +import agent from '../shared/agent' +import Profile from '../shared/containers/profile' + +class ProfileFavoritesPage extends React.Component { + componentWillMount() { + this.props.onLoad( + Promise.all([ + agent.Profile.get(this.props.params.username), + agent.Articles.favoritedBy(this.props.params.username), + ]), + ) + } + + componentWillUnmount() { + this.props.onUnload() + } + + render() { + return ( + + ) + } +} + +function mapStateToProps(state) { + return { + profileFavorites: state.profileFavorites, + } +} + +function mapDispatchToProps(dispatch) { + return { + onLoad: payload => + dispatch({type: 'PROFILE_FAVORITES_PAGE_LOADED', payload}), + onUnload: () => dispatch({type: 'PROFILE_FAVORITES_PAGE_UNLOADED'}), + } +} + +export default connect(mapStateToProps, mapDispatchToProps)( + ProfileFavoritesPage, +) diff --git a/client/src/screens/profile.js b/client/src/screens/profile.js new file mode 100644 index 00000000..9ceda685 --- /dev/null +++ b/client/src/screens/profile.js @@ -0,0 +1,38 @@ +import React from 'react' +import {connect} from 'react-redux' +import agent from '../shared/agent' +import Profile from '../shared/containers/profile' + +class ProfilePage extends React.Component { + componentWillMount() { + this.props.onLoad( + Promise.all([ + agent.Profile.get(this.props.params.username), + agent.Articles.byAuthor(this.props.params.username), + ]), + ) + } + + componentWillUnmount() { + this.props.onUnload() + } + + render() { + return + } +} + +function mapStateToProps(state) { + return { + profile: state.profile, + } +} + +function mapDispatchToProps(dispatch) { + return { + onLoad: payload => dispatch({type: 'PROFILE_PAGE_LOADED', payload}), + onUnload: () => dispatch({type: 'PROFILE_PAGE_UNLOADED'}), + } +} + +export default connect(mapStateToProps, mapDispatchToProps)(ProfilePage) diff --git a/client/src/components/Register.js b/client/src/screens/register.js similarity index 97% rename from client/src/components/Register.js rename to client/src/screens/register.js index af76d848..602dab5c 100644 --- a/client/src/components/Register.js +++ b/client/src/screens/register.js @@ -1,8 +1,8 @@ import React from 'react' import {Link} from 'react-router' import {connect} from 'react-redux' -import agent from '../agent' -import ListErrors from './ListErrors' +import agent from '../shared/agent' +import ListErrors from '../shared/components/list-errors' const mapStateToProps = state => ({...state.auth}) diff --git a/client/src/components/Settings.js b/client/src/screens/settings.js similarity index 97% rename from client/src/components/Settings.js rename to client/src/screens/settings.js index f393e8c9..f0b7022f 100644 --- a/client/src/components/Settings.js +++ b/client/src/screens/settings.js @@ -1,7 +1,7 @@ import React from 'react' import {connect} from 'react-redux' -import agent from '../agent' -import ListErrors from './ListErrors' +import agent from '../shared/agent' +import ListErrors from '../shared/components/list-errors' class SettingsForm extends React.Component { constructor() { diff --git a/client/src/agent.js b/client/src/shared/agent.js similarity index 92% rename from client/src/agent.js rename to client/src/shared/agent.js index cd9eb907..825519ee 100644 --- a/client/src/agent.js +++ b/client/src/shared/agent.js @@ -1,9 +1,13 @@ import axios from 'axios' import queryString from 'query-string' -// const API_ROOT = 'https://conduit.productionready.io/api'; -const API_ROOT = queryString.parse(location.search)['api-url'] || - 'http://localhost:3000/api' +const productionUrl = 'https://conduit.productionready.io/api' +const developmentUrl = 'http://localhost:3000/api' +const urlToUse = process.env.NODE_ENV === 'production' ? + productionUrl : + developmentUrl + +const API_ROOT = queryString.parse(location.search)['api-url'] || urlToUse const api = axios.create({ baseURL: API_ROOT, diff --git a/client/src/agent.test.js b/client/src/shared/agent.test.js similarity index 100% rename from client/src/agent.test.js rename to client/src/shared/agent.test.js diff --git a/client/src/components/ArticleList.js b/client/src/shared/components/article-list.js similarity index 86% rename from client/src/components/ArticleList.js rename to client/src/shared/components/article-list.js index 4772788e..da3cad2b 100644 --- a/client/src/components/ArticleList.js +++ b/client/src/shared/components/article-list.js @@ -1,6 +1,6 @@ import React from 'react' -import ArticlePreview from './ArticlePreview' -import ListPagination from './ListPagination' +import ArticlePreview from './article-preview' +import ListPagination from './list-pagination' const ArticleList = props => { if (!props.articles) { diff --git a/client/src/components/ArticlePreview.js b/client/src/shared/components/article-preview.js similarity index 100% rename from client/src/components/ArticlePreview.js rename to client/src/shared/components/article-preview.js diff --git a/client/src/components/Header.js b/client/src/shared/components/header.js similarity index 95% rename from client/src/components/Header.js rename to client/src/shared/components/header.js index 5af71931..5729ca44 100644 --- a/client/src/components/Header.js +++ b/client/src/shared/components/header.js @@ -1,6 +1,6 @@ import React from 'react' import {Link} from 'react-router' -import smiley from './smiley-cyrus.jpg' +import smiley from '../../shared/smiley-cyrus.jpg' function LoggedOutView(props) { if (!props.currentUser) { @@ -37,7 +37,7 @@ function LoggedInView(props) {
  • - + Home
  • diff --git a/client/src/components/ListErrors.js b/client/src/shared/components/list-errors.js similarity index 100% rename from client/src/components/ListErrors.js rename to client/src/shared/components/list-errors.js diff --git a/client/src/components/ListPagination.js b/client/src/shared/components/list-pagination.js similarity index 100% rename from client/src/components/ListPagination.js rename to client/src/shared/components/list-pagination.js diff --git a/client/src/shared/containers/profile.js b/client/src/shared/containers/profile.js new file mode 100644 index 00000000..3c58e7fb --- /dev/null +++ b/client/src/shared/containers/profile.js @@ -0,0 +1,190 @@ +import React, {PropTypes} from 'react' +import {connect} from 'react-redux' +import {Link} from 'react-router' +import smiley from '../smiley-cyrus.jpg' +import agent from '../agent' +import ArticleList from '../components/article-list' + +function Profile( + { + profile, + onFollow, + onUnfollow, + currentUser, + articles, + articlesCount, + currentPage, + activeTab, + }, +) { + if (!profile || !profile.username) { + return null + } + + const isUser = currentUser && profile.username === currentUser.username + + return ( +
    +
    +
    +
    +
    + user +

    {profile.username}

    +

    {profile.bio}

    + + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    + +
    + +
    +
    +
    + ) +} +Profile.propTypes = { + profile: PropTypes.shape({ + username: PropTypes.string, + }), + currentUser: PropTypes.shape({ + username: PropTypes.string.isRequired, + }), + onFollow: PropTypes.func.isRequired, + onUnfollow: PropTypes.func.isRequired, + + // forward these on to other components + activeTab: PropTypes.any, + articles: PropTypes.any, + articlesCount: PropTypes.any, + currentPage: PropTypes.any, +} + +function Tabs({username, activeTab}) { + const mineClasses = ['nav-link'] + const favoritesClasses = ['nav-link'] + if (activeTab === 'mine') { + mineClasses.push('active') + } + if (activeTab === 'favorites') { + favoritesClasses.push('active') + } + return ( +
      +
    • + + My Articles + +
    • + +
    • + + Favorited Articles + +
    • +
    + ) +} +Tabs.propTypes = { + username: PropTypes.string.isRequired, + activeTab: PropTypes.string.isRequired, +} + +function EditProfileSettings({isUser}) { + if (isUser) { + return null + } + return ( + + Edit Profile Settings + + ) +} +EditProfileSettings.propTypes = {isUser: PropTypes.bool.isRequired} + +function FollowUserButton({isUser, user, unfollow, follow}) { + if (isUser) { + return null + } + + let classes = 'btn btn-sm action-btn' + if (user.following) { + classes += ' btn-secondary' + } else { + classes += ' btn-outline-secondary' + } + + const handleClick = ev => { + ev.preventDefault() + if (user.following) { + unfollow(user.username) + } else { + follow(user.username) + } + } + + return ( + + ) +} +FollowUserButton.propTypes = { + isUser: PropTypes.bool.isRequired, + user: PropTypes.shape({ + following: PropTypes.bool.isRequired, + username: PropTypes.string.isRequired, + }), + unfollow: PropTypes.func.isRequired, + follow: PropTypes.func.isRequired, +} + +function mapStateToProps(state) { + return { + ...state.articleList, + currentUser: state.common.currentUser, + } +} + +function mapDispatchToProps(dispatch) { + return { + onFollow: username => dispatch({ + type: 'FOLLOW_USER', + payload: agent.Profile.follow(username), + }), + onUnfollow: username => dispatch({ + type: 'UNFOLLOW_USER', + payload: agent.Profile.unfollow(username), + }), + } +} + +export default connect(mapStateToProps, mapDispatchToProps)(Profile) diff --git a/client/src/components/smiley-cyrus.jpg b/client/src/shared/smiley-cyrus.jpg similarity index 100% rename from client/src/components/smiley-cyrus.jpg rename to client/src/shared/smiley-cyrus.jpg diff --git a/client/tests/integration/app.js b/client/tests/integration/app.js new file mode 100644 index 00000000..e69de29b diff --git a/client/yarn.lock b/client/yarn.lock index dd66a4b6..2c771635 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -1324,10 +1324,6 @@ commondir@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" -component-emitter@^1.2.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" - compressible@~2.0.8: version "2.0.9" resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.9.tgz#6daab4e2b599c2770dd9e21e7a891b1c5a755425" @@ -1433,10 +1429,6 @@ cookie@0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" -cookiejar@^2.0.6: - version "2.1.0" - resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.0.tgz#86549689539b6d0e269b6637a304be508194d898" - core-js@^1.0.0: version "1.2.7" resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" @@ -2214,7 +2206,7 @@ express@^4.13.3: utils-merge "1.0.0" vary "~1.1.0" -extend@^3.0.0, extend@~3.0.0: +extend@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.0.tgz#5a474353b9f3353ddd8176dfd37b91c83a46f1d4" @@ -2395,7 +2387,7 @@ forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" -form-data@^2.1.1, form-data@~2.1.1: +form-data@~2.1.1: version "2.1.2" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.1.2.tgz#89c3534008b97eada4cbb157d58f6f5df025eae4" dependencies: @@ -2403,10 +2395,6 @@ form-data@^2.1.1, form-data@~2.1.1: combined-stream "^1.0.5" mime-types "^2.1.12" -formidable@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.1.1.tgz#96b8886f7c3c3508b932d6bd70c4d3a88f35f1a9" - forwarded@~0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.0.tgz#19ef9874c4ae1c297bcf078fde63a09b66a84363" @@ -2598,7 +2586,7 @@ har-schema@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e" -har-validator@~4.2.0: +har-validator@~4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-4.2.1.tgz#33481d0f1bbff600dd203d75812a6a5fba002e2a" dependencies: @@ -3688,7 +3676,7 @@ merge@^1.1.3: version "1.2.0" resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.0.tgz#7531e39d4949c281a66b8c5a6e0265e8b05894da" -methods@^1.1.1, methods@~1.1.2: +methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" @@ -4552,14 +4540,10 @@ q@^1.1.2: version "1.4.1" resolved "https://registry.yarnpkg.com/q/-/q-1.4.1.tgz#55705bcd93c5f3673530c2c2cbc0c2b3addc286e" -qs@6.4.0, qs@^6.1.0: +qs@6.4.0, qs@~6.4.0: version "6.4.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" -qs@~6.3.0: - version "6.3.2" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.3.2.tgz#e75bd5f6e268122a2a0e0bda630b2550c166502c" - query-string@^4.1.0, query-string@^4.2.2, query-string@^4.3.2: version "4.3.2" resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.2.tgz#ec0fd765f58a50031a3968c2431386f8947a5cdd" @@ -4646,7 +4630,7 @@ react-router@^3.0.2: loose-envify "^1.2.0" warning "^3.0.0" -react-scripts@^0.9.0: +react-scripts@^0.9.4: version "0.9.4" resolved "https://registry.yarnpkg.com/react-scripts/-/react-scripts-0.9.4.tgz#e43ca46f4641561dd84caae4c91795923defda4f" dependencies: @@ -4900,8 +4884,8 @@ repeating@^2.0.0: is-finite "^1.0.0" request@^2.79.0: - version "2.80.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.80.0.tgz#8cc162d76d79381cdefdd3505d76b80b60589bd0" + version "2.81.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0" dependencies: aws-sign2 "~0.6.0" aws4 "^1.2.1" @@ -4910,7 +4894,7 @@ request@^2.79.0: extend "~3.0.0" forever-agent "~0.6.1" form-data "~2.1.1" - har-validator "~4.2.0" + har-validator "~4.2.1" hawk "~3.1.3" http-signature "~1.1.0" is-typedarray "~1.0.0" @@ -4919,10 +4903,11 @@ request@^2.79.0: mime-types "~2.1.7" oauth-sign "~0.8.1" performance-now "^0.2.0" - qs "~6.3.0" + qs "~6.4.0" + safe-buffer "^5.0.1" stringstream "~0.0.4" tough-cookie "~2.3.0" - tunnel-agent "~0.4.1" + tunnel-agent "^0.6.0" uuid "^3.0.0" require-directory@^2.1.1: @@ -5009,6 +4994,10 @@ rx@2.3.24: version "2.3.24" resolved "https://registry.yarnpkg.com/rx/-/rx-2.3.24.tgz#14f950a4217d7e35daa71bbcbe58eff68ea4b2b7" +safe-buffer@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.0.1.tgz#d263ca54696cd8a306b5ca6551e92de57918fbe7" + sane@~1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/sane/-/sane-1.4.1.tgz#88f763d74040f5f0c256b6163db399bf110ac715" @@ -5388,25 +5377,6 @@ style-loader@0.13.1: dependencies: loader-utils "^0.2.7" -superagent-promise@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/superagent-promise/-/superagent-promise-1.1.0.tgz#baf22d8bbdd439a9b07dd10f8c08f54fe2503533" - -superagent@^3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/superagent/-/superagent-3.5.0.tgz#56872b8e1ee6de994035ada2e53266899af95a6d" - dependencies: - component-emitter "^1.2.0" - cookiejar "^2.0.6" - debug "^2.2.0" - extend "^3.0.0" - form-data "^2.1.1" - formidable "^1.1.1" - methods "^1.1.1" - mime "^1.3.4" - qs "^6.1.0" - readable-stream "^2.0.5" - supports-color@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-0.2.0.tgz#d92de2694eb3f67323973d7ae3d8b55b4c22190a" @@ -5562,9 +5532,11 @@ tty-browserify@0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" -tunnel-agent@~0.4.1: - version "0.4.3" - resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.4.3.tgz#6373db76909fe570e08d73583365ed828a74eeeb" +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + dependencies: + safe-buffer "^5.0.1" tweetnacl@^0.14.3, tweetnacl@~0.14.0: version "0.14.5"