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 (
-
-
-
-
-
-
-
-
-
{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 (
+
+
+
+
+
+
+
{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"