Skip to content

Commit

Permalink
feature(adds functionality to enforce user authentication):
Browse files Browse the repository at this point in the history
 - add functionality to prevent un-authenticated users from accessing functionalities that require user login

 [ Delivers #163355244]
  • Loading branch information
vincent Onyango committed Jan 30, 2019
1 parent bcece85 commit 00a8b7f
Show file tree
Hide file tree
Showing 8 changed files with 110 additions and 70 deletions.
13 changes: 3 additions & 10 deletions src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import ViewSingleArticleComponent from './container/viewArticleContainer';
import ArticleContainerComponent from './components/Articles/ArticlesContainer';
import RegistrationPageComponent from './components/Authentication/Registration/RegistrationPage';
import LoginContainer from './components/Authentication/Login/LoginContainer';

import Profile from './containers/ProfileContainer';
import SearchResultsPageContainer from './components/Search/SearchResultsPage';
import UserSpecificArticlesContainer from './components/Articles/UserSpecificArticlesContainer';
import IsAuthenticated from './common/IsAuthenticated';


class App extends Component {
Expand All @@ -21,20 +21,13 @@ class App extends Component {
<BrowserRouter>
<Switch>
<Route exact path="/login" component={LoginContainer} />
<Route path="/search" component={SearchResultsPageComponent} exact />
<Route exact path="/articles/:slug" component={ViewSingleArticleComponent} />
<Route path="/" component={ArticleContainerComponent} exact />
<Route path="/articles" component={ArticleContainerComponent} exact />
<Route path="/register" component={RegistrationPageComponent} exact />
<Route path="/profile" exact component={Profile} />
<Route path="/" component={ArticleContainerComponent} exact />
<Route path="/articles" component={ArticleContainerComponent} exact />
<Route path="/register" component={RegistrationPageComponent} exact />
<Route exact path="/login" component={LoginContainer} />
<Route path="/search" component={SearchResultsPageContainer} exact />
<Route exact path="/articles/:slug" component={ViewSingleArticleComponent} />
<IsAuthenticated path="/profile" exact component={Profile} />
<Route path="/search" component={SearchResultsPageContainer} exact />
<Route exact path="/myarticles" component={UserSpecificArticlesContainer} />
<IsAuthenticated exact path="/myarticles" component={UserSpecificArticlesContainer} />
<Route component={Error} />
</Switch>
</BrowserRouter>
Expand Down
32 changes: 32 additions & 0 deletions src/common/IsAuthenticated.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import {
Route,
Redirect,
} from 'react-router-dom';
import React from 'react';
import PropTypes from 'prop-types';
// import { ReactComponent } from '*.svg';
import Cookies from 'js-cookie';

const IsAuthenticated = ({ component: Component, ...props }) => (
<Route
{...props}
render={properties => (
Cookies.get('username') ? <Component {...properties} />
: (
<Redirect to={{
pathname: '/login',
state: { from: props.location },
}}
/>
)
)}
/>
);


IsAuthenticated.propTypes = {
component: PropTypes.func.isRequired,
location: PropTypes.shape().isRequired,
};

export default IsAuthenticated;
4 changes: 2 additions & 2 deletions src/components/Articles/DeletedArticle.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ DeletedArticle.propTypes = {
export default DeletedArticle;

export const ArticleNotFound = (props) => {
const { history } = props;
const hanldeOnClick = () => {
history.push('/create_article');
const { history } = props;
history.push('/new_article');
};
return (
<div className="ui break">
Expand Down
87 changes: 41 additions & 46 deletions src/components/Articles/UserSpecificArticleComponent.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,43 +7,11 @@ import Moment from 'react-moment';

const UserSpecificArticleComponent = (props) => {
const {
// eslint-disable-next-line camelcase
title, description, author, created_at,
// eslint-disable-next-line camelcase
article_slug, onDelete, modalOpen, openModal, image, name, setName,
article_slug: articleSlug, onDelete, modalOpen, openModal, name, setName,
} = props;
// eslint-disable-next-line camelcase
const createdAt = created_at;
// eslint-disable-next-line camelcase
const articleSlug = article_slug;
return (
<div className="item">
<div className="image">
<img alt="" className="article-image" src={image || 'https://i1.wp.com/thefrontline.org.uk/wp-content/uploads/2018/10/placeholder.jpg?ssl=1'} />
</div>
<div className="top aligned content">
<a className="header" href={`/articles/${articleSlug}`}>{title}</a>
<div className="excerpt">
{description}
</div>
<div className="small-margin tagline">
<span className="date"><Moment fromNow>{createdAt}</Moment></span>
</div>
<div className="tagline">
<span className="date">
By:
{' '}
{author.username}
<span className="stay">
{' '}
/ 500
{' '}
<i className="eye icon" />
Views.
</span>
</span>
</div>
</div>
<Details {...props} />
<div className="right float">
<Modal
trigger={(
Expand All @@ -53,7 +21,7 @@ const UserSpecificArticleComponent = (props) => {
className="delete-icon"
color="red"
onClick={() => {
setName(article_slug);
setName(articleSlug);
openModal();
}}
/>
Expand All @@ -66,16 +34,11 @@ const UserSpecificArticleComponent = (props) => {
<p>Are you sure you want to delete this article</p>
</Modal.Content>
<Modal.Actions>
<Button positive id="cancel-delete" onClick={() => onDelete(null, false)}>
Cancel
</Button>
<Button positive id="cancel-delete" onClick={() => onDelete(null, false)}>Cancel</Button>
<Button
negative
id="confirm-delete"
onClick={() => {
onDelete(name, true);
}
}
onClick={() => { onDelete(name, true); }}
>
Delete
</Button>
Expand All @@ -91,10 +54,6 @@ const UserSpecificArticleComponent = (props) => {
);
};
UserSpecificArticleComponent.propTypes = {
title: propTypes.string.isRequired,
description: propTypes.string.isRequired,
author: propTypes.string.isRequired,
created_at: propTypes.string.isRequired,
article_slug: propTypes.string.isRequired,
modalOpen: propTypes.bool.isRequired,
openModal: propTypes.func.isRequired,
Expand All @@ -104,5 +63,41 @@ UserSpecificArticleComponent.propTypes = {
image: propTypes.string.isRequired,
};

const Details = (props) => {
const {
title, description, author, created_at: createdAt,
article_slug: articleSlug, image,
} = props;
return (
<React.Fragment>
<div className="image">
<img alt="" className="article-image" src={image || 'https://i1.wp.com/thefrontline.org.uk/wp-content/uploads/2018/10/placeholder.jpg?ssl=1'} />
</div>
<div className="top aligned content">
<a className="header" href={`/articles/${articleSlug}`}>{title}</a>
<div className="excerpt">{description}</div>
<div className="small-margin tagline">
<span className="date"><Moment fromNow>{createdAt}</Moment></span>
</div>
<div className="tagline">
<span className="date">
By:
{' '}
{author.username}
</span>
</div>
</div>
</React.Fragment>
);
};

Details.propTypes = {
title: propTypes.string.isRequired,
description: propTypes.string.isRequired,
author: propTypes.string.isRequired,
created_at: propTypes.string.isRequired,
article_slug: propTypes.string.isRequired,
image: propTypes.string.isRequired,
};

export default UserSpecificArticleComponent;
20 changes: 14 additions & 6 deletions src/components/Articles/UserSpecificArticlesContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,10 @@ export class UserSpecificArticleContainer extends React.Component {
)
)

render() {
const {
articles, deleteSuccessful, history, loading,
} = this.props;
ArticlesComponents = () => {
const { articles } = this.props;
const { confirmDeleteOpen, name } = this.state;
const ArticlesComponents = articles.map(
return articles.map(
article => (
<UserSpecificArticleComponent
{...article}
Expand All @@ -73,6 +71,12 @@ export class UserSpecificArticleContainer extends React.Component {
/>
),
);
}

Component = () => {
const {
deleteSuccessful, history, loading, articles,
} = this.props;
const MyArticles = () => (
<React.Fragment>
<div className="space">
Expand All @@ -84,7 +88,7 @@ export class UserSpecificArticleContainer extends React.Component {
<div className="twelve centered wide column">
<div className="space">
{loading ? <Loader active />
: this.articlesFound(articles, ArticlesComponents, history)
: this.articlesFound(articles, this.ArticlesComponents(), history)
}
{ loading ? <Loader active /> : null }
</div>
Expand All @@ -110,6 +114,10 @@ export class UserSpecificArticleContainer extends React.Component {
</React.Fragment>
);
}

render() {
return this.Component();
}
}

UserSpecificArticleContainer.propTypes = {
Expand Down
6 changes: 4 additions & 2 deletions src/components/Authentication/Login/LoginComponent.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { Button } from 'semantic-ui-react';
import 'react-semantic-toasts/styles/react-semantic-alert.css';
import { Link, Redirect } from 'react-router-dom';
import { Link } from 'react-router-dom';
import logo from '../../../common/ah.logo-square.svg';
import './login.scss';
import FormInput from './FormInput';
Expand All @@ -18,6 +18,7 @@ const LoginComponent = ({
onChange,
error,
loading,
redirect,
success,
}) => (

Expand All @@ -32,7 +33,7 @@ const LoginComponent = ({
<div className="ui ten wide column">

<SuccessComponent success={success} />
{ success ? <Redirect to="/articles" /> : null }
{ success ? redirect() : null }

<div className="ui center aligned basic segment">
<h3>
Expand Down Expand Up @@ -100,6 +101,7 @@ LoginComponent.propTypes = {
onChange: PropTypes.func.isRequired,
error: PropTypes.shape().isRequired,
success: PropTypes.bool,
redirect: PropTypes.func.isRequired,
loading: PropTypes.bool.isRequired,
};

Expand Down
9 changes: 7 additions & 2 deletions src/components/Authentication/Login/LoginContainer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
import { Redirect } from 'react-router-dom';
import * as loginActions from '../../../actions/loginActions';
import LoginComponent from './LoginComponent';
import './login.scss';
Expand Down Expand Up @@ -35,8 +36,11 @@ class LoginContainer extends React.Component {
}

redirect() {
const { router } = this.context;
router.push('/articles');
const {
location,
} = this.props;
const { from } = location.state || { from: { pathname: '/articles' } };
return <Redirect to={from} />;
}

render() {
Expand All @@ -63,6 +67,7 @@ LoginContainer.propTypes = {
error: PropTypes.shape().isRequired,
success: PropTypes.bool,
loading: PropTypes.bool.isRequired,
location: PropTypes.shape().isRequired,
};

LoginContainer.defaultProps = { success: null };
Expand Down
9 changes: 7 additions & 2 deletions src/components/__tests__/DeletedArticleComponent.test.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import React from 'react';
import Enzyme, { shallow, mount } from 'enzyme';
import expect from 'expect';
import jest from 'jest-mock';
import Adapter from 'enzyme-adapter-react-16';
import DeletedArticle from '../Articles/DeletedArticle';

Enzyme.configure({
adapter: new Adapter(),
});

const go = jest.fn();
const history = {
go,
};
const setupEnzymeWrapper = () => {
const enzymeWrapper = shallow(<DeletedArticle />);
return {
Expand All @@ -21,8 +25,9 @@ describe('DeletedArticle component', () => {
expect(enzymeWrapper.exists()).toEqual(true);
});
it('should redirect to /myticles when clicked', () => {
const enzymeWrapper = mount(<DeletedArticle />);
const enzymeWrapper = mount(<DeletedArticle history={history} />);
enzymeWrapper.find('button').simulate('click');
expect(enzymeWrapper.exists()).toEqual(true);
expect(go).toHaveBeenCalled();
});
});

0 comments on commit 00a8b7f

Please sign in to comment.