Skip to content

Commit

Permalink
ft(like): A user should be able to like or dislike an article (#36)
Browse files Browse the repository at this point in the history
- A user provides sentiment on the article through likes and dislikes

[Maintains #164798172]
  • Loading branch information
dannylwe authored and hadijahkyampeire committed May 22, 2019
1 parent 8a6bcac commit 6deac09
Show file tree
Hide file tree
Showing 27 changed files with 558 additions and 12,368 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ node_modules/
coverage/
src/.DS_Store
.env
package-lock.json
12,291 changes: 0 additions & 12,291 deletions package-lock.json

This file was deleted.

2 changes: 1 addition & 1 deletion src/actions/facebookActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const loginWithFb = token => (dispatch) => {
});
localStorage.setItem('accessToken', resp.data.auth_token.token);
localStorage.setItem('userAuthenticated', true);
document.location.href = '/';
document.location.href = '/dashboard';
});
};

Expand Down
51 changes: 51 additions & 0 deletions src/actions/likeDislikeActions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { notify } from 'react-notify-toast';
import axios from '../helpers/axiosInstance';
import {
LIKE_AN_ARTICLE, DISLIKE_AN_ARTICLE,
ERROR, ALREADY_LIKE_AN_ARTICLE,
ALREADY_DISLIKE_AN_ARTICLE,
} from './types';
import { getSingleArticle } from './articlesActions';

export const likeAnArticle = slug => dispatch => axios
.post(`${process.env.baseURL}/articles/${slug}/like`, {
likes: true,
})
.then((response) => {
if (response.data.msg !== 'You have already liked this article') {
dispatch({ type: LIKE_AN_ARTICLE, payload: 1 });
notify.show('You have LIKED this Article', 'success', 2000);
getSingleArticle(slug);
} else {
dispatch({ type: ALREADY_LIKE_AN_ARTICLE, payload: 0 });
notify.show('You have ALREADY LIKED this Article', 'error', 2000);
}
})
.catch(() => {
dispatch({
type: ERROR,
payload: 'Can not fetch your data, please login again',
});
notify.show('You have been logged out, Please Login', 'error', 2000);
});

export const dislikeAnArticle = slug => dispatch => axios
.post(`${process.env.baseURL}/articles/${slug}/like`, {
likes: false,
})
.then((response) => {
if (response.data.msg !== 'You have already disliked this article') {
dispatch({ type: DISLIKE_AN_ARTICLE, payload: 1 });
notify.show('You have DISLIKED this Article', 'success', 2000);
} else {
dispatch({ type: ALREADY_DISLIKE_AN_ARTICLE, payload: 0 });
notify.show('You have ALREADY DISLIKED this Article', 'error', 2000);
}
})
.catch(() => {
dispatch({
type: ERROR,
payload: 'Can not fetch your data, please login again',
});
notify.show('You have been logged out, Please Login', 'error', 2000);
});
141 changes: 141 additions & 0 deletions src/actions/tests/likeDislikeActions.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import moxios from 'moxios';
import instance from '../../helpers/axiosInstance';
import { likeAnArticle, dislikeAnArticle } from '../likeDislikeActions';
import {
LIKE_AN_ARTICLE, DISLIKE_AN_ARTICLE,
ALREADY_DISLIKE_AN_ARTICLE,
ALREADY_LIKE_AN_ARTICLE,
ERROR,
} from '../types';


const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
jest.mock('react-notify-toast');

const localStorageMock = {
getItem: jest.fn(),
setItem: jest.fn(),
clear: jest.fn(),
};
global.localStorage = localStorageMock;

describe('like and dislike', () => {
beforeEach(() => {
moxios.install(instance);
});

afterEach(() => {
moxios.uninstall(instance);
});
it('likes an article successfully', () => {
const store = mockStore({});
moxios.wait(() => {
const requestM = moxios.requests.mostRecent();
requestM.respondWith({
status: 200,
response: {
article: 'this is a test article',
username: 'test_user',
details: {
likes: true,
},
},
});
});
const expectedActions = [{ type: LIKE_AN_ARTICLE, payload: 1 }];
return store.dispatch(likeAnArticle('testSlug')).then(() => {
expect(store.getActions()).toEqual(expectedActions);
});
});
it('dislikes an article successfully', () => {
const store = mockStore({});
moxios.wait(() => {
const requestM = moxios.requests.mostRecent();
requestM.respondWith({
status: 200,
response: {
article: 'this is a test article',
username: 'test_user',
details: {
likes: false,
},
},
});
});
const expectedActions = [{ type: DISLIKE_AN_ARTICLE, payload: 1 }];
return store.dispatch(dislikeAnArticle('testSlug')).then(() => {
expect(store.getActions()).toEqual(expectedActions);
});
});
it('already dislike an article', () => {
const store = mockStore({});
moxios.wait(() => {
const requestM = moxios.requests.mostRecent();
requestM.respondWith({
status: 200,
response: {
msg: 'You have already disliked this article',
},
});
});
const expectedActions = [{ type: ALREADY_DISLIKE_AN_ARTICLE, payload: 0 }];
return store.dispatch(dislikeAnArticle('testSlug')).then(() => {
expect(store.getActions()).toEqual(expectedActions);
});
});
it('already liked an article', () => {
const store = mockStore({});
moxios.wait(() => {
const requestM = moxios.requests.mostRecent();
requestM.respondWith({
status: 200,
response: {
msg: 'You have already liked this article',
},
});
});
const expectedActions = [{ type: ALREADY_LIKE_AN_ARTICLE, payload: 0 }];
return store.dispatch(likeAnArticle('testSlug')).then(() => {
expect(store.getActions()).toEqual(expectedActions);
});
});
it('log in required, at liking article', () => {
const store = mockStore({});
moxios.wait(() => {
const requestM = moxios.requests.mostRecent();
requestM.respondWith({
status: 403,
response: {
},
});
});
const expectedActions = [{
type: ERROR,
payload: 'Can not fetch your data, please login again',
}];
return store.dispatch(likeAnArticle('testSlug')).then(() => {
expect(store.getActions()).toEqual(expectedActions);
});
});
it('log in required, at disliking article', () => {
const store = mockStore({});
moxios.wait(() => {
const requestM = moxios.requests.mostRecent();
requestM.respondWith({
status: 403,
response: {
},
});
});
const expectedActions = [{
type: ERROR,
payload: 'Can not fetch your data, please login again',
}];
return store.dispatch(dislikeAnArticle('testSlug')).then(() => {
expect(store.getActions()).toEqual(expectedActions);
});
});
});
53 changes: 53 additions & 0 deletions src/actions/tests/likeDislikeReducer.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import {
LIKE_AN_ARTICLE,
ALREADY_DISLIKE_AN_ARTICLE,
DISLIKE_AN_ARTICLE,
ALREADY_LIKE_AN_ARTICLE,
} from '../types';
import likeDislikeReducer from '../../reducers/likeDislikeReducer';

describe('the like dislike reducer', () => {
const initialState = {
likes: null,
};
it('should like an article', () => {
const theDispatchedAction = {
type: LIKE_AN_ARTICLE,
};
const newState = {
likes: 1,
};
expect(likeDislikeReducer(initialState,
theDispatchedAction)).toEqual(newState);
});
it('should dislike an article', () => {
const theDispatchedAction = {
type: DISLIKE_AN_ARTICLE,
};
const newState = {
likes: 0,
};
expect(likeDislikeReducer(initialState,
theDispatchedAction)).toEqual(newState);
});
it('should already dislike an article', () => {
const theDispatchedAction = {
type: ALREADY_DISLIKE_AN_ARTICLE,
};
const newState = {
likes: null,
};
expect(likeDislikeReducer(initialState,
theDispatchedAction)).toEqual(newState);
});
it('should already liked an article', () => {
const theDispatchedAction = {
type: ALREADY_LIKE_AN_ARTICLE,
};
const newState = {
likes: null,
};
expect(likeDislikeReducer(initialState,
theDispatchedAction)).toEqual(newState);
});
});
4 changes: 4 additions & 0 deletions src/actions/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,7 @@ export const GET_NEXT = 'GET_NEXT';
export const ORIGINAL = 'ORIGINAL';
export const PAGE_STATE_CHANGE = 'PAGE_STATE_CHANGE';
export const FETCH_COMMENTS = 'FETCH_COMMENTS';
export const LIKE_AN_ARTICLE = 'LIKE_AN_ARTICLE';
export const ALREADY_LIKE_AN_ARTICLE = 'ALREADY_LIKE_AN_ARTICLE';
export const ALREADY_DISLIKE_AN_ARTICLE = 'ALREADY_LIKE_AN_ARTICLE';
export const DISLIKE_AN_ARTICLE = 'DISLIKE_AN_ARTICLE';
56 changes: 43 additions & 13 deletions src/components/Articles/ArticleDetail.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const ArticleDetail = ({
deleteComment,
updateComment,
onChangeComment,
likeArticle, dislikeArticle,
}) => (
<div className="container article-detail-container">
<div className="row">
Expand All @@ -28,14 +29,30 @@ const ArticleDetail = ({
<h1 className="card-title article-title ">{article.title}</h1>
<div className="row">
<div className="col-md-3">
<img src={avartaImage} className="img-circle" alt="user-icon" width="70px" height="70px" />
<img
src={avartaImage}
className="img-circle"
alt="user-icon"
width="70px"
height="70px"
/>
</div>
</div>
<br />
<button type="button" className="btn btn-sm btn-outline-secondary">Unfollow</button>
<button
type="button"
className="btn btn-sm btn-outline-secondary"
>
Unfollow
</button>
&nbsp;
&nbsp;
<button type="button" className="btn btn-sm btn-outline-secondary">Follow</button>
<button
type="button"
className="btn btn-sm btn-outline-secondary"
>
Follow
</button>
<p>
{article.author && (
<b>
Expand All @@ -46,11 +63,10 @@ const ArticleDetail = ({
)}
</p>
<h6 className="card-subtitle mb-2 text-muted">
{
article.createdAt && moment(article.createdAt.slice(0, 10)).format(
'LL',
)
}
{article.createdAt && moment(article.createdAt.slice(0, 10))
.format(
'LL',
)}

</h6>
<br />
Expand All @@ -72,7 +88,8 @@ const ArticleDetail = ({
</span>
))}
</p>
{article.author && article.author.username === localStorage.getItem('username')
{article.author && article.author.username
=== localStorage.getItem('username')
? (
<div className="article-btns">
<Link
Expand All @@ -96,10 +113,23 @@ const ArticleDetail = ({
<i className="fas fa-trash-alt" />
</button>
</div>
)
: ''

}
) : ''}
<div className="like-dislike">
<i
className="far fa-thumbs-up"
onClick={likeArticle}
role="presentation"
/>
{' '}
{article.likes}
<i
className="far fa-thumbs-down"
onClick={dislikeArticle}
role="presentation"
/>
{' '}
{article.dislikes}
</div>
<DeleteArticleComponentModel
title={article.title}
slug={article.slug}
Expand Down
5 changes: 4 additions & 1 deletion src/components/Articles/DisplayArticles.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ const DisplayArticles = ({ article, articleDate }) => (
<div className="row">
<div className="col-md-12">
<div className="row">
<div className="card col-md-12" style={{ boxShadow: '5px 10px 18px #888888' }}>
<div
className="card col-md-12"
style={{ boxShadow: '5px 10px 18px #888888' }}
>
<h5 className="card-header">{article.title}</h5>
<div className="card-body">
<h5 className="card-title">
Expand Down
10 changes: 9 additions & 1 deletion src/components/Articles/article.scss
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,15 @@ div.ReactTags__suggestions ul li.ReactTags__activeSuggestion {
}

.custom-model-dialog{
padding-top: 240px;
padding-top: 15rem;
}

.fa-thumbs-up, .fa-thumbs-down {
cursor: pointer;
font-size: 1.875rem;
padding: 0.9375rem;
margin-left: 60px;

}

.delete-article-title {
Expand Down
Loading

0 comments on commit 6deac09

Please sign in to comment.