Skip to content

Commit

Permalink
Merge pull request #35 from andela/feature/164829182-user-report-article
Browse files Browse the repository at this point in the history
feature(report-article): users can report an article in violation
  • Loading branch information
Mirriam-Maina committed May 24, 2019
2 parents d395adf + 637feff commit 5129ac6
Show file tree
Hide file tree
Showing 13 changed files with 316 additions and 2 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"react-router-dom": "^5.0.0",
"react-scripts": "3.0.0",
"react-semantic-toasts": "^0.5.3",
"react-toastify": "^5.1.1",
"redux": "^4.0.1",
"redux-devtools-extension": "^2.13.8",
"redux-mock-store": "^1.5.3",
Expand Down
1 change: 0 additions & 1 deletion src/actions/articlesActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ export const getAllArticles = page => (dispatch) => {
};

export const getSingleArticles = (slug, history) => (dispatch) => {
dispatch(loadingMessage(ARTICLE_LOADING_PROGRESS));
return api.articles.getSingleArticles(slug)
.then((response) => {
dispatch(singleArticleSuccessMessage(response.data.article));
Expand Down
24 changes: 24 additions & 0 deletions src/actions/reportArticleActions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { toast } from 'react-toastify';
import { REPORT_SUCCESS, REPORT_FAILED, REPORTED_ARTICLE, LOADING_REPORT } from './types';
import { api } from '../services/Api';
import { loadingMessage } from './articlesActions';

export function successMessage(responseData) {
return { type: REPORT_SUCCESS, payload: responseData };
}
export function failureMessage(error) {
return { type: REPORT_FAILED, payload: error };
}

export const reportAnArticleAction = data => (dispatch, history) => {
dispatch(loadingMessage(LOADING_REPORT));
return api.articles.reportAnArticle(data)
.then((response) => {
dispatch(successMessage(response.data));
})
.catch((error) => {
dispatch(failureMessage(error.response.data));
});
};

export default reportAnArticleAction;
7 changes: 7 additions & 0 deletions src/actions/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,13 @@ export const STATS_LOADING_PROGRESS = 'STATS_LOADING_PROGRESS';
export const FETCH_ALL_MY_VIEWS = 'FETCH_ALL_MY_VIEWS';
export const FETCH_MY_ARTICLE_VIEWS = 'FETCH_MY_ARTICLE_VIEWS';
export const ERROR_FETCHING_STATS = 'ERROR_FETCHING_STATS';

export const NOTIFICATION_LIST_UPDATED = 'NOTIFICATION_LIST_UPDATED';
export const FRESH_NOTIFICATIONS_LIST = 'FRESH_NOTIFICATIONS_LIST';
export const RESET_NOTIFICATIONS_COUNT = 'RESET_NOTIFICATIONS_COUNT';

export const REPORT_ARTICLE = 'REPORT_ARTICLE';
export const REPORT_SUCCESS = 'REPORT_SUCCESS';
export const REPORT_FAILED = 'REPORT_FAILED';
export const LOADING_REPORT = 'LOADING_REPORT';
export const SHOW_MODAL = 'SHOW_MODAL';
20 changes: 20 additions & 0 deletions src/assets/scss/base/_base.scss
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,26 @@ body {
.p-3{
padding-top: 3rem;
}
.left-aligner{
float: right !important;
}

.fa-ellipsis-h{
float: right;
font-size: 40px;
}
.report-article{
cursor: pointer;
}

.line-spacing{
display: block;
margin-bottom: 0.5em;
}

.modal-header {
font-size: 3.5rem !important;
}

.ui.primary.button, .ui.primary.buttons .button {
background-color: var(--color-primary);
Expand Down
1 change: 0 additions & 1 deletion src/components/CustomArticle/MyArticleDraftItem.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ export class MyArticleDraftItem extends Component {
</a>
<p>{article.description}</p>
</div>

<div className="p-3">
Published &nbsp;
{moment(article.created_at).format('MMM Do YY')}
Expand Down
5 changes: 5 additions & 0 deletions src/components/CustomArticle/SingleArticleItem.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Button, Popup } from 'semantic-ui-react';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import moment from 'moment';
Expand All @@ -18,6 +19,7 @@ import {
removeBookmark,
} from '../../actions/bookmarksActions';
import isEmpty from '../../utils/is_empty';
import ReportArticleModal from '../ReportArticle/ReportArticleModal';

export class SingleArticleItem extends Component {
handleBookmark = (e, slug) => {
Expand Down Expand Up @@ -229,6 +231,9 @@ export class SingleArticleItem extends Component {
<div className="social-share-circles">
<CircularSocial size="huge" shareLinks={article.share_links ? article.share_links : {}}/>
</div>
<span className="left-aligner">
<ReportArticleModal slug={article.slug}/>
</span>
</div>
</div>
)}
Expand Down
140 changes: 140 additions & 0 deletions src/components/ReportArticle/ReportArticleModal.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import React, { Component } from 'react';
import { PropTypes } from 'prop-types';
import _ from 'lodash';
import { connect } from 'react-redux';
import { Button, Header, Icon, Modal, Form, Popup, Message } from 'semantic-ui-react';
import { reportAnArticleAction } from '../../actions/reportArticleActions';
import { SHOW_MODAL } from '../../actions/types';
import store from '../../store';
import isEmpty from "../../utils/is_empty";

export class ReportArticleModal extends Component {
constructor() {
super();
this.state = {
type_of_report: '',
report: '',
};
}

handleChange = (event) => {
this.setState({ [event.target.name]: event.target.value });
};

handleSelectChange=(e, { value }) => this.setState({ type_of_report: value });

reportArticle = (event) => {
event.preventDefault();
const { articleReporter, slug } = this.props;

articleReporter({ slug, report: this.state });
store.dispatch({ type: SHOW_MODAL, payload: { modalShow: false } });
};

componentWillReceiveProps(nextProps) {
if (nextProps.reports){
this.setState({
type_of_report: '',
report: ''
})
}
}

render() {
const countryOptions = _.map(['spam', 'harassment', 'rules violation', 'plagiarism'], country => ({
key: country,
text: country,
value: country,
}));
const {type_of_report, report} = this.state
const {loading, reports, errorMessage} = this.props;

console.log(this.props)

return (
<Modal
as='h1'
trigger={
<Popup
trigger={<Button circular size='large' icon="fas fa-ellipsis-h" />}
content='Report this article'
on={['click']}
hideOnScroll
position='bottom center'
/>
}
basic
closeIcon>
<Header
as='h1'
icon='shield alternate'
content='REPORT ARTICLE' />
<Modal.Content>
{!isEmpty(reports.report)
? <Message success header='Reporting Completed' content="Your report has successfully been recieved. Kindly close the form." />
: ''
}

<Form loading={loading}>
<Form.Select
label="Type of report"
placeholder='Type_of_report'
options={countryOptions}
name='type_of_report'
value={type_of_report}
onChange={this.handleSelectChange}
error={!isEmpty(errorMessage ? errorMessage.type_of_report : '')}
/>
<Form.TextArea
label='Report'
placeholder='Tell us more about it...'
name='report'
value={report}
onChange={this.handleChange}
error={!isEmpty(errorMessage ? errorMessage.report : '')}
/>
</Form>
</Modal.Content>
<Modal.Actions>
<Button
color='green'
type="submit"
onClick={this.reportArticle}
>
<Icon name='checkmark' />
Report
</Button>
</Modal.Actions>
</Modal>
);
}
}


ReportArticleModal.defaultProps = {
slug: '',
loading: false,
reports: {},
errorMessage: {},
};

ReportArticleModal.propTypes = {
isLoading: PropTypes.bool.isRequired,
slug: PropTypes.string,
articleReporter: PropTypes.func.isRequired,
};

export const mapStateToProps = ({reports}) => ({
loading: reports.loading,
reports: reports.report,
errorMessage: reports.errorMessage.errors,
});

export const mapDispatchToProps = dispatch => ({
articleReporter: (slug, reportData) => {
dispatch(reportAnArticleAction(slug, reportData));
},
});

export default connect(mapStateToProps, mapDispatchToProps)(ReportArticleModal);
// export default ReportArticleModal;
47 changes: 47 additions & 0 deletions src/components/ReportArticle/__test__/ReportModal.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import React from 'react';
import { shallow, mount } from 'enzyme';
import { ReportArticleModal } from '../ReportArticleModal';


const setUp = () => {
const props = {
slug: "try",
isLoading: false,
articleReporter: jest.fn(),
};
const wrapper = shallow(<ReportArticleModal {...props} />);

return {
props,
wrapper,
};
};

describe('ReportArticleModal slug test', () => {
const { wrapper, props } = setUp();

it(' render slug section exists', () => {
const slugSection = wrapper.find('slug');
expect(wrapper.find('Form')).toBeDefined();

expect(slugSection.exists())
.toBe(false);
});
});

describe('ReportArticleModal', () => {
it('displays Form tag correctly', () => {
const wrapper = shallow(<ReportArticleModal />);
expect(wrapper.find('Modal')).toBeDefined();
});

it('displays Modal.Content tag correctly', () => {
const component = shallow(<ReportArticleModal />);
expect(component.find('Modal.Content')).toBeDefined();
});

it('displays the select tag correctly', () => {
const component = shallow(<ReportArticleModal debug />);
expect(component.find('Form.Select')).toBeDefined();
});
});
35 changes: 35 additions & 0 deletions src/reducers/__tests__/reportArticleReducer.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import expect from 'expect';
import reducer from '../reportArticleReducer';

import {
successMessage,
reportAnArticleAction,
failureMessage,
} from '../../actions/reportArticleActions';

import { loadingMessage } from '../../actions/articlesActions';

const INITIAL_STATE = {
loading: false,
loading: true,
report: {"id": 1},
errorMessage: {},
};

describe('Testing REPORT ARTICLE REDUCER', () => {
it('should return the initial state', () => {
const state = reducer(INITIAL_STATE, {});
expect(state).toEqual(INITIAL_STATE);
});

it('should handle LOADING_PROGRESS', () => {
const loadingAction = loadingMessage();
const state = reducer(INITIAL_STATE, loadingAction);
const expectedState = {
...INITIAL_STATE,
loading: true,
};
expect(state)
.toEqual(expectedState);
});
});
2 changes: 2 additions & 0 deletions src/reducers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import bookmarksReducer from './bookmarksReducer';
import notificationsReducer from './notificationsReducer';
import commentsReducer from './commentsReducer';
import analyticsReducers from './analyticsReducers';
import reportArticleReducer from './reportArticleReducer';

export default combineReducers({
auth: authReducer,
Expand All @@ -22,4 +23,5 @@ export default combineReducers({
notifications: notificationsReducer,
comments: commentsReducer,
analytics: analyticsReducers,
reports: reportArticleReducer,
});
34 changes: 34 additions & 0 deletions src/reducers/reportArticleReducer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { REPORT_SUCCESS, REPORT_FAILED, LOADING_REPORT } from '../actions/types';

export const initialState = {
loading: false,
report: {},
errorMessage: {},
};

const reportArticleReducers = (state = initialState, action) => {
switch (action.type) {
case LOADING_REPORT:
return {
...state,
loading: true,
};
case REPORT_SUCCESS:
return {
...state,
report: action.payload,
errorMessage: {},
loading: false,
};
case REPORT_FAILED:
return {
...state,
errorMessage: action.payload,
loading: false,
};
default:
return state;
}
};

export default reportArticleReducers;
1 change: 1 addition & 0 deletions src/services/Api.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export const api = {
dislikeArticle: slug => axios.post(`${API_HOST}/articles/${slug}/dislike/`),
updateArticleRating: data => axios.post(`${API_HOST}/articles/${data.rating.slug}/ratings/`, data),
searchArticles: data => axios.get(`${API_HOST}/articles/q?${data.key}=${data.value}`),
reportAnArticle: data => axios.post(`${API_HOST}/articles/${data.slug}/reports/`, data),
},
bookmarks: {
getAllBookmarkArticle: () => axios.get(`${API_HOST}/bookmarks/`),
Expand Down

0 comments on commit 5129ac6

Please sign in to comment.