({
+ dropdownOpen: !prevState.dropdownOpen,
+ }));
+ }
+
+ render() {
+ return (
+
+
+
+
+
+
+ Report This Article
+
+ Rate this Article
+
+ Bookmark this Article
+
+
+
+ );
+ }
+}
+
+export default DropLeft;
diff --git a/src/components/SingleArticle/RatingsModal/index.jsx b/src/components/SingleArticle/RatingsModal/index.jsx
new file mode 100644
index 0000000..5ed2973
--- /dev/null
+++ b/src/components/SingleArticle/RatingsModal/index.jsx
@@ -0,0 +1,66 @@
+import React from 'react';
+import {
+ Modal, Button,
+} from 'bootstrap-4-react';
+import StarRatingComponent from 'react-star-rating-component';
+import PropTypes from 'prop-types';
+
+/**
+ * This component is called RatingsModal component, it renders the ratings modal and also holds
+ * the button to close or save ratings.
+ */
+const RatingsModal = (props) => {
+ const {
+ title, rating, starClick, handleRatingsSubmit,
+ } = props;
+ return (
+
+
+
+ {title}
+
+ ×
+
+
+
+
+
+Your current rating for this article is:
+ {' '}
+ {rating}
+ {' '}
+stars
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+RatingsModal.defaultProps = {
+ title: '',
+ starClick: () => '',
+ handleRatingsSubmit: () => '',
+ rating: 0,
+};
+
+RatingsModal.propTypes = {
+ title: PropTypes.string,
+ starClick: PropTypes.func,
+ handleRatingsSubmit: PropTypes.func,
+ rating: PropTypes.number,
+};
+
+export default RatingsModal;
diff --git a/src/components/SingleArticle/index.jsx b/src/components/SingleArticle/index.jsx
index ccbe595..60a9a28 100644
--- a/src/components/SingleArticle/index.jsx
+++ b/src/components/SingleArticle/index.jsx
@@ -1,6 +1,8 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
+import { Modal } from 'bootstrap-4-react';
+import jwtDecode from 'jwt-decode';
import MainArticle from './MainArticle';
import articleActions from '../../actions/ArticleActions';
import Tags from './Tags';
@@ -8,11 +10,16 @@ import Recommended from './Recommended';
import CommentsBtn from './CommentsBtn';
import Loader from '../Loader';
import './index.scss';
+import DropLeft from './RatingsBtn/index';
+import RatingsModal from './RatingsModal/index';
+import { articleRating } from '../../actions/article.action';
const { viewArticle, fetchArticles } = articleActions;
export class SingleArticle extends Component {
- state = {}
+ state = {
+ value: 0,
+ }
componentDidMount() {
const { slug } = this.props.match.params;
@@ -27,19 +34,53 @@ export class SingleArticle extends Component {
}
}
- render() {
- if (this.props.article === {} || this.props.loading) {
- return ;
- }
- return (
-
-
-
-
-
-
- );
+ onStarClick = (nextValue) => {
+ this.setState({ value: nextValue });
}
+
+
+handleRatingsSubmit = () => {
+ const { match } = this.props;
+ const { params: { slug } } = match;
+ const { value } = this.state;
+ const { articleRating: articleRate } = this.props;
+ articleRate(slug, value);
+}
+
+checkUser = () => {
+ const token = localStorage.jwtToken;
+ const decoded = jwtDecode(token);
+ const { id } = decoded;
+ const { userId } = this.props.article;
+ if (id !== userId) {
+ return true;
+ }
+}
+
+render() {
+ const token = localStorage.jwtToken;
+ if (this.props.article === {} || this.props.loading) {
+ return ;
+ }
+ const { value } = this.state;
+ return (
+
+
+
+ { token && this.checkUser() && }
+
+
+
+
+
+
+ );
+}
}
export const mapStateToProps = state => ({
@@ -56,10 +97,12 @@ SingleArticle.propTypes = {
}).isRequired,
viewArticle: PropTypes.func.isRequired,
fetchArticles: PropTypes.func.isRequired,
+ articleRating: PropTypes.func.isRequired,
article: PropTypes.shape({}).isRequired,
recommendedArticles: PropTypes.shape({}).isRequired,
loading: PropTypes.bool.isRequired,
history: PropTypes.shape({}).isRequired,
};
-export default connect(mapStateToProps, { viewArticle, fetchArticles })(SingleArticle);
+export default connect(mapStateToProps,
+ { viewArticle, fetchArticles, articleRating })(SingleArticle);
diff --git a/src/components/SingleArticle/index.scss b/src/components/SingleArticle/index.scss
index e53846b..ddbb693 100644
--- a/src/components/SingleArticle/index.scss
+++ b/src/components/SingleArticle/index.scss
@@ -1,3 +1,16 @@
.article-container {
padding-bottom: 100px;
}
+
+.remove-border {
+ border-color: transparent !important;
+}
+.remove-border:focus {
+ box-shadow: none !important;
+}
+
+@media only screen and (max-width: 870px) {
+ .fa-4x {
+ font-size: 2rem !important;
+ }
+}
\ No newline at end of file
diff --git a/src/config/axiosInstance.js b/src/config/axiosInstance.js
index dd5092b..e538b71 100644
--- a/src/config/axiosInstance.js
+++ b/src/config/axiosInstance.js
@@ -1,7 +1,14 @@
import axios from 'axios';
+const token = localStorage.getItem('jwtToken');
+
+
const instance = axios.create({
baseURL: 'https://ah-nyati-backend-staging.herokuapp.com/api/v1',
+ headers: {
+ 'Content-Type': 'application/json',
+ token,
+ },
});
export default instance;
diff --git a/src/reducers/index.js b/src/reducers/index.js
index 0f2c889..f2deca3 100644
--- a/src/reducers/index.js
+++ b/src/reducers/index.js
@@ -9,7 +9,6 @@ import articles from './articlesReducer';
import user from './getUserReducer';
import success from './success';
import singleArticleReducer from './singleArticleReducer';
-
import createArticleReducer from './article/article';
import fetchCategoryReducer from './category/category';
diff --git a/src/reducers/singleArticleReducer.js b/src/reducers/singleArticleReducer.js
index 663daec..c0eb90c 100644
--- a/src/reducers/singleArticleReducer.js
+++ b/src/reducers/singleArticleReducer.js
@@ -1,6 +1,17 @@
-import { VIEW_SINGLE_ARTICLE, SET_LOADING } from '../actions/types';
+import {
+ VIEW_SINGLE_ARTICLE,
+ SET_LOADING,
+ RATE_ARTICLE_START,
+ RATE_ARTICLE_SUCCESS,
+ RATE_ARTICLE_FAILURE,
+} from '../actions/types';
-const initialState = { article: {}, loading: false };
+const initialState = {
+ article: {},
+ loading: false,
+ rating: '',
+ alert: '',
+};
export default function (state = initialState, action) {
switch (action.type) {
@@ -15,6 +26,23 @@ export default function (state = initialState, action) {
loading: false,
article: action.payload,
};
+ case RATE_ARTICLE_START:
+ return {
+ ...state,
+ loading: true,
+ };
+ case RATE_ARTICLE_SUCCESS:
+ return {
+ ...state,
+ loading: false,
+ rating: action.rating,
+ alert: action.message,
+ };
+ case RATE_ARTICLE_FAILURE:
+ return {
+ ...state,
+ loading: false,
+ };
default:
return state;
}