From b053275dc92e5f73f20d3308206086c13c52b1b0 Mon Sep 17 00:00:00 2001 From: ATUHAIRE JUDE INNOCENT Date: Mon, 10 Dec 2018 16:53:59 +0300 Subject: [PATCH] ft(Create Article): User should be able to create an article - Add user create article form. - Add container for create article form. - Add component for create article form. - Add actions and reducers for create article. [#161348758] --- .env | 1 + package.json | 1 + public/index.html | 2 +- src/__snapshots__/index.test.js.snap | 4 + src/actions/actionTypes.js | 4 + .../createArticleActions.test.js | 62 ++++++ src/actions/createArticleActions/index.js | 44 ++++ src/actions/swalAlerts.js | 6 + src/commons/initialStates.js | 1 + src/components/App/index.js | 2 + .../CreateArticle/CreateArticle.scss | 50 +++++ .../CreateArticle/CreateArticle.test.js | 16 ++ .../__snapshots__/CreateArticle.test.js.snap | 122 ++++++++++++ src/components/CreateArticle/index.js | 63 ++++++ src/components/Header/Header.scss | 6 + src/components/Header/index.js | 20 +- .../CreateArticlePage.test.js | 61 ++++++ .../CreateArticlePage.test.js.snap | 188 ++++++++++++++++++ src/containers/CreateArticlePage/index.js | 68 +++++++ src/containers/Footer/index.scss | 2 +- src/containers/RatingPage/RatingPage.test.js | 2 +- .../createArticleReducer.js | 33 +++ .../createArticleReducer.test.js | 44 ++++ src/reducers/index.js | 2 + 24 files changed, 799 insertions(+), 5 deletions(-) create mode 100644 .env create mode 100644 src/actions/createArticleActions/createArticleActions.test.js create mode 100644 src/actions/createArticleActions/index.js create mode 100644 src/components/CreateArticle/CreateArticle.scss create mode 100644 src/components/CreateArticle/CreateArticle.test.js create mode 100644 src/components/CreateArticle/__snapshots__/CreateArticle.test.js.snap create mode 100644 src/components/CreateArticle/index.js create mode 100644 src/containers/CreateArticlePage/CreateArticlePage.test.js create mode 100644 src/containers/CreateArticlePage/__snapshots__/CreateArticlePage.test.js.snap create mode 100644 src/containers/CreateArticlePage/index.js create mode 100644 src/reducers/createArticleReducer/createArticleReducer.js create mode 100644 src/reducers/createArticleReducer/createArticleReducer.test.js diff --git a/.env b/.env new file mode 100644 index 0000000..6bdb0b7 --- /dev/null +++ b/.env @@ -0,0 +1 @@ +APP_URL=https://ah-backend-thanos-staging.herokuapp.com/api diff --git a/package.json b/package.json index d2047fd..5431c88 100644 --- a/package.json +++ b/package.json @@ -85,6 +85,7 @@ "react-router-dom": "^4.3.1", "react-toastify": "^4.4.3", "react-twitter-auth": "^0.0.12", + "react-tagsinput": "^3.19.0", "redux": "^4.0.1", "redux-devtools-extension": "^2.13.7", "redux-logger": "^3.0.6", diff --git a/public/index.html b/public/index.html index 841b966..7a48c01 100644 --- a/public/index.html +++ b/public/index.html @@ -21,4 +21,4 @@ - \ No newline at end of file + diff --git a/src/__snapshots__/index.test.js.snap b/src/__snapshots__/index.test.js.snap index c8dd364..7a8784d 100644 --- a/src/__snapshots__/index.test.js.snap +++ b/src/__snapshots__/index.test.js.snap @@ -35,6 +35,10 @@ exports[`Provider and App renders correctly 2`] = ` component={[Function]} path="/profiles/edit" /> + diff --git a/src/actions/actionTypes.js b/src/actions/actionTypes.js index 1e43b18..27e5f2d 100644 --- a/src/actions/actionTypes.js +++ b/src/actions/actionTypes.js @@ -25,6 +25,10 @@ const ACTION_TYPE = { POST_RATING_SUCCESS: 'POST_RATING_SUCCESS', POST_RATING_FAILED: 'POST_RATING_FAILED', POST_RATING_DATA: 'POST_RATING_DATA', + CREATE_ARTICLE_SUCCESS: 'CREATE_ARTICLE_SUCCESS', + CREATE_ARTICLE_FAILED: 'CREATE_ARTICLE_FAILED', + POST_ARTICLE_DATA: 'POST_ARTICLE_DATA', + UPDATE_IMAGE_URL: 'UPDATE_IMAGE_URL', }; export default ACTION_TYPE; diff --git a/src/actions/createArticleActions/createArticleActions.test.js b/src/actions/createArticleActions/createArticleActions.test.js new file mode 100644 index 0000000..7f271f4 --- /dev/null +++ b/src/actions/createArticleActions/createArticleActions.test.js @@ -0,0 +1,62 @@ +import moxios from 'moxios'; +import configureMockStore from 'redux-mock-store'; +import thunk from 'redux-thunk'; +import { + createArticleSuccess, createArticleThunk, +} from './index'; +import ACTION_TYPE from '../actionTypes'; +import APP_URL from '../../utils/constants'; + + +describe('Create article Actions tests', () => { + let store; + let actionTypesData; + let response; + beforeEach(() => { + response = { title: 'going to school' }; + const mockStore = configureMockStore([thunk]); + store = mockStore({}); + actionTypesData = actionType => ({ + type: actionType, + payload: response, + }); + // import and pass your custom axios instance to this method + moxios.install(); + }); + afterEach(() => { + // import and pass your custom axios instance to this method + moxios.uninstall(); + }); + test('Successful get and post rating action', () => { + expect(createArticleSuccess(response)).toEqual(expect.objectContaining( + actionTypesData(ACTION_TYPE.CREATE_ARTICLE_SUCCESS), + )); + }); + test('Post article successfull', () => { + moxios.stubRequest(`${APP_URL}/articles`, { + status: 200, + response: { message: 'ok' }, + }); + store.dispatch(createArticleThunk()).then(() => { + expect(store.getActions()).toEqual(expect.objectContaining( + { + type: ACTION_TYPE.CREATE_ARTICLE_SUCCESS, + payload: { message: 'ok' }, + }, + )); + }); + }); + test('Post article successfull', () => { + moxios.stubRequest(`${APP_URL}/articles`, { + status: 400, + responseText: { error: 'ok' }, + }); + store.dispatch(createArticleThunk()).then(() => { + expect(store.getActions()).toEqual(expect.objectContaining( + [{ + type: ACTION_TYPE.CREATE_ARTICLE_FAILED, + }], + )); + }); + }); +}); diff --git a/src/actions/createArticleActions/index.js b/src/actions/createArticleActions/index.js new file mode 100644 index 0000000..e717cf8 --- /dev/null +++ b/src/actions/createArticleActions/index.js @@ -0,0 +1,44 @@ +import axios from 'axios'; +import ACTION_TYPE from '../actionTypes'; +import APP_URL from '../../utils/constants'; + +export const postArticleData = response => ({ + type: ACTION_TYPE.POST_ARTICLE_DATA, + payload: response, +}); + +export const createArticleSuccess = response => ({ + type: ACTION_TYPE.CREATE_ARTICLE_SUCCESS, + payload: response, +}); + +export const createArticleFailure = errorMessage => ({ + type: ACTION_TYPE.CREATE_ARTICLE_FAILED, + errorMessage, +}); + +export const updateImageUrl = imageUrl => ({ + type: ACTION_TYPE.UPDATE_IMAGE_URL, + payload: imageUrl, +}); + +const tok = localStorage.getItem('token'); +export const createArticleThunk = data => (dispatch) => { + const userdata = { + ...data, + }; + const url = `${APP_URL}/articles`; + const token = `Token ${tok}`; + const headers = { + headers: { Authorization: token }, + }; + return axios.post(url, userdata, headers) + .then((res) => { + const resData = res.data.results; + dispatch(createArticleSuccess(resData)); + }).catch((error) => { + const errorData = error.response.data; + dispatch(createArticleFailure(errorData)); + }); +}; +export default createArticleThunk; diff --git a/src/actions/swalAlerts.js b/src/actions/swalAlerts.js index 8220028..8845db6 100644 --- a/src/actions/swalAlerts.js +++ b/src/actions/swalAlerts.js @@ -33,6 +33,12 @@ const swalMessages = { type: 'success', confirmButtonText: 'continue', }, + CREATE_ARTICLE_SUCCESSFUL: { + title: 'Article created', + text: 'Your article was successfully created', + type: 'success', + confirmButtonText: 'continue', + }, }; export default swalMessages; diff --git a/src/commons/initialStates.js b/src/commons/initialStates.js index 29c3468..2eabdf9 100644 --- a/src/commons/initialStates.js +++ b/src/commons/initialStates.js @@ -28,6 +28,7 @@ const initialState = { }, socialLoginReducer: { isLoggedIn: false }, loginReducer: { errorMessage: '', successMessage: '', user_details: '' }, + createArticleReducer: {}, userReducer: { freshUser: { email: '', password: '', username: '' }, }, diff --git a/src/components/App/index.js b/src/components/App/index.js index 8a68b05..bce1378 100644 --- a/src/components/App/index.js +++ b/src/components/App/index.js @@ -11,6 +11,7 @@ import Articles from '../../containers/Articles'; import ArticlePageConnected from '../../containers/ArticlePage'; import ProfileConnected from '../../containers/profiles/profiles'; import EditProfilePageConnected from '../../containers/profiles/editProfile'; +import CreateArticlePage from '../../containers/CreateArticlePage'; library.add(faSearch); const App = () => ( @@ -25,6 +26,7 @@ const App = () => ( + diff --git a/src/components/CreateArticle/CreateArticle.scss b/src/components/CreateArticle/CreateArticle.scss new file mode 100644 index 0000000..4b71caa --- /dev/null +++ b/src/components/CreateArticle/CreateArticle.scss @@ -0,0 +1,50 @@ +@import url('https://fonts.googleapis.com/css?family=Roboto+Condensed'); + +#titleInput, +#descriptionInput, +#image_urlInput, +#tag_listInput { + height: 40px; + border-top: none; + border-left: none; + border-right: none; +} + +#createArticleBt { + padding: 15px; + width: 30%; + background-color: #47d79f; + border: none; + font-family: "Roboto Condensed", sans-serif; + &:hover { + background-color: #3db184; + } + +} +.artcileCreate{ + font-family: "Roboto Condensed", sans-serif; +} +.createArticleTitle { + font-family: "Roboto Condensed", sans-serif; +} + +.artcileCreate { + padding-bottom: 30px; +} +#bodyInput { + height: 500px; +} +.create-article-form { + width: 80%; +} + +.image-upload{ + color: turquoise !important; + border-color: turquoise !important; } + +.image { + color: white; +} +input[type="file"] { + border-color: turquoise !important; +} diff --git a/src/components/CreateArticle/CreateArticle.test.js b/src/components/CreateArticle/CreateArticle.test.js new file mode 100644 index 0000000..a698c35 --- /dev/null +++ b/src/components/CreateArticle/CreateArticle.test.js @@ -0,0 +1,16 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import CreateArticle from './index'; + +describe(' ', () => { + const CreateArticleComponent = shallow( + , + ); + test('renders the component', () => { + expect(CreateArticleComponent).toMatchSnapshot(); + }); +}); diff --git a/src/components/CreateArticle/__snapshots__/CreateArticle.test.js.snap b/src/components/CreateArticle/__snapshots__/CreateArticle.test.js.snap new file mode 100644 index 0000000..8b8e171 --- /dev/null +++ b/src/components/CreateArticle/__snapshots__/CreateArticle.test.js.snap @@ -0,0 +1,122 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` renders the component 1`] = ` +
+
+

+ Create Your Article +

+
+
+
+
+
+ Title: +
+ + Title must be 130 characters maximum and 5 characters minimum + +
+