diff --git a/_test_/Dashboard.spec.js b/_test_/Dashboard.spec.js index 2d85e96..f31d0ee 100644 --- a/_test_/Dashboard.spec.js +++ b/_test_/Dashboard.spec.js @@ -1,37 +1,49 @@ import React from 'react'; import expect from 'expect'; import { shallow } from 'enzyme'; +import configureMockStore from 'redux-mock-store'; -import Dashboard from '../src/views/Dashboard'; +import { UserDashboard } from '../src/components/Dashboard/index'; + +const mockStore = configureMockStore(); describe('Dashboard', () => { - let component; + const props = { + authUserProfile: jest.fn(), + getAuthUserFollowers: jest.fn(), + getAuthUserFollowee: jest.fn(), + getAuthUserArticles: jest.fn(), + auth: { + user: { + userName: '', + id: '', + }, + }, + }; + + let app; + let store; beforeEach(() => { - component = shallow(); + store = mockStore(); + app = shallow(); }); it('renders successfully', () => { - expect(component).toBeDefined(); - }); - - it('renders a div component', () => { - expect(component.find('div').length).toBe(1); - }); - - it('renders an h4 tag', () => { - expect(component.find('h4').length).toBe(1); + expect(app).toBeDefined(); }); - it('renders a ul tag', () => { - expect(component.find('ul').length).toBe(2); + it('renders a div tag', () => { + expect(app.find('div').length).toBe(1); }); - it('renders a li tag', () => { - expect(component.find('li').length).toBe(2); + it('renders dashboard index', () => { + expect(app).toMatchSnapshot(); }); - it('renders a Link tag', () => { - expect(component.find('Link').length).toBe(2); + it('renders without auth', () => { + props.auth = null; + app = shallow(); + expect(app).toMatchSnapshot(); }); }); diff --git a/_test_/DraftArticles.spec.js b/_test_/DraftArticles.spec.js new file mode 100644 index 0000000..f0b8448 --- /dev/null +++ b/_test_/DraftArticles.spec.js @@ -0,0 +1,60 @@ +import React from 'react'; +import expect from 'expect'; +import Enzyme, { shallow } from 'enzyme'; +import configureMockStore from 'redux-mock-store'; +import Adapter from 'enzyme-adapter-react-16'; +import { DraftArticles } from '../src/components/Dashboard/DraftArticles'; + +const mockStore = configureMockStore(); +Enzyme.configure({ adapter: new Adapter() }); + +describe('Draft Articles', () => { + const props = { + getAuthUserDraftArticles: jest.fn(), + auth: { user: { id: 3 } }, + userProfile: { + draft: { + articles: [ + { + id: 1, + title: 'title 1', + imageUrl: 'http://img.com', + body: 'I am the body', + isDraft: true, + Category: { + name: 'Game', + }, + views: '34', + + }, + ], + }, + }, + }; + + let app; + let store; + + beforeEach(() => { + store = mockStore(); + app = shallow( + , + ); + }); + + it('renders successfully', () => { + expect(app).toBeDefined(); + }); + + it('gets all draft articles', () => { + const articleContainer = app.find('.article-title'); + expect(articleContainer).toHaveLength(1); + expect(articleContainer.props().children).toEqual(props.userProfile.draft.articles[0].title); + }); + + it('Click on pagination', () => { + app.instance().paginate(2); + const render = jest.spyOn(app.instance(), 'paginate'); + expect(render).toHaveBeenCalledTimes(0); + }); +}); diff --git a/_test_/PublishedArticles.spec.js b/_test_/PublishedArticles.spec.js new file mode 100644 index 0000000..ad8ef78 --- /dev/null +++ b/_test_/PublishedArticles.spec.js @@ -0,0 +1,60 @@ +import React from 'react'; +import expect from 'expect'; +import Enzyme, { shallow } from 'enzyme'; +import configureMockStore from 'redux-mock-store'; +import Adapter from 'enzyme-adapter-react-16'; +import { PublishedArticles } from '../src/components/Dashboard/PublishedArticles'; + +const mockStore = configureMockStore(); +Enzyme.configure({ adapter: new Adapter() }); + +describe('Published Articles', () => { + const props = { + getAuthUserPublishedArticles: jest.fn(), + auth: { user: { id: 3 } }, + userProfile: { + published: { + articles: [ + { + id: 1, + title: 'title 1', + imageUrl: 'http://img.com', + body: 'I am the body', + isDraft: false, + Category: { + name: 'Game', + }, + views: '34', + + }, + ], + }, + }, + }; + + let app; + let store; + + beforeEach(() => { + store = mockStore(); + app = shallow( + , + ); + }); + + it('renders successfully', () => { + expect(app).toBeDefined(); + }); + + it('gets all published articles', () => { + const articleContainer = app.find('.article-title'); + expect(articleContainer).toHaveLength(1); + expect(articleContainer.props().children).toEqual(props.userProfile.published.articles[0].title); + }); + + it('Click on pagination', () => { + app.instance().paginate(2); + const render = jest.spyOn(app.instance(), 'paginate'); + expect(render).toHaveBeenCalledTimes(0); + }); +}); diff --git a/_test_/authReducer.spec.js b/_test_/authReducer.spec.js index 6e8b960..f563879 100644 --- a/_test_/authReducer.spec.js +++ b/_test_/authReducer.spec.js @@ -25,6 +25,22 @@ describe('Login reducer', () => { loading: false, }); }); + it('should set loading to true', () => { + expect(reducer(initialState, { + type: actionTypes.AUTH_LOADING, + })).toEqual({ + ...initialState, + loading: true, + }); + }); + it('should remove current user', () => { + expect(reducer(initialState, { + type: actionTypes.REMOVE_CURRENT_USER, + })).toEqual({ + ...initialState, + loading: false, + }); + }); it('should successfully store the token upon login', () => { expect(reducer(initialState, { diff --git a/_test_/dashboardView.spec.js b/_test_/dashboardView.spec.js new file mode 100644 index 0000000..371dd11 --- /dev/null +++ b/_test_/dashboardView.spec.js @@ -0,0 +1,15 @@ +import React from 'react'; +import expect from 'expect'; +import { shallow } from 'enzyme'; + +import App from '../src/views/Dashboard'; + +describe('App', () => { + let app; + beforeEach(() => { + app = shallow(); + }); + it('renders successfully', () => { + expect(app).toBeDefined(); + }); +}); diff --git a/_test_/editProfileForm.spec.js b/_test_/editProfileForm.spec.js new file mode 100644 index 0000000..ddf997f --- /dev/null +++ b/_test_/editProfileForm.spec.js @@ -0,0 +1,50 @@ +import React from 'react'; +import expect from 'expect'; +import { shallow } from 'enzyme'; +import configureMockStore from 'redux-mock-store'; + +import { EditProfileForm } from '../src/components/Dashboard/EditProfileForm'; + +const mockStore = configureMockStore(); + +describe('App', () => { + const props = { + editAuthUserProfile: jest.fn(), + auth: { user: { id: '' } }, + userProfile: { isLoading: '' }, + }; + let app; + let store; + + beforeEach(() => { + const initialState = { + firstName: '', + lastName: '', + userName: '', + bio: '', + avatar: '', + }; + + store = mockStore(initialState); + app = shallow( + , + ); + }); + + it('renders successfully', () => { + expect(app).toBeDefined(); + }); + it('Simulates an onchange event', () => { + const event = { + target: { + id: 'firstName', + value: 'This is to test for email change', + name: 'firstName', + }, + }; + app.find('input').at(0).simulate('change', event); + }); + it('Simulates a form submit event', () => { + app.find('form').simulate('submit', { preventDefault: jest.fn() }); + }); +}); diff --git a/_test_/userAction.spec.js b/_test_/userAction.spec.js new file mode 100644 index 0000000..47b3f88 --- /dev/null +++ b/_test_/userAction.spec.js @@ -0,0 +1,320 @@ +import expect from 'expect'; +import configureMockStore from 'redux-mock-store'; +import thunk from 'redux-thunk'; +import moxios from 'moxios'; +import * as types from '../src/actions/types'; +import * as actions from '../src/actions/userActions'; +import axios from '../src/config/axiosInstance'; + +const middlewares = [thunk]; +const mockStore = configureMockStore(middlewares); + +const initialState = {}; +let store = mockStore(initialState); + +const userId = 2; +const userToken = 'nhfjdjskkkaa'; + +describe('User Action Tests', () => { + afterEach(() => { + moxios.install(axios); + store.clearActions(); + }); + afterEach(() => moxios.uninstall(axios)); + + it('should dispatch GET_AUTH_USER_PROFILE action', () => { + store.dispatch(actions.getAuthUserProfileStart()); + expect(store.getActions()).toContainEqual({ type: types.GET_AUTH_USER_PROFILE_START }); + }); + it('Returns success if user profile was successfully fetched', (done) => { + moxios.stubRequest(`https://ah-nyati-backend-staging.herokuapp.com/api/v1/user/profiles/${userId}`, { + status: 200, + data: [], + }); + const expectedActions = [ + { + type: types.GET_AUTH_USER_PROFILE_START, + }, + { + type: types.GET_AUTH_USER_PROFILE_SUCCESS, + }, + { + type: types.GET_AUTH_USER_PROFILE_FAILURE, + }, + ]; + store = mockStore({}); + store.dispatch(actions.authUserProfile(userToken)); + store.dispatch(actions.getAuthUserProfileSuccess()); + store.dispatch(actions.getAuthUserProfileFailure()); + expect(store.getActions()).toEqual(expectedActions); + done(); + }); + + it('Returns success if get user profile was successful', (done) => { + jest.spyOn(axios, 'get').mockResolvedValue({ data: { data: {} } }); + + const expectedActions = [ + { + type: types.GET_AUTH_USER_PROFILE_START, + }, + { + type: types.GET_AUTH_USER_PROFILE_SUCCESS, + }, + ]; + store.dispatch(actions.authUserProfile(userId)) + .then(() => { + expect(store.getActions()).toEqual(expectedActions); + done(); + }); + }); + + it('Returns success if get user followers was successful', (done) => { + jest.spyOn(axios, 'get').mockResolvedValue({ data: { data: {} } }); + + const expectedActions = [ + { + type: types.GET_AUTH_USER_FOLLOWERS_START, + }, + { + type: types.GET_AUTH_USER_FOLLOWERS_FAILURE, + }, + ]; + store.dispatch(actions.getAuthUserFollowers(userId)) + .then(() => { + expect(store.getActions()).toEqual(expectedActions); + done(); + }); + }); + + it('Return failure if get user profile was unsuccessful', (done) => { + jest.spyOn(axios, 'get').mockRejectedValue({ response: { data: { message: '' } } } ); + + const expectedActions = [ + { + type: types.GET_AUTH_USER_PROFILE_START, + }, + { + type: types.GET_AUTH_USER_PROFILE_FAILURE, + }, + ]; + store.dispatch(actions.authUserProfile(userId)) + .then(() => { + expect(store.getActions()).toEqual(expectedActions); + done(); + }); + }); + + it('Return failure if get user followers was unsuccessful', (done) => { + jest.spyOn(axios, 'get').mockRejectedValue({ response: { data: { message: '' } } } ); + + const expectedActions = [ + { + type: types.GET_AUTH_USER_FOLLOWERS_START, + }, + { + type: types.GET_AUTH_USER_FOLLOWERS_FAILURE, + }, + ]; + store.dispatch(actions.getAuthUserFollowers(userToken)) + .then(() => { + expect(store.getActions()).toEqual(expectedActions); + done(); + }); + }); + it('Returns success if get user followees was successful', (done) => { + jest.spyOn(axios, 'get').mockResolvedValue({ data: { data: [{followees: []}] } }); + + const expectedActions = [ + { + type: types.GET_AUTH_USER_FOLLOWEE_START, + }, + { + type: types.GET_AUTH_USER_FOLLOWEE_SUCCESS, + payload: [], + }, + ]; + store.dispatch(actions.getAuthUserFollowee(userId)) + .then(() => { + expect(store.getActions()).toEqual(expectedActions); + done(); + }); + }); + + it('Return failure if get user followees was unsuccessful', (done) => { + jest.spyOn(axios, 'get').mockRejectedValue({ response: { data: { message: '' } } } ); + + const expectedActions = [ + { + type: types.GET_AUTH_USER_FOLLOWEE_START, + }, + { + type: types.GET_AUTH_USER_FOLLOWEE_FAILURE, + }, + ]; + store.dispatch(actions.getAuthUserFollowee(userToken)) + .then(() => { + expect(store.getActions()).toEqual(expectedActions); + done(); + }); + }); + + + + it('Returns success if get user articles was successful', (done) => { + jest.spyOn(axios, 'get').mockResolvedValue({ data: { data: {} } }); + const userName = ''; + const expectedActions = [ + { + type: types.GET_AUTH_USER_ARTICLES_START, + }, + { + type: types.GET_AUTH_USER_ARTICLES_SUCCESS, + }, + ]; + store.dispatch(actions.getAuthUserArticles(userName)) + .then(() => { + expect(store.getActions()).toEqual(expectedActions); + done(); + }); + }); + + it('Return failure if get user articles was unsuccessful', (done) => { + jest.spyOn(axios, 'get').mockRejectedValue({ response: { data: { message: '' }, status: 404 } } ); + const userName = ''; + const expectedActions = [ + { + type: types.GET_AUTH_USER_ARTICLES_START, + }, + { + type: types.GET_AUTH_USER_ARTICLES_FAILURE, + }, + ]; + store.dispatch(actions.getAuthUserArticles(userName)) + .then(() => { + expect(store.getActions()).toEqual(expectedActions); + done(); + }); + }); + it('Returns success if get user published articles was successful', (done) => { + jest.spyOn(axios, 'get').mockResolvedValue({ data: { data: {} } }); + const userid = 1; + const offset = 2; + const expectedActions = [ + { + type: types.GET_AUTH_USER_PUBLISHED_ARTICLES_START, + }, + { + type: types.GET_AUTH_USER_PUBLISHED_ARTICLES_SUCCESS, + }, + ]; + store.dispatch(actions.getAuthUserPublishedArticles(userid, offset)) + .then(() => { + expect(store.getActions()).toEqual(expectedActions); + done(); + }); + }); + + it('Return failure if get user published articles was unsuccessful', (done) => { + jest.spyOn(axios, 'get').mockRejectedValue({ response: { data: { message: '' }, status: 404 } } ); + const userid = 1; + const offset = 2; + const expectedActions = [ + { + type: types.GET_AUTH_USER_PUBLISHED_ARTICLES_START, + }, + { + type: types.GET_AUTH_USER_PUBLISHED_ARTICLES_FAILURE, + }, + ]; + store.dispatch(actions.getAuthUserPublishedArticles(userid, offset)) + .then(() => { + expect(store.getActions()).toEqual(expectedActions); + done(); + }); + }); + it('Returns success if get user draft articles was successful', (done) => { + jest.spyOn(axios, 'get').mockResolvedValue({ data: { data: {} } }); + const userid = 1; + const offset = 2; + const expectedActions = [ + { + type: types.GET_AUTH_USER_DRAFT_ARTICLES_START, + }, + { + type: types.GET_AUTH_USER_DRAFT_ARTICLES_SUCCESS, + }, + ]; + store.dispatch(actions.getAuthUserDraftArticles(userid, offset)) + .then(() => { + expect(store.getActions()).toEqual(expectedActions); + done(); + }); + }); + + it('Return failure if get user draft articles was unsuccessful', (done) => { + jest.spyOn(axios, 'get').mockRejectedValue({ response: { data: { message: '' }, status: 404 } } ); + const userid = 1; + const offset = 2; + const expectedActions = [ + { + type: types.GET_AUTH_USER_DRAFT_ARTICLES_START, + }, + { + type: types.GET_AUTH_USER_DRAFT_ARTICLES_FAILURE, + }, + ]; + store.dispatch(actions.getAuthUserDraftArticles(userid, offset)) + .then(() => { + expect(store.getActions()).toEqual(expectedActions); + done(); + }); + }); + + it('Returns success if edit user profile was successful', (done) => { + jest.spyOn(axios, 'put').mockResolvedValue({ data: { data: [[]], message: '' } }); + const newProfileDetails = { + firstName: '', + lastName: '', + userName: '', + }; + const userid = 1; + const expectedActions = [ + { + type: types.EDIT_AUTH_USER_PROFILE_START, + }, + { + type: types.EDIT_AUTH_USER_PROFILE_SUCCESS, + payload: [], + }, + ]; + store.dispatch(actions.editAuthUserProfile(newProfileDetails, userid)) + .then(() => { + expect(store.getActions()).toEqual(expectedActions); + done(); + }); + }); + + it('Returns failure if edit user profile was unsuccessful', (done) => { + jest.spyOn(axios, 'put').mockRejectedValue({ response: { data: { message: '' }} }); + const newProfileDetails = { + firstName: '', + lastName: '', + userName: '', + }; + const userid = 1; + const expectedActions = [ + { + type: types.EDIT_AUTH_USER_PROFILE_START, + }, + { + type: types.EDIT_AUTH_USER_PROFILE_FAILURE, + + }, + ]; + store.dispatch(actions.editAuthUserProfile(newProfileDetails, userid)) + .then(() => { + expect(store.getActions()).toEqual(expectedActions); + done(); + }); + }); +}); diff --git a/_test_/userArticles.spec.js b/_test_/userArticles.spec.js new file mode 100644 index 0000000..d9a5050 --- /dev/null +++ b/_test_/userArticles.spec.js @@ -0,0 +1,75 @@ +import React from 'react'; +import expect from 'expect'; +import Enzyme, { shallow, mount } from 'enzyme'; +import configureMockStore from 'redux-mock-store'; +import Adapter from 'enzyme-adapter-react-16'; +import { UserArticles } from '../src/components/Dashboard/UserArticles'; + +const mockStore = configureMockStore(); +Enzyme.configure({ adapter: new Adapter() }); + +describe('Dashboard', () => { + const props = { + paginate: jest.fn(), + getAuthUserArticles: jest.fn(), + userProfile: { + articles: { + articles: [ + { + id: 1, + title: 'title 1', + imageUrl: 'http://img.com', + body: 'I am the body', + isDraft: false, + Category: { + name: 'Game', + }, + views: '34', + }, + { + id: 2, + title: 'title 2', + body: 'I am the body 2', + isDraft: true, + Category: { + name: 'Game', + }, + views: '34', + }, + ], + }, + }, + auth: { user: { id: 3 } }, + }; + let app; + let store; + + + beforeEach(() => { + store = mockStore(); + app = shallow( + , + ); + }); + + it('renders successfully', () => { + expect(app).toBeDefined(); + }); + + it('gets all active articles when article button is clicked', () => { + const publishedButton = app.find('#published'); + expect(publishedButton).toHaveLength(1); + publishedButton.simulate('click'); + + expect(app).toMatchSnapshot(); + }); + + it('gets all draft articles when draft button is clicked', () => { + const draftButton = app.find('#draft'); + expect(draftButton).toHaveLength(1); + draftButton.simulate('click'); + + expect(app).toMatchSnapshot(); + + }); +}); diff --git a/_test_/userProfile.spec.js b/_test_/userProfile.spec.js new file mode 100644 index 0000000..9322233 --- /dev/null +++ b/_test_/userProfile.spec.js @@ -0,0 +1,38 @@ +import React from 'react'; +import expect from 'expect'; +import { shallow } from 'enzyme'; +import configureMockStore from 'redux-mock-store'; + +import { UserProfile } from '../src/components/Dashboard/UserProfile'; + +const mockStore = configureMockStore(); + +describe('Dashboard', () => { + const props = { + userProfile: { + profile: { + firstName: '', + lastName: '', + userName: '', + imageUrl: '', + bio: '', + }, + followers: '', + followees: '', + articles: [], + }, + }; + let app; + let store; + + beforeEach(() => { + store = mockStore(); + app = shallow( + , + ); + }); + + it('renders successfully', () => { + expect(app).toBeDefined(); + }); +}); diff --git a/_test_/userReduce.spec.js b/_test_/userReduce.spec.js new file mode 100644 index 0000000..500a00f --- /dev/null +++ b/_test_/userReduce.spec.js @@ -0,0 +1,214 @@ +import reducer from '../src/reducers/userReducer'; +import * as actionTypes from '../src/actions/types'; + +describe('User password', () => { + let initialState; + + beforeEach(() => { + initialState = { + isLoading: false, + profile: {}, + followers: [], + followees: [], + articles: [], + published: [], + draft: [], + }; + }); + + it('should return the initial state', () => { + expect(reducer(undefined, {})).toEqual(initialState); + }); + it('should set isLoading to true when the page load', () => { + expect(reducer(initialState, { + type: actionTypes.GET_AUTH_USER_PROFILE_START, + })).toEqual({ + ...initialState, + isLoading: true, + }); + }); + + it('should set profile with the users profile', () => { + expect(reducer(initialState, { + type: actionTypes.GET_AUTH_USER_PROFILE_SUCCESS, payload: {}, + })).toEqual({ + ...initialState, + isLoading: false, + profile: {}, + }); + }); + + it('should return the initial state', () => { + expect(reducer(initialState, { + type: actionTypes.GET_AUTH_USER_PROFILE_FAILURE, + })).toEqual({ + ...initialState, + isLoading: false, + }); + }); + + it('should set loading to true when get follower api is fetching', () => { + expect(reducer(initialState, { + type: actionTypes.GET_AUTH_USER_FOLLOWERS_START, + })).toEqual({ + ...initialState, + isLoading: true, + }); + }); + + it('should return followers if successful', () => { + expect(reducer(initialState, { + type: actionTypes.GET_AUTH_USER_FOLLOWERS_SUCCESS, payload: [], + })).toEqual({ + ...initialState, + isLoading: false, + followers: [], + }); + }); + + it('should set loading to false when get followers api fails', () => { + expect(reducer(initialState, { + type: actionTypes.GET_AUTH_USER_FOLLOWERS_FAILURE, + })).toEqual({ + ...initialState, + isLoading: false, + }); + }); + + it('should set loading to true when get followees api start', () => { + expect(reducer(initialState, { + type: actionTypes.GET_AUTH_USER_FOLLOWEE_START, + })).toEqual({ + ...initialState, + isLoading: true, + }); + }); + + it('should return followees when get followees api succeeds', () => { + expect(reducer(initialState, { + type: actionTypes.GET_AUTH_USER_FOLLOWEE_SUCCESS, payload: [], + })).toEqual({ + ...initialState, + isLoading: false, + followees: [], + }); + }); + + it('should set loading to false when get followees api fails', () => { + expect(reducer(initialState, { + type: actionTypes.GET_AUTH_USER_FOLLOWEE_FAILURE, + })).toEqual({ + ...initialState, + isLoading: false, + }); + }); + + it('should set loading to true when get articles api starts', () => { + expect(reducer(initialState, { + type: actionTypes.GET_AUTH_USER_ARTICLES_START, + })).toEqual({ + ...initialState, + isLoading: true, + }); + }); + + it('should return articles when get articles api succeeds', () => { + expect(reducer(initialState, { + type: actionTypes.GET_AUTH_USER_ARTICLES_SUCCESS, payload: [], + })).toEqual({ + ...initialState, + isLoading: false, + articles: [], + }); + }); + + it('should set loading to false when get articles api fails', () => { + expect(reducer(initialState, { + type: actionTypes.GET_AUTH_USER_ARTICLES_FAILURE, + })).toEqual({ + ...initialState, + isLoading: false, + }); + }); + it('should set loading to true when get published articles api starts', () => { + expect(reducer(initialState, { + type: actionTypes.GET_AUTH_USER_PUBLISHED_ARTICLES_START, + })).toEqual({ + ...initialState, + isLoading: true, + }); + }); + + it('should return articles when get published articles api succeeds', () => { + expect(reducer(initialState, { + type: actionTypes.GET_AUTH_USER_PUBLISHED_ARTICLES_SUCCESS, payload: [], + })).toEqual({ + ...initialState, + isLoading: false, + published: [], + }); + }); + + it('should set loading to false when get published articles api fails', () => { + expect(reducer(initialState, { + type: actionTypes.GET_AUTH_USER_PUBLISHED_ARTICLES_FAILURE, + })).toEqual({ + ...initialState, + isLoading: false, + }); + }); + + it('should set loading to true when get draft articles api starts', () => { + expect(reducer(initialState, { + type: actionTypes.GET_AUTH_USER_DRAFT_ARTICLES_START, + })).toEqual({ + ...initialState, + isLoading: true, + }); + }); + + it('should return articles when get draft articles api succeeds', () => { + expect(reducer(initialState, { + type: actionTypes.GET_AUTH_USER_DRAFT_ARTICLES_SUCCESS, payload: [], + })).toEqual({ + ...initialState, + isLoading: false, + draft: [], + }); + }); + + it('should set loading to false when get draft articles api fails', () => { + expect(reducer(initialState, { + type: actionTypes.GET_AUTH_USER_DRAFT_ARTICLES_FAILURE, + })).toEqual({ + ...initialState, + isLoading: false, + }); + }); + + it('should set loading to true when edit profile api starts', () => { + expect(reducer(initialState, { + type: actionTypes.EDIT_AUTH_USER_PROFILE_START, + })).toEqual({ + ...initialState, + isLoading: true, + }); + }); + it('should return articles when edit profile api succeeds', () => { + expect(reducer(initialState, { + type: actionTypes.EDIT_AUTH_USER_PROFILE_SUCCESS, payload: {}, + })).toEqual({ + ...initialState, + isLoading: false, + profile: {}, + }); + }); + it('should set loading to false when edit profile api fails', () => { + expect(reducer(initialState, { + type: actionTypes.EDIT_AUTH_USER_PROFILE_FAILURE, + })).toEqual({ + ...initialState, + isLoading: false, + }); + }); +}); diff --git a/package-lock.json b/package-lock.json index 12b8caf..e16ebce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1005,6 +1005,12 @@ "minimist": "^1.2.0" } }, + "@date-io/moment": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@date-io/moment/-/moment-1.3.5.tgz", + "integrity": "sha512-b0JQb10Lie07iW2/9uKCQSrXif262d6zfYBstCLLJUk0JVA+7o/yLDg5p2+GkjgJbmodjHozIXs4Bi34RRhL8Q==", + "dev": true + }, "@emotion/cache": { "version": "10.0.15", "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-10.0.15.tgz", @@ -1086,6 +1092,12 @@ "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.3.tgz", "integrity": "sha512-zVgvPwGK7c1aVdUVc9Qv7SqepOGRDrqCw7KZPSZziWGxSlbII3gmvGLPzLX4d0n0BMbamBacUrN22zOMyFFEkQ==" }, + "@fortawesome/fontawesome-free": { + "version": "5.10.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-5.10.2.tgz", + "integrity": "sha512-9pw+Nsnunl9unstGEHQ+u41wBEQue6XPBsILXtJF/4fNN1L3avJcMF/gGF86rIjeTAgfLjTY9ndm68/X4f4idQ==", + "dev": true + }, "@jest/console": { "version": "24.9.0", "resolved": "https://registry.npmjs.org/@jest/console/-/console-24.9.0.tgz", @@ -1289,6 +1301,78 @@ "@types/yargs": "^13.0.0" } }, + "@material-ui/core": { + "version": "3.9.3", + "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-3.9.3.tgz", + "integrity": "sha512-REIj62+zEvTgI/C//YL4fZxrCVIySygmpZglsu/Nl5jPqy3CDjZv1F9ubBYorHqmRgeVPh64EghMMWqk4egmfg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.2.0", + "@material-ui/system": "^3.0.0-alpha.0", + "@material-ui/utils": "^3.0.0-alpha.2", + "@types/jss": "^9.5.6", + "@types/react-transition-group": "^2.0.8", + "brcast": "^3.0.1", + "classnames": "^2.2.5", + "csstype": "^2.5.2", + "debounce": "^1.1.0", + "deepmerge": "^3.0.0", + "dom-helpers": "^3.2.1", + "hoist-non-react-statics": "^3.2.1", + "is-plain-object": "^2.0.4", + "jss": "^9.8.7", + "jss-camel-case": "^6.0.0", + "jss-default-unit": "^8.0.2", + "jss-global": "^3.0.0", + "jss-nested": "^6.0.1", + "jss-props-sort": "^6.0.0", + "jss-vendor-prefixer": "^7.0.0", + "normalize-scroll-left": "^0.1.2", + "popper.js": "^1.14.1", + "prop-types": "^15.6.0", + "react-event-listener": "^0.6.2", + "react-transition-group": "^2.2.1", + "recompose": "0.28.0 - 0.30.0", + "warning": "^4.0.1" + }, + "dependencies": { + "react-transition-group": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz", + "integrity": "sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==", + "dev": true, + "requires": { + "dom-helpers": "^3.4.0", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2", + "react-lifecycles-compat": "^3.0.4" + } + } + } + }, + "@material-ui/system": { + "version": "3.0.0-alpha.2", + "resolved": "https://registry.npmjs.org/@material-ui/system/-/system-3.0.0-alpha.2.tgz", + "integrity": "sha512-odmxQ0peKpP7RQBQ8koly06YhsPzcoVib1vByVPBH4QhwqBXuYoqlCjt02846fYspAqkrWzjxnWUD311EBbxOA==", + "dev": true, + "requires": { + "@babel/runtime": "^7.2.0", + "deepmerge": "^3.0.0", + "prop-types": "^15.6.0", + "warning": "^4.0.1" + } + }, + "@material-ui/utils": { + "version": "3.0.0-alpha.3", + "resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-3.0.0-alpha.3.tgz", + "integrity": "sha512-rwMdMZptX0DivkqBuC+Jdq7BYTXwqKai5G5ejPpuEDKpWzi1Oxp+LygGw329FrKpuKeiqpcymlqJTjmy+quWng==", + "dev": true, + "requires": { + "@babel/runtime": "^7.2.0", + "prop-types": "^15.6.0", + "react-is": "^16.6.3" + } + }, "@tinymce/tinymce-react": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/@tinymce/tinymce-react/-/tinymce-react-3.3.1.tgz", @@ -1383,6 +1467,16 @@ "integrity": "sha512-Il2DtDVRGDcqjDtE+rF8iqg1CArehSK84HZJCT7AMITlyXRBpuPhqGLDQMowraqqu1coEaimg4ZOqggt6L6L+A==", "dev": true }, + "@types/jss": { + "version": "9.5.8", + "resolved": "https://registry.npmjs.org/@types/jss/-/jss-9.5.8.tgz", + "integrity": "sha512-bBbHvjhm42UKki+wZpR89j73ykSXg99/bhuKuYYePtpma3ZAnmeGnl0WxXiZhPGsIfzKwCUkpPC0jlrVMBfRxA==", + "dev": true, + "requires": { + "csstype": "^2.0.0", + "indefinite-observable": "^1.0.1" + } + }, "@types/minimatch": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", @@ -1394,6 +1488,40 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.2.tgz", "integrity": "sha512-dyYO+f6ihZEtNPDcWNR1fkoTDf3zAK3lAABDze3mz6POyIercH0lEUawUFXlG8xaQZmm1yEBON/4TsYv/laDYg==" }, + "@types/prop-types": { + "version": "15.7.1", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.1.tgz", + "integrity": "sha512-CFzn9idOEpHrgdw8JsoTkaDDyRWk1jrzIV8djzcgpq0y9tG4B4lFT+Nxh52DVpDXV+n4+NPNv7M1Dj5uMp6XFg==", + "dev": true + }, + "@types/react": { + "version": "16.9.2", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.2.tgz", + "integrity": "sha512-jYP2LWwlh+FTqGd9v7ynUKZzjj98T8x7Yclz479QdRhHfuW9yQ+0jjnD31eXSXutmBpppj5PYNLYLRfnZJvcfg==", + "dev": true, + "requires": { + "@types/prop-types": "*", + "csstype": "^2.2.0" + } + }, + "@types/react-text-mask": { + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/@types/react-text-mask/-/react-text-mask-5.4.6.tgz", + "integrity": "sha512-0KkER9oXZY/v1x8aoMTHwANlWnKT5tnmV7Zz+g81gBvcHRtcIHotcpY4KgWRwx0T5JMcsYmEh7wGOz0lwdONew==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, + "@types/react-transition-group": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-2.9.2.tgz", + "integrity": "sha512-5Fv2DQNO+GpdPZcxp2x/OQG/H19A01WlmpjVD9cKvVFmoVLOZ9LvBgSWG6pSXIU4og5fgbvGPaCV5+VGkWAEHA==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, "@types/stack-utils": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz", @@ -2809,6 +2937,28 @@ "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" }, + "bootstrap-4-react": { + "version": "0.0.59", + "resolved": "https://registry.npmjs.org/bootstrap-4-react/-/bootstrap-4-react-0.0.59.tgz", + "integrity": "sha512-j3a618tWHl/ajYQi6zn0LdVfksRH+gyQ4RwIanTPin6xkHkgYfk+47ZlHFSwyiO9zVFn4xznoGjTwWA0AIlJkA==", + "dev": true, + "requires": { + "bootstrap-4-required": "0.0.1", + "fsts": "0.0.44" + } + }, + "bootstrap-4-required": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/bootstrap-4-required/-/bootstrap-4-required-0.0.1.tgz", + "integrity": "sha512-4S6Trn9pRVSR756GRYr3hy2cZL3Vc0tw0/H9E+mbNeOR+4tn6CeRgcLx0YqZmL2XlabtEV73+XAesmwkgTDKvQ==", + "dev": true + }, + "bootstrap-css-only": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/bootstrap-css-only/-/bootstrap-css-only-4.3.1.tgz", + "integrity": "sha512-xPQNmTR6skX7boM3Q/K2vWDL8RFhfHm5PbTcn/vd7nZtkzg9tc6ScNreIIsMaP9QLUxeqvUx+OGnDaiK4KBRiQ==", + "dev": true + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -2845,6 +2995,12 @@ } } }, + "brcast": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/brcast/-/brcast-3.0.1.tgz", + "integrity": "sha512-eI3yqf9YEqyGl9PCNTR46MGvDylGtaHjalcz6Q3fAPnP/PhpKkkve52vFdfGpwp4VUvK6LUr4TQN+2stCrEwTg==", + "dev": true + }, "brorand": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", @@ -3174,12 +3330,64 @@ "resolved": "https://registry.npmjs.org/change-emitter/-/change-emitter-0.1.6.tgz", "integrity": "sha1-6LL+PX8at9aaMhma/5HqaTFAlRU=" }, + "character-entities": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.3.tgz", + "integrity": "sha512-yB4oYSAa9yLcGyTbB4ItFwHw43QHdH129IJ5R+WvxOkWlyFnR5FAaBNnUq4mcxsTVZGh28bHoeTHMKXH1wZf3w==" + }, + "character-entities-legacy": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.3.tgz", + "integrity": "sha512-YAxUpPoPwxYFsslbdKkhrGnXAtXoHNgYjlBM3WMXkWGTl5RsY3QmOyhwAgL8Nxm9l5LBThXGawxKPn68y6/fww==" + }, + "character-reference-invalid": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.3.tgz", + "integrity": "sha512-VOq6PRzQBam/8Jm6XBGk2fNEnHXAdGd6go0rtd4weAGECBamHDwwCQSOT12TACIYUZegUXnV6xBXqUssijtxIg==" + }, "chardet": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "dev": true }, + "chart.js": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.8.0.tgz", + "integrity": "sha512-Di3wUL4BFvqI5FB5K26aQ+hvWh8wnP9A3DWGvXHVkO13D3DSnaSsdZx29cXlEsYKVkn1E2az+ZYFS4t0zi8x0w==", + "dev": true, + "requires": { + "chartjs-color": "^2.1.0", + "moment": "^2.10.2" + } + }, + "chartjs-color": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/chartjs-color/-/chartjs-color-2.3.0.tgz", + "integrity": "sha512-hEvVheqczsoHD+fZ+tfPUE+1+RbV6b+eksp2LwAhwRTVXEjCSEavvk+Hg3H6SZfGlPh/UfmWKGIvZbtobOEm3g==", + "dev": true, + "requires": { + "chartjs-color-string": "^0.6.0", + "color-convert": "^0.5.3" + }, + "dependencies": { + "color-convert": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-0.5.3.tgz", + "integrity": "sha1-vbbGnOZg+t/+CwAHzER+G59ygr0=", + "dev": true + } + } + }, + "chartjs-color-string": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/chartjs-color-string/-/chartjs-color-string-0.6.0.tgz", + "integrity": "sha512-TIB5OKn1hPJvO7JcteW4WY/63v6KwEdt6udfnDE9iCAZgy+V4SrbSxoIbTw/xkUIapjEI4ExGtD0+6D3KyFd7A==", + "dev": true, + "requires": { + "color-name": "^1.0.0" + } + }, "check-prop-types": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/check-prop-types/-/check-prop-types-1.1.2.tgz", @@ -3305,6 +3513,17 @@ "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", "dev": true }, + "clipboard": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.4.tgz", + "integrity": "sha512-Vw26VSLRpJfBofiVaFb/I8PVfdI1OxKcYShe6fm0sP/DtmiWQNCjhM/okTvdCo0G+lMMm1rMYbk4IK4x1X+kgQ==", + "optional": true, + "requires": { + "good-listener": "^1.2.2", + "select": "^1.1.2", + "tiny-emitter": "^2.0.0" + } + }, "cliui": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", @@ -3326,6 +3545,12 @@ "shallow-clone": "^3.0.0" } }, + "clsx": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.0.4.tgz", + "integrity": "sha512-1mQ557MIZTrL/140j+JVdRM6e31/OA4vTYxXgqIIZlndyfjHpyawKZia1Im05Vp9BWmImkcNrNtFYQMyFcgJDg==", + "dev": true + }, "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -3367,6 +3592,11 @@ "delayed-stream": "~1.0.0" } }, + "comma-separated-tokens": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.7.tgz", + "integrity": "sha512-Jrx3xsP4pPv4AwJUDWY9wOXGtwPXARej6Xd99h4TUGotmf8APuquKMpK+dnD3UgyxK7OEWaisjZz+3b5jtL6xQ==" + }, "commander": { "version": "2.20.0", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", @@ -3854,6 +4084,15 @@ } } }, + "css-vendor": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-0.3.8.tgz", + "integrity": "sha1-ZCHP0wNM5mT+dnOXL9ARn8KJQfo=", + "dev": true, + "requires": { + "is-in-browser": "^1.0.2" + } + }, "css-what": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", @@ -3942,6 +4181,12 @@ "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=", "dev": true }, + "debounce": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.0.tgz", + "integrity": "sha512-mYtLl1xfZLi1m4RtQYlZgJUNQjl4ZxVnHzIR8nLLgi4q1YT8o/WM+MK/f8yfcc9s5Ir5zRaPZyZU6xs1Syoocg==", + "dev": true + }, "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", @@ -3979,6 +4224,12 @@ "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", "dev": true }, + "deepmerge": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-3.3.0.tgz", + "integrity": "sha512-GRQOafGHwMHpjPx9iCvTgpu9NojZ49q794EEL94JVEw6VaeA8XTUyBKvAkOOjBX9oJNiV6G3P+T+tihFjo2TqA==", + "dev": true + }, "default-gateway": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-4.2.0.tgz", @@ -4062,6 +4313,12 @@ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, + "delegate": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz", + "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==", + "optional": true + }, "delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", @@ -5414,6 +5671,14 @@ "integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==", "dev": true }, + "fault": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fault/-/fault-1.0.3.tgz", + "integrity": "sha512-sfFuP4X0hzrbGKjAUNXYvNqsZ5F6ohx/dZ9I0KQud/aiZNwg263r5L9yGB0clvXHCkzXh5W3t7RSHchggYIFmA==", + "requires": { + "format": "^0.2.2" + } + }, "faye-websocket": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", @@ -5694,6 +5959,11 @@ "mime-types": "^2.1.12" } }, + "format": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", + "integrity": "sha1-1hcBB+nv3E7TDJ3DkBbflCtctYs=" + }, "forwarded": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", @@ -6422,6 +6692,12 @@ "rimraf": "2" } }, + "fsts": { + "version": "0.0.44", + "resolved": "https://registry.npmjs.org/fsts/-/fsts-0.0.44.tgz", + "integrity": "sha512-0U4qvbzOE+3s2711DdszIyaAnZ3M0dbFAhnkez/ITy31MwzDI2lepGSkVeFOyx6jqWvwaSZr01RP4hdM4I8wxQ==", + "dev": true + }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -6637,6 +6913,15 @@ "minimatch": "~3.0.2" } }, + "good-listener": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz", + "integrity": "sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=", + "optional": true, + "requires": { + "delegate": "^3.1.2" + } + }, "graceful-fs": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.2.tgz", @@ -6790,12 +7075,33 @@ "minimalistic-assert": "^1.0.1" } }, + "hast-util-parse-selector": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.2.tgz", + "integrity": "sha512-jIMtnzrLTjzqgVEQqPEmwEZV+ea4zHRFTP8Z2Utw0I5HuBOXHzUPPQWr6ouJdJqDKLbFU/OEiYwZ79LalZkmmw==" + }, + "hastscript": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-5.1.0.tgz", + "integrity": "sha512-7mOQX5VfVs/gmrOGlN8/EDfp1GqV6P3gTNVt+KnX4gbYhpASTM8bklFdFQCbFRAadURXAmw0R1QQdBdqp7jswQ==", + "requires": { + "comma-separated-tokens": "^1.0.0", + "hast-util-parse-selector": "^2.2.0", + "property-information": "^5.0.1", + "space-separated-tokens": "^1.0.0" + } + }, "he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, + "highlight.js": { + "version": "9.13.1", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.13.1.tgz", + "integrity": "sha512-Sc28JNQNDzaH6PORtRLMvif9RSn1mYuOoX3omVjnb0+HbpPygU2ALBI0R/wsiqCb4/fcp07Gdo8g+fhtFrQl6A==" + }, "history": { "version": "4.9.0", "resolved": "https://registry.npmjs.org/history/-/history-4.9.0.tgz", @@ -7090,6 +7396,12 @@ "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", "dev": true }, + "hyphenate-style-name": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.3.tgz", + "integrity": "sha512-EcuixamT82oplpoJ2XU4pDtKGWQ7b00CD9f1ug9IaQ3p1bkHMiKCZ9ut9QDI6qsa6cpUuB+A/I+zLtdNK4n2DQ==", + "dev": true + }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -7245,6 +7557,15 @@ "resolved": "https://registry.npmjs.org/in-publish/-/in-publish-2.0.0.tgz", "integrity": "sha1-4g/146KvwmkDILbcVSaCqcf631E=" }, + "indefinite-observable": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/indefinite-observable/-/indefinite-observable-1.0.2.tgz", + "integrity": "sha512-Mps0898zEduHyPhb7UCgNmfzlqNZknVmaFz5qzr0mm04YQ5FGLhAyK/dJ+NaRxGyR6juQXIxh5Ev0xx+qq0nYA==", + "dev": true, + "requires": { + "symbol-observable": "1.2.0" + } + }, "indent-string": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", @@ -7432,6 +7753,20 @@ } } }, + "is-alphabetical": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.3.tgz", + "integrity": "sha512-eEMa6MKpHFzw38eKm56iNNi6GJ7lf6aLLio7Kr23sJPAECscgRtZvOBYybejWDQ2bM949Y++61PY+udzj5QMLA==" + }, + "is-alphanumerical": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.3.tgz", + "integrity": "sha512-A1IGAPO5AW9vSh7omxIlOGwIqEvpW/TA+DksVOPM5ODuxKlZS09+TEM1E3275lJqO2oJ38vDpeAL3DCIiHE6eA==", + "requires": { + "is-alphabetical": "^1.0.0", + "is-decimal": "^1.0.0" + } + }, "is-arguments": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz", @@ -7503,6 +7838,11 @@ "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=" }, + "is-decimal": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.3.tgz", + "integrity": "sha512-bvLSwoDg2q6Gf+E2LEPiklHZxxiSi3XAh4Mav65mKqTfCO1HM3uBs24TjEH8iJX3bbDdLXKJXBTmGzuTUuAEjQ==" + }, "is-descriptor": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", @@ -7586,6 +7926,17 @@ "is-extglob": "^1.0.0" } }, + "is-hexadecimal": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.3.tgz", + "integrity": "sha512-zxQ9//Q3D/34poZf8fiy3m3XVpbQc7ren15iKqrTtLPwkPD/t3Scy9Imp63FujULGxuK0ZlCwoo5xNpktFgbOA==" + }, + "is-in-browser": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz", + "integrity": "sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU=", + "dev": true + }, "is-number": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", @@ -7740,6 +8091,17 @@ "requires": { "node-fetch": "^1.0.1", "whatwg-fetch": ">=0.10.0" + }, + "dependencies": { + "node-fetch": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", + "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", + "requires": { + "encoding": "^0.1.11", + "is-stream": "^1.0.1" + } + } } }, "isstream": { @@ -8731,6 +9093,84 @@ "verror": "1.10.0" } }, + "jss": { + "version": "9.8.7", + "resolved": "https://registry.npmjs.org/jss/-/jss-9.8.7.tgz", + "integrity": "sha512-awj3XRZYxbrmmrx9LUSj5pXSUfm12m8xzi/VKeqI1ZwWBtQ0kVPTs3vYs32t4rFw83CgFDukA8wKzOE9sMQnoQ==", + "dev": true, + "requires": { + "is-in-browser": "^1.1.3", + "symbol-observable": "^1.1.0", + "warning": "^3.0.0" + }, + "dependencies": { + "warning": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz", + "integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=", + "dev": true, + "requires": { + "loose-envify": "^1.0.0" + } + } + } + }, + "jss-camel-case": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jss-camel-case/-/jss-camel-case-6.1.0.tgz", + "integrity": "sha512-HPF2Q7wmNW1t79mCqSeU2vdd/vFFGpkazwvfHMOhPlMgXrJDzdj9viA2SaHk9ZbD5pfL63a8ylp4++irYbbzMQ==", + "dev": true, + "requires": { + "hyphenate-style-name": "^1.0.2" + } + }, + "jss-default-unit": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/jss-default-unit/-/jss-default-unit-8.0.2.tgz", + "integrity": "sha512-WxNHrF/18CdoAGw2H0FqOEvJdREXVXLazn7PQYU7V6/BWkCV0GkmWsppNiExdw8dP4TU1ma1dT9zBNJ95feLmg==", + "dev": true + }, + "jss-global": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/jss-global/-/jss-global-3.0.0.tgz", + "integrity": "sha512-wxYn7vL+TImyQYGAfdplg7yaxnPQ9RaXY/cIA8hawaVnmmWxDHzBK32u1y+RAvWboa3lW83ya3nVZ/C+jyjZ5Q==", + "dev": true + }, + "jss-nested": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/jss-nested/-/jss-nested-6.0.1.tgz", + "integrity": "sha512-rn964TralHOZxoyEgeq3hXY8hyuCElnvQoVrQwKHVmu55VRDd6IqExAx9be5HgK0yN/+hQdgAXQl/GUrBbbSTA==", + "dev": true, + "requires": { + "warning": "^3.0.0" + }, + "dependencies": { + "warning": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz", + "integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=", + "dev": true, + "requires": { + "loose-envify": "^1.0.0" + } + } + } + }, + "jss-props-sort": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/jss-props-sort/-/jss-props-sort-6.0.0.tgz", + "integrity": "sha512-E89UDcrphmI0LzmvYk25Hp4aE5ZBsXqMWlkFXS0EtPkunJkRr+WXdCNYbXbksIPnKlBenGB9OxzQY+mVc70S+g==", + "dev": true + }, + "jss-vendor-prefixer": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/jss-vendor-prefixer/-/jss-vendor-prefixer-7.0.0.tgz", + "integrity": "sha512-Agd+FKmvsI0HLcYXkvy8GYOw3AAASBUpsmIRvVQheps+JWaN892uFOInTr0DRydwaD91vSSUCU4NssschvF7MA==", + "dev": true, + "requires": { + "css-vendor": "^0.3.8" + } + }, "jsx-ast-utils": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.2.1.tgz", @@ -8986,6 +9426,12 @@ "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", "dev": true }, + "lodash.throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ=", + "dev": true + }, "lodash.unescape": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/lodash.unescape/-/lodash.unescape-4.0.1.tgz", @@ -9021,6 +9467,15 @@ "integrity": "sha1-miyr0bno4K6ZOkv31YdcOcQujqw=", "dev": true }, + "lowlight": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-1.11.0.tgz", + "integrity": "sha512-xrGGN6XLL7MbTMdPD6NfWPwY43SNkjf/d0mecSx/CW36fUZTjRHEq0/Cdug3TWKtRXLWi7iMl1eP0olYxj/a4A==", + "requires": { + "fault": "^1.0.2", + "highlight.js": "~9.13.0" + } + }, "lru-cache": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", @@ -9090,6 +9545,34 @@ "object-visit": "^1.0.0" } }, + "material-ui-pickers": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/material-ui-pickers/-/material-ui-pickers-2.2.4.tgz", + "integrity": "sha512-QCQh08Ylmnt+o4laW+rPs92QRAcESv3sPXl50YadLm++rAZAXAOh3K8lreGdynCMYFgZfdyu81Oz9xzTlAZNfw==", + "dev": true, + "requires": { + "@types/react-text-mask": "^5.4.3", + "clsx": "^1.0.2", + "react-event-listener": "^0.6.6", + "react-text-mask": "^5.4.3", + "react-transition-group": "^2.5.3", + "tslib": "^1.9.3" + }, + "dependencies": { + "react-transition-group": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz", + "integrity": "sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==", + "dev": true, + "requires": { + "dom-helpers": "^3.4.0", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2", + "react-lifecycles-compat": "^3.0.4" + } + } + } + }, "math-random": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.4.tgz", @@ -9108,6 +9591,95 @@ "safe-buffer": "^5.1.2" } }, + "mdbreact": { + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/mdbreact/-/mdbreact-4.19.2.tgz", + "integrity": "sha512-uA8WgbUj9JI13U2M2stPnJFi40p5hyxLrWweeXkNi+4NM22hNNVKmQSjD5je3PGSiTdTwjPXe47He/87zFuqgQ==", + "dev": true, + "requires": { + "@date-io/moment": "1.3.5", + "@fortawesome/fontawesome-free": "^5.10.2", + "@material-ui/core": "3.9.3", + "bootstrap-css-only": "4.3.1", + "chart.js": "2.8.0", + "classnames": "2.2.6", + "material-ui-pickers": "2.2.4", + "moment": "2.24.0", + "perfect-scrollbar": "1.4.0", + "raf": "3.4.1", + "react-chartjs-2": "2.7.6", + "react-image-lightbox": "5.1.0", + "react-numeric-input": "2.2.3", + "react-popper": "^1.3.4", + "react-router-dom": "^5.0.1", + "react-scroll": "1.7.11", + "react-toastify": "5.1.0", + "react-transition-group": "4.0.1" + }, + "dependencies": { + "create-react-context": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/create-react-context/-/create-react-context-0.3.0.tgz", + "integrity": "sha512-dNldIoSuNSvlTJ7slIKC/ZFGKexBMBrrcc+TTe1NdmROnaASuLPvqpwj9v4XS4uXZ8+YPu0sNmShX2rXI5LNsw==", + "dev": true, + "requires": { + "gud": "^1.0.0", + "warning": "^4.0.3" + } + }, + "react-popper": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-1.3.4.tgz", + "integrity": "sha512-9AcQB29V+WrBKk6X7p0eojd1f25/oJajVdMZkywIoAV6Ag7hzE1Mhyeup2Q1QnvFRtGQFQvtqfhlEoDAPfKAVA==", + "dev": true, + "requires": { + "@babel/runtime": "^7.1.2", + "create-react-context": "^0.3.0", + "popper.js": "^1.14.4", + "prop-types": "^15.6.1", + "typed-styles": "^0.0.7", + "warning": "^4.0.2" + } + }, + "react-toastify": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-5.1.0.tgz", + "integrity": "sha512-0kVAAE7VO609EeXLVaFHDTc6Bnd/OUAb7rrRAwMsHeaThKEhH+WEQEPftTjuA4rP59K0QhCnWu4Ds2hXAcFxaw==", + "dev": true, + "requires": { + "@babel/runtime": "^7.4.2", + "classnames": "^2.2.6", + "prop-types": "^15.7.2", + "react-transition-group": "^2.6.1" + }, + "dependencies": { + "react-transition-group": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz", + "integrity": "sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==", + "dev": true, + "requires": { + "dom-helpers": "^3.4.0", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2", + "react-lifecycles-compat": "^3.0.4" + } + } + } + }, + "react-transition-group": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.0.1.tgz", + "integrity": "sha512-SsLcBYhO4afXJC9esL8XMxi/y0ZvEc7To0TvtrBELqzpjXQHPZOTxvuPh2/4EhYc0uSMfp2SExIxsyJ0pBdNzg==", + "dev": true, + "requires": { + "dom-helpers": "^3.4.0", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + } + } + } + }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -9342,6 +9914,23 @@ } } }, + "modali": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/modali/-/modali-1.2.0.tgz", + "integrity": "sha512-yPgN+I6EHR7Liq5poMCBFJavsNCx2QKtEdnO+UT0IGpiOSOK7tcbfb5oOiaBIvpMlVn15Du+4IYktdLW9UoSKQ==", + "requires": { + "classnames": "^2.2.6", + "prop-types": "^15.7.2", + "react-syntax-highlighter": "^10.2.1", + "shortid": "^2.2.14" + } + }, + "moment": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", + "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==", + "dev": true + }, "moo": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/moo/-/moo-0.4.3.tgz", @@ -9398,6 +9987,11 @@ "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==" }, + "nanoid": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-2.1.0.tgz", + "integrity": "sha512-g5WwS+p6Cm+zQhO2YOpRbQThZVnNb7DDq74h8YDCLfAGynrEOrbx2E16dc8ciENiP1va5sqaAruqn2sN+xpkWg==" + }, "nanomatch": { "version": "1.2.13", "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", @@ -9461,13 +10055,10 @@ } }, "node-fetch": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", - "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", - "requires": { - "encoding": "^0.1.11", - "is-stream": "^1.0.1" - } + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", + "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==", + "dev": true }, "node-forge": { "version": "0.7.5", @@ -9678,6 +10269,12 @@ "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=", "dev": true }, + "normalize-scroll-left": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-scroll-left/-/normalize-scroll-left-0.1.2.tgz", + "integrity": "sha512-F9YMRls0zCF6BFIE2YnXDRpHPpfd91nOIaNdDgrx5YMoPLo8Wqj+6jNXHQsYBavJeXP4ww8HCt0xQAKc5qk2Fg==", + "dev": true + }, "npm-run-path": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", @@ -10153,6 +10750,19 @@ "safe-buffer": "^5.1.1" } }, + "parse-entities": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-1.2.2.tgz", + "integrity": "sha512-NzfpbxW/NPrzZ/yYSoQxyqUZMZXIdCfE0OIN4ESsnptHJECoUk3FZktxNuzQf4tjt5UEopnxpYJbvYuxIFDdsg==", + "requires": { + "character-entities": "^1.0.0", + "character-entities-legacy": "^1.0.0", + "character-reference-invalid": "^1.0.0", + "is-alphanumerical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-hexadecimal": "^1.0.0" + } + }, "parse-glob": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", @@ -10269,6 +10879,12 @@ "sha.js": "^2.4.8" } }, + "perfect-scrollbar": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/perfect-scrollbar/-/perfect-scrollbar-1.4.0.tgz", + "integrity": "sha512-/2Sk/khljhdrsamjJYS5NjrH+GKEHEwh7zFSiYyxROyYKagkE4kSn2zDQDRTOMo8mpT2jikxx6yI1dG7lNP/hw==", + "dev": true + }, "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", @@ -10327,6 +10943,12 @@ "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==", "dev": true }, + "popper.js": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.15.0.tgz", + "integrity": "sha512-w010cY1oCUmI+9KwwlWki+r5jxKfTFDVoadl7MSrIujHU5MJ5OR6HTDj6Xo8aoR/QsA56x8jKjA59qGH4ELtrA==", + "dev": true + }, "portfinder": { "version": "1.0.23", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.23.tgz", @@ -10525,6 +11147,14 @@ "react-is": "^16.8.4" } }, + "prismjs": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.17.1.tgz", + "integrity": "sha512-PrEDJAFdUGbOP6xK/UsfkC5ghJsPJviKgnQOoxaDbBjwc8op68Quupwt1DeAFoG8GImPhiKXAvvsH7wDSLsu1Q==", + "requires": { + "clipboard": "^2.0.0" + } + }, "private": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", @@ -10592,6 +11222,14 @@ "reflect.ownkeys": "^0.2.0" } }, + "property-information": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-5.2.2.tgz", + "integrity": "sha512-N2moasZmjn2mjVGIWpaqz5qnz6QyeQSGgGvMtl81gA9cPTWa6wpesRSe/quNnOjUHpvSH1oZx0pdz0EEckLFnA==", + "requires": { + "xtend": "^4.0.1" + } + }, "proxy-addr": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz", @@ -10803,6 +11441,16 @@ "resolved": "https://registry.npmjs.org/react-addons-test-utils/-/react-addons-test-utils-15.6.2.tgz", "integrity": "sha1-wStu/cIkfBDae4dw0YUICnsEcVY=" }, + "react-chartjs-2": { + "version": "2.7.6", + "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-2.7.6.tgz", + "integrity": "sha512-xDr0jhgt/o26atftXxTVsepz+QYZI2GNKBYpxtLvYgwffLUm18a9n562reUJAHvuwKsy2v+qMlK5HyjFtSW0mg==", + "dev": true, + "requires": { + "lodash": "^4.17.4", + "prop-types": "^15.5.8" + } + }, "react-dom": { "version": "16.9.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.9.0.tgz", @@ -10826,11 +11474,32 @@ "prop-types": "^15.6.0" } }, + "react-event-listener": { + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/react-event-listener/-/react-event-listener-0.6.6.tgz", + "integrity": "sha512-+hCNqfy7o9wvO6UgjqFmBzARJS7qrNoda0VqzvOuioEpoEXKutiKuv92dSz6kP7rYLmyHPyYNLesi5t/aH1gfw==", + "dev": true, + "requires": { + "@babel/runtime": "^7.2.0", + "prop-types": "^15.6.0", + "warning": "^4.0.1" + } + }, "react-flip-move": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/react-flip-move/-/react-flip-move-3.0.3.tgz", "integrity": "sha512-gR2jvjUgIXI7ceFWJkr8owX4vKhV0IJoXIf/Dt7gESFe5OKiSz2H6d10mKTW8fN134NDI16J4HgEgq9pKqJd5A==" }, + "react-image-lightbox": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/react-image-lightbox/-/react-image-lightbox-5.1.0.tgz", + "integrity": "sha512-R46QvffoDBscLQgTl4s3kFxVbnP7a+nIh7AXJNS0EXVeDaa6zKDKtIT+jFeEvs+F9oUHtZfenG1NHhTkO4hEOA==", + "dev": true, + "requires": { + "prop-types": "^15.6.2", + "react-modal": "^3.6.1" + } + }, "react-images-upload": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/react-images-upload/-/react-images-upload-1.2.7.tgz", @@ -10881,16 +11550,6 @@ "prop-types": "^15.5.10", "react-lifecycles-compat": "^3.0.0", "warning": "^4.0.3" - }, - "dependencies": { - "warning": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", - "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", - "requires": { - "loose-envify": "^1.0.0" - } - } } }, "react-notify-toast": { @@ -10902,6 +11561,12 @@ "prop-types": "^15.5.8" } }, + "react-numeric-input": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-numeric-input/-/react-numeric-input-2.2.3.tgz", + "integrity": "sha1-S/WRjD6v7YUagN8euZLZQQArtVI=", + "dev": true + }, "react-paginate": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/react-paginate/-/react-paginate-6.3.0.tgz", @@ -10978,6 +11643,16 @@ "tiny-warning": "^1.0.0" } }, + "react-scroll": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/react-scroll/-/react-scroll-1.7.11.tgz", + "integrity": "sha512-MCWtt8KWTBzBlo9oFE7xgAhGcgbslsfQAuGZAfYlBTt3Pxi2CX+kh8OoTUVAuOwNlt9XkoWcvDTWQwtHzm2uOg==", + "dev": true, + "requires": { + "lodash.throttle": "^4.1.1", + "prop-types": "^15.5.8" + } + }, "react-share": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/react-share/-/react-share-3.0.1.tgz", @@ -10998,6 +11673,18 @@ "prop-types": "^15.5.8" } }, + "react-syntax-highlighter": { + "version": "10.3.5", + "resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-10.3.5.tgz", + "integrity": "sha512-KR4YE7Q91bHEhvIxuvs/J3tJWfxTyBAAMS4fcMOR9h0C6SoCZIr1OUkVamHOqHMDEck4tdS9gp0D/vlAyPLftA==", + "requires": { + "@babel/runtime": "^7.3.1", + "highlight.js": "~9.13.0", + "lowlight": "~1.11.0", + "prismjs": "^1.8.4", + "refractor": "^2.4.1" + } + }, "react-test-renderer": { "version": "16.9.0", "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.9.0.tgz", @@ -11009,6 +11696,15 @@ "scheduler": "^0.15.0" } }, + "react-text-mask": { + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/react-text-mask/-/react-text-mask-5.4.3.tgz", + "integrity": "sha1-mR77QpnjDC5sLEbRP2FxaUY+DS0=", + "dev": true, + "requires": { + "prop-types": "^15.5.6" + } + }, "react-toastify": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-5.3.2.tgz", @@ -11052,6 +11748,16 @@ "loose-envify": "^1.3.1", "prop-types": "^15.5.6", "warning": "^3.0.0" + }, + "dependencies": { + "warning": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz", + "integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=", + "requires": { + "loose-envify": "^1.0.0" + } + } } }, "read-pkg": { @@ -11215,6 +11921,16 @@ "resolved": "https://registry.npmjs.org/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz", "integrity": "sha1-dJrO7H8/34tj+SegSAnpDFwLNGA=" }, + "refractor": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/refractor/-/refractor-2.10.0.tgz", + "integrity": "sha512-maW2ClIkm9IYruuFYGTqKzj+m31heq92wlheW4h7bOstP+gf8bocmMec+j7ljLcaB1CAID85LMB3moye31jH1g==", + "requires": { + "hastscript": "^5.0.0", + "parse-entities": "^1.1.2", + "prismjs": "~1.17.0" + } + }, "regenerate": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", @@ -11750,6 +12466,12 @@ } } }, + "select": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz", + "integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0=", + "optional": true + }, "select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", @@ -11971,11 +12693,25 @@ "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", "dev": true }, + "shortid": { + "version": "2.2.14", + "resolved": "https://registry.npmjs.org/shortid/-/shortid-2.2.14.tgz", + "integrity": "sha512-4UnZgr9gDdA1kaKj/38IiudfC3KHKhDc1zi/HSxd9FQDR0VLwH3/y79tZJLsVYPsJgIjeHjqIWaWVRJUj9qZOQ==", + "requires": { + "nanoid": "^2.0.0" + } + }, "signal-exit": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" }, + "simple-html-tokenizer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/simple-html-tokenizer/-/simple-html-tokenizer-0.1.1.tgz", + "integrity": "sha1-BcLuxXn//+FFoDCsJs/qYbmA+r4=", + "dev": true + }, "sisteransi": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.3.tgz", @@ -12212,6 +12948,11 @@ "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=" }, + "space-separated-tokens": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.4.tgz", + "integrity": "sha512-UyhMSmeIqZrQn2UdjYpxEkwY9JUrn8pP+7L4f91zRzOQuI8MF1FGLfYU9DKCYeLdo7LXMxwrX5zKFy7eeeVHuA==" + }, "spdx-correct": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", @@ -12653,6 +13394,43 @@ "has-flag": "^3.0.0" } }, + "svg-inline-loader": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/svg-inline-loader/-/svg-inline-loader-0.8.0.tgz", + "integrity": "sha512-rynplY2eXFrdNomL1FvyTFQlP+dx0WqbzHglmNtA9M4IHRC3no2aPAl3ny9lUpJzFzFMZfWRK5YIclNU+FRePA==", + "dev": true, + "requires": { + "loader-utils": "^0.2.11", + "object-assign": "^4.0.1", + "simple-html-tokenizer": "^0.1.1" + }, + "dependencies": { + "big.js": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.2.0.tgz", + "integrity": "sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==", + "dev": true + }, + "json5": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", + "dev": true + }, + "loader-utils": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz", + "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=", + "dev": true, + "requires": { + "big.js": "^3.1.3", + "emojis-list": "^2.0.0", + "json5": "^0.5.0", + "object-assign": "^4.0.1" + } + } + } + }, "symbol-observable": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", @@ -12927,6 +13705,12 @@ "setimmediate": "^1.0.4" } }, + "tiny-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", + "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==", + "optional": true + }, "tiny-invariant": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.0.6.tgz", @@ -13111,6 +13895,12 @@ "mime-types": "~2.1.24" } }, + "typed-styles": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/typed-styles/-/typed-styles-0.0.7.tgz", + "integrity": "sha512-pzP0PWoZUhsECYjABgCGQlRGL1n7tOHsgwYv3oIiEpJwGhFTuty/YNeduxQYzXXa3Ge5BdT6sHYIQYpl4uJ+5Q==", + "dev": true + }, "typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", @@ -13454,9 +14244,9 @@ } }, "warning": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz", - "integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", "requires": { "loose-envify": "^1.0.0" } @@ -14273,8 +15063,7 @@ "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" }, "y18n": { "version": "3.2.1", diff --git a/package.json b/package.json index 37b20f6..23c9eaa 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "jsonwebtoken": "^8.5.1", "jwt-decode": "^2.2.0", "lodash": "^4.17.15", + "modali": "^1.2.0", "moxios": "^0.4.0", "node-sass": "^4.12.0", "prop-types": "^15.7.2", @@ -58,10 +59,10 @@ "react-addons-test-utils": "^15.6.2", "react-dom": "^16.8.6", "react-draft-wysiwyg": "^1.13.2", + "react-loader-spinner": "^3.1.2", "react-images-upload": "^1.2.7", "react-infinite-scroller": "^1.2.4", "react-lazyload": "^2.6.2", - "react-loader-spinner": "^3.1.2", "react-modal": "^3.10.1", "react-notify-toast": "^0.5.0", "react-paginate": "^6.3.0", @@ -99,6 +100,7 @@ "babel-loader": "8.0.6", "babel-plugin-styled-components": "^1.10.6", "babel-plugin-transform-class-properties": "^6.24.1", + "bootstrap-4-react": "0.0.59", "css-loader": "^3.2.0", "enzyme-to-json": "^3.4.0", "eslint": "^5.16.0", @@ -115,13 +117,16 @@ "jest": "^24.8.0", "jest-localstorage-mock": "^2.4.0", "jest-transform-css": "^2.0.0", + "mdbreact": "^4.19.1", + "node-fetch": "^2.6.0", "postcss-loader": "^3.0.0", "react-redux-toastr": "^7.5.1", - "react-test-renderer": "^16.8.6", + "react-test-renderer": "^16.9.0", "redux-mock-store": "^1.5.3", "redux-promise-middleware": "^6.1.1", "sass-loader": "^7.1.0", "sass-resources-loader": "^2.0.1", + "svg-inline-loader": "^0.8.0", "url-loader": "^2.1.0", "webpack": "^4.39.1", "webpack-cli": "^3.3.6", diff --git a/src/actions/types.js b/src/actions/types.js index 59b8501..22e27e0 100644 --- a/src/actions/types.js +++ b/src/actions/types.js @@ -31,3 +31,24 @@ export const PROFILE_NOT_FOUND = 'PROFILE_NOT_FOUND'; export const FETCH_PROFILE_ARTICLE = 'FETCH_ALL_ARTICLE'; export const PROFILE_ARTICLE_LOADING = 'PROFILE_ARTICLE_LOADING'; export const REMOVE_CURRENT_USER = 'REMOVE_CURRENT_USER'; +export const GET_AUTH_USER_PROFILE_START = 'GET_AUTH_USER_PROFILE_START'; +export const GET_AUTH_USER_PROFILE_SUCCESS = 'GET_AUTH_USER_PROFILE_SUCCESS'; +export const GET_AUTH_USER_PROFILE_FAILURE = 'GET_AUTH_USER_PROFILE_FAILURE'; +export const GET_AUTH_USER_FOLLOWERS_START = 'GET_AUTH_USER_FOLLOWERS_START'; +export const GET_AUTH_USER_FOLLOWERS_SUCCESS = 'GET_AUTH_USER_FOLLOWERS_SUCCESS'; +export const GET_AUTH_USER_FOLLOWERS_FAILURE = 'GET_AUTH_USER_FOLLOWERS_FAILURE'; +export const GET_AUTH_USER_FOLLOWEE_START = 'GET_AUTH_USER_FOLLOWEE_START'; +export const GET_AUTH_USER_FOLLOWEE_SUCCESS = 'GET_AUTH_USER_FOLLOWEE_SUCCESS'; +export const GET_AUTH_USER_FOLLOWEE_FAILURE = 'GET_AUTH_USER_FOLLOWEE_FAILURE'; +export const GET_AUTH_USER_PUBLISHED_ARTICLES_START = 'GET_AUTH_USER_PUBLISHED_ARTICLES_START'; +export const GET_AUTH_USER_PUBLISHED_ARTICLES_SUCCESS = 'GET_AUTH_USER_PUBLISHED_ARTICLES_SUCCESS'; +export const GET_AUTH_USER_PUBLISHED_ARTICLES_FAILURE = 'GET_AUTH_USER_PUBLISHED_ARTICLES_FAILURE'; +export const GET_AUTH_USER_DRAFT_ARTICLES_START = 'GET_AUTH_USER_DRAFT_ARTICLES_START'; +export const GET_AUTH_USER_DRAFT_ARTICLES_SUCCESS = 'GET_AUTH_USER_DRAFT_ARTICLES_SUCCESS'; +export const GET_AUTH_USER_DRAFT_ARTICLES_FAILURE = 'GET_AUTH_USER_DRAFT_ARTICLES_FAILURE'; +export const GET_AUTH_USER_ARTICLES_START = 'GET_AUTH_USER_ARTICLES_START'; +export const GET_AUTH_USER_ARTICLES_SUCCESS = 'GET_AUTH_USER_ARTICLES_SUCCESS'; +export const GET_AUTH_USER_ARTICLES_FAILURE = 'GET_AUTH_USER_ARTICLES_FAILURE'; +export const EDIT_AUTH_USER_PROFILE_START = 'EDIT_AUTH_USER_PROFILE_START'; +export const EDIT_AUTH_USER_PROFILE_SUCCESS = 'EDIT_AUTH_USER_PROFILE_SUCCESS'; +export const EDIT_AUTH_USER_PROFILE_FAILURE = 'EDIT_AUTH_USER_PROFILE_FAILURE'; diff --git a/src/actions/userActions.js b/src/actions/userActions.js new file mode 100644 index 0000000..77f02e7 --- /dev/null +++ b/src/actions/userActions.js @@ -0,0 +1,215 @@ +import { toast } from 'react-toastify'; +import axios from '../config/axiosInstance'; +import { + GET_AUTH_USER_PROFILE_START, + GET_AUTH_USER_PROFILE_SUCCESS, + GET_AUTH_USER_PROFILE_FAILURE, + GET_AUTH_USER_FOLLOWERS_START, + GET_AUTH_USER_FOLLOWERS_SUCCESS, + GET_AUTH_USER_FOLLOWERS_FAILURE, + GET_AUTH_USER_FOLLOWEE_START, + GET_AUTH_USER_FOLLOWEE_SUCCESS, + GET_AUTH_USER_FOLLOWEE_FAILURE, + GET_AUTH_USER_PUBLISHED_ARTICLES_START, + GET_AUTH_USER_PUBLISHED_ARTICLES_SUCCESS, + GET_AUTH_USER_PUBLISHED_ARTICLES_FAILURE, + GET_AUTH_USER_DRAFT_ARTICLES_START, + GET_AUTH_USER_DRAFT_ARTICLES_SUCCESS, + GET_AUTH_USER_DRAFT_ARTICLES_FAILURE, + GET_AUTH_USER_ARTICLES_START, + GET_AUTH_USER_ARTICLES_SUCCESS, + GET_AUTH_USER_ARTICLES_FAILURE, + EDIT_AUTH_USER_PROFILE_START, + EDIT_AUTH_USER_PROFILE_SUCCESS, + EDIT_AUTH_USER_PROFILE_FAILURE, +} from './types'; + +export const getAuthUserProfileStart = () => ({ + type: GET_AUTH_USER_PROFILE_START, +}); + +export const getAuthUserProfileSuccess = payload => ({ + type: GET_AUTH_USER_PROFILE_SUCCESS, + payload, +}); + +export const getAuthUserProfileFailure = () => ({ + type: GET_AUTH_USER_PROFILE_FAILURE, +}); + +export const authUserProfile = userId => (dispatch) => { + dispatch(getAuthUserProfileStart()); + return axios + .get(`/user/profiles/${userId}`) + .then((res) => { + const { data } = res.data; + dispatch(getAuthUserProfileSuccess(data[0])); + }) + .catch((error) => { + const { message } = error.response.data; + dispatch(getAuthUserProfileFailure()); + }); +}; + +export const getAuthUserFollowersStart = () => ({ + type: GET_AUTH_USER_FOLLOWERS_START, +}); + +export const getAuthUserFollowersSuccess = payload => ({ + type: GET_AUTH_USER_FOLLOWERS_SUCCESS, + payload, +}); +export const getAuthUserFollowersFailure = () => ({ + type: GET_AUTH_USER_FOLLOWERS_FAILURE, +}); + + +export const getAuthUserFollowers = userId => (dispatch) => { + dispatch(getAuthUserFollowersStart()); + return axios + .get(`/user/followers/${userId}`) + .then((res) => { + const { data } = res.data; + dispatch(getAuthUserFollowersSuccess(data[0].followers)); + }) + .catch((error) => { + dispatch(getAuthUserFollowersFailure()); + }); +}; + +export const getAuthUserFolloweeStart = () => ({ + type: GET_AUTH_USER_FOLLOWEE_START, +}); + +export const getAuthUserFolloweeSuccess = payload => ({ + type: GET_AUTH_USER_FOLLOWEE_SUCCESS, + payload, +}); + +export const getAuthUserFolloweeFailure = () => ({ + type: GET_AUTH_USER_FOLLOWEE_FAILURE, +}); + +export const getAuthUserFollowee = userId => (dispatch) => { + dispatch(getAuthUserFolloweeStart()); + return axios + .get(`/user/followees/${userId}`) + .then((res) => { + const { data } = res.data; + + dispatch(getAuthUserFolloweeSuccess(data[0].followees)); + }) + .catch((error) => { + + dispatch(getAuthUserFolloweeFailure()); + }); +}; + +export const getAuthUserPublishedArticlesStart = () => ({ + type: GET_AUTH_USER_PUBLISHED_ARTICLES_START, +}); + +export const getAuthUserPublishedArticlesSuccess = payload => ({ + type: GET_AUTH_USER_PUBLISHED_ARTICLES_SUCCESS, + payload, +}); + +export const getAuthUserPublishedArticlesFailure = () => ({ + type: GET_AUTH_USER_PUBLISHED_ARTICLES_FAILURE, +}); + +export const getAuthUserPublishedArticles = (userId, offset) => (dispatch) => { + dispatch(getAuthUserPublishedArticlesStart()); + return axios + .get(`/articles/user/published/${userId}?limit=6¤tPage=${offset || 1}`) + .then((res) => { + const { data } = res.data; + dispatch(getAuthUserPublishedArticlesSuccess(data[0])); + }) + .catch((error) => { + const { message } = error.response.data; + dispatch(getAuthUserPublishedArticlesFailure()); + }); +}; +export const getAuthUserDraftArticlesStart = () => ({ + type: GET_AUTH_USER_DRAFT_ARTICLES_START, +}); + +export const getAuthUserDraftArticlesSuccess = payload => ({ + type: GET_AUTH_USER_DRAFT_ARTICLES_SUCCESS, + payload, +}); + +export const getAuthUserDraftArticlesFailure = () => ({ + type: GET_AUTH_USER_DRAFT_ARTICLES_FAILURE, +}); + +export const getAuthUserDraftArticles = (userId, offset) => (dispatch) => { + dispatch(getAuthUserDraftArticlesStart()); + return axios + .get(`/articles/user/draft/${userId}?limit=6¤tPage=${offset || 1}`) + .then((res) => { + const { data } = res.data; + dispatch(getAuthUserDraftArticlesSuccess(data[0])); + }) + .catch((error) => { + const { message } = error.response.data; + dispatch(getAuthUserDraftArticlesFailure()); + }); +}; + +export const getAuthUserArticlesStart = () => ({ + type: GET_AUTH_USER_ARTICLES_START, +}); + +export const getAuthUserArticlesSuccess = payload => ({ + type: GET_AUTH_USER_ARTICLES_SUCCESS, + payload, +}); + +export const getAuthUserArticlesFailure = () => ({ + type: GET_AUTH_USER_ARTICLES_FAILURE, +}); + +export const getAuthUserArticles = userName => (dispatch) => { + dispatch(getAuthUserArticlesStart()); + return axios + .get(`/searcharticles?author=${userName}`) + .then((res) => { + const { data } = res.data; + dispatch(getAuthUserArticlesSuccess(data[0])); + }) + .catch((error) => { + const { message } = error.response.data; + dispatch(getAuthUserArticlesFailure()); + }); +}; + +export const editAuthUserProfileStart = () => ({ + type: EDIT_AUTH_USER_PROFILE_START, +}); + +export const editAuthUserProfileSuccess = payload => ({ + type: EDIT_AUTH_USER_PROFILE_SUCCESS, + payload, +}); + +export const editAuthUserProfileFailure = () => ({ + type: EDIT_AUTH_USER_PROFILE_FAILURE, +}); + +export const editAuthUserProfile = (newUserProfile, userId) => (dispatch) => { + dispatch(editAuthUserProfileStart()); + return axios + .put(`/user/profiles/${userId}`, newUserProfile) + .then((res) => { + const { data, message } = res.data; + dispatch(editAuthUserProfileSuccess(data[0])); + toast.success(message); + }) + .catch((error) => { + const { message } = error.response.data; + dispatch(editAuthUserProfileFailure()); + toast.error(message); + }); +}; diff --git a/src/components/App.js b/src/components/App.js index dbde0f8..00514b7 100644 --- a/src/components/App.js +++ b/src/components/App.js @@ -9,7 +9,7 @@ import Homepage from '../views/Homepage'; import Register from '../views/Register'; import Login from './LoginForm/Index'; import About from '../views/about'; -import Dashboard from '../views/Dashboard'; +import AuthUserProfile from '../views/Dashboard'; import NotFound from '../views/NotFound'; import SocialAuth from '../views/SocialAuth'; import ResetPassword from '../views/ResetPassword'; @@ -33,6 +33,7 @@ if (token) { loggedInUser = store.dispatch(setCurrentUser(decoded)); } + const App = () => ( ( - + diff --git a/src/components/Dashboard/DraftArticles.jsx b/src/components/Dashboard/DraftArticles.jsx new file mode 100644 index 0000000..86f61dc --- /dev/null +++ b/src/components/Dashboard/DraftArticles.jsx @@ -0,0 +1,84 @@ +import React, { Component } from 'react'; +import { connect } from 'react-redux'; +import PropTypes from 'prop-types'; +import { Link } from 'react-router-dom'; +import Pagination from '../Paginate'; +import { getAuthUserDraftArticles } from '../../actions/userActions'; + +const defaultArticle = 'https://i1.wp.com/www.africanbusinesscentral.com/wp-content/uploads/2018/07/Andela.jpeg'; + +export class DraftArticles extends Component { + state = {} + + componentDidMount() { + const { + auth: { user: { id: userId } }, + } = this.props; + this.props.getAuthUserDraftArticles(userId); + } + + paginate = (data) => { + const currentPage = data.selected; + const offset = currentPage + 1; + const { auth: { user: { id: userId } } } = this.props; + this.props.getAuthUserDraftArticles(userId, offset); + }; + + render() { + const { userProfile: { draft } } = this.props; + const { articles = [] } = draft; + return ( +
+
+
+ { articles.length === 0 ? ( +
+ No Drafted Articles +
+ ) + : articles.map(article => ( +
+ + + +
{`${article.title.substring(0, 20)}`}
+
+ + )) + } + +
+
+ +
+ {articles.length !== 0 && ( + + ) } +
+
+ ); + } +} + +DraftArticles.propTypes = { + getAuthUserDraftArticles: PropTypes.func.isRequired, + auth: PropTypes.shape({ user: {} }).isRequired, + userProfile: PropTypes.shape({ draft: [] }).isRequired, +}; + +const mapStatetoProps = ({ userProfile, auth }) => ({ + userProfile, + auth, +}); + +export default connect(mapStatetoProps, + { + getAuthUserDraftArticles, + })(DraftArticles); diff --git a/src/components/Dashboard/EditProfileForm.jsx b/src/components/Dashboard/EditProfileForm.jsx new file mode 100644 index 0000000..6055e3b --- /dev/null +++ b/src/components/Dashboard/EditProfileForm.jsx @@ -0,0 +1,145 @@ +import React, { Component } from 'react'; +import { connect } from 'react-redux'; +import PropTypes from 'prop-types'; +import { editAuthUserProfile } from '../../actions/userActions'; +/** + * @class EditProfileForm + * @extends {Component} + */ +export class EditProfileForm extends Component { + state = { + firstName: '', + lastName: '', + userName: '', + bio: '', + avatar: null, + }; + + componentDidMount() { + this.setState({ + ...this.props.userdetails, + }); + } + + onImageChange = ({ target }) => { + this.setState(state => ({ + ...state, + avatar: target.files[0], + })); + } + + onInputChange = (event) => { + this.setState({ + [event.target.name]: event.target.value, + }); + } + + onButtonSubmit = (event) => { + event.preventDefault(); + const formData = new FormData(); + Object.keys(this.state).forEach(key => formData.append([key], this.state[key])); + + const { + editAuthUserProfile: updateProfile, + auth: { user: { id: userId } }, + } = this.props; + updateProfile(formData, userId); + } + + + render() { + const { userProfile: { isLoading } } = this.props; + + return ( +
+
+
+
+

Change FirstName:

+ +
+
+
+
+

Change LastName:

+ +
+
+
+
+

Change UserName:

+ +
+
+
+
+

Edit Bio:

+ +
+
+ +
+
+

Upload new profile image:

+ +
+
+ +
+
+ ); + } +} + +EditProfileForm.propTypes = { + editAuthUserProfile: PropTypes.func.isRequired, + auth: PropTypes.shape({ user: {} }).isRequired, + userdetails: PropTypes.shape().isRequired, + userProfile: PropTypes.shape({ user: {}, isLoading: '' }).isRequired, +}; + +const mapStatetoProps = ({ auth, userProfile }) => ({ + auth, + userProfile, +}); + +export default connect(mapStatetoProps, { editAuthUserProfile })(EditProfileForm); diff --git a/src/components/Dashboard/PublishedArticles.jsx b/src/components/Dashboard/PublishedArticles.jsx new file mode 100644 index 0000000..f0bd509 --- /dev/null +++ b/src/components/Dashboard/PublishedArticles.jsx @@ -0,0 +1,88 @@ +import React, { Component } from 'react'; +import { connect } from 'react-redux'; +import PropTypes from 'prop-types'; +import { Link } from 'react-router-dom'; +import Pagination from '../Paginate'; +import { getAuthUserPublishedArticles } from '../../actions/userActions'; + +const defaultArticle = 'https://i1.wp.com/www.africanbusinesscentral.com/wp-content/uploads/2018/07/Andela.jpeg'; + +export class PublishedArticles extends Component { + state = {} + + componentDidMount() { + const { + auth: { user: { id: userId } }, + } = this.props; + this.props.getAuthUserPublishedArticles(userId); + } + + paginate = (data) => { + const currentPage = data.selected; + const offset = currentPage + 1; + const { auth: { user: { id: userId } } } = this.props; + this.props.getAuthUserPublishedArticles(userId, offset); + }; + + render() { + const { userProfile: { published } } = this.props; + const { articles = [] } = published; + + + return ( +
+
+
+ { articles.length === 0 ? ( +
+ No published Articles +
+ ) + : articles.map(article => ( +
+ + + +
+ {`${article.title.substring(0, 20)}`} +
+
+ )) + } +
+
+ + +
+ + {articles.length !== 0 && ( + + ) } +
+
+ ); + } +} + +PublishedArticles.propTypes = { + getAuthUserPublishedArticles: PropTypes.func.isRequired, + auth: PropTypes.shape({ user: {} }).isRequired, + userProfile: PropTypes.shape({ published: [] }).isRequired, +}; + +const mapStatetoProps = ({ userProfile, auth }) => ({ + userProfile, + auth, +}); + +export default connect(mapStatetoProps, + { + getAuthUserPublishedArticles, + })(PublishedArticles); diff --git a/src/components/Dashboard/UserArticles.jsx b/src/components/Dashboard/UserArticles.jsx new file mode 100644 index 0000000..b9b3d94 --- /dev/null +++ b/src/components/Dashboard/UserArticles.jsx @@ -0,0 +1,29 @@ +import React, { useState } from 'react'; +import DraftArticles from './DraftArticles'; +import PublishedArticles from './PublishedArticles'; + + +export const UserArticles = () => { + const [showPublished, setShowPublished] = useState(true); + + return ( +
+
+ +
+ + + {' '} + | + + +
+ + { showPublished ? : } + +
+
+ ); +}; + +export default UserArticles; diff --git a/src/components/Dashboard/UserDashboard.scss b/src/components/Dashboard/UserDashboard.scss new file mode 100644 index 0000000..f3ffa2d --- /dev/null +++ b/src/components/Dashboard/UserDashboard.scss @@ -0,0 +1,123 @@ +body { + background-color: #D9D7D7; + min-height: 100vh; + width: 100%; +} + +.profilePix { + width: 150px; + height: 150px; + border-radius: 50%; +} + +.imgConatiner { + position: relative; +} +.coco { + margin-top: 20px; + } +.editText { + position: absolute; + top: 50%; + left: 5%; + transform: translate(-5%, -50%); + visibility: hidden; + opacity: 0; + transition: all .4s ease-in-out; + background: white; + padding: 10px; + cursor: pointer; +} +.center-toggle { + text-align: center; +} +.articlePublished { + background: none; + border: none; +} +.articleDraft { + background: none; + border: none; + padding-left: 10px; +} + +.articleDraft:focus { + outline: none; +} +.imgConatiner:hover .editText { + left: 50%; + visibility: visible; + opacity: 1; + transform: translate(-50%, -50%); + +} + +.articlePublished:focus { + border: none; + outline: none; +} + +.margin-left-15 { + margin-left: 15px !important; +} +.margin-right-15 { + margin-right: 15px !important; +} +.top-left { + align-self: center; +} +.tops { + justify-content: space-between; + margin-top: 15px; +} +.centering { + margin: 0 auto; + +} +.padding-top-10 { + padding-top: 10px; +} +.articleImages { + height: 170px !important; + width: 100% +} +.margin { + margin-top: 20px; + margin-bottom: 20px; + text-align: center; +} +.fonts{ + font-weight: 300; + font-size: 2rem; + line-height: 3rem; +} + +.overview{ + display: grid; + grid-template-columns: auto 30%; +} + +.overview > button{ + padding: 5px; + border: 3px solid #2E99D6; + justify-self: center; + align-self: center; + background: #ffffff; + color: #2E99D6; +} +.edit-form { + font-size: 1rem; + margin: 0px auto; + background: rgba(46,153,214,0.9) !important; + color: #fff !important; + padding: 35px !important; + border-radius: 10px !important; + box-sizing: border-box !important; +} +.modali-body { + max-height: 70vh; + overflow: auto; +} +.active { + border-bottom: 2px #2E99D6 solid !important; +} diff --git a/src/components/Dashboard/UserProfile.jsx b/src/components/Dashboard/UserProfile.jsx new file mode 100644 index 0000000..e912f3f --- /dev/null +++ b/src/components/Dashboard/UserProfile.jsx @@ -0,0 +1,99 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import Modali, { useModali } from 'modali'; +import PropTypes from 'prop-types'; + + +import EditProfileForm from './EditProfileForm'; + +const avatar = 'https://res.cloudinary.com/phembarl/image/upload/v1567166355/avartar3_clrio9.png'; + +export const UserProfile = (props) => { + const [userProfileModal, toggleUserProfileModal] = useModali({ + animated: true, + title: 'Edit Profile', + }); + + const { + userProfile: { + profile: { + firstName, lastName, userName, imageUrl, bio, + }, + followers, + followees, + articles, + }, + } = props; + + return ( +
+
+
+
+
+ +
+ +
+ +

{`${firstName} ${lastName}`}

+

{`@${userName}`}

+ +
+
+ + + + +
+ + +
+
+ followers +

{followers.length}

+
+
+ following +

{followees.length}

+
+
+ articles +

+ {typeof articles === 'string' ? 'N/A' : articles.articleCount} +

+
+
+ +
+
+ ); +}; + + +UserProfile.propTypes = { + editAuthUserProfile: PropTypes.func.isRequired, + auth: PropTypes.shape({ user: {} }).isRequired, + userProfile: PropTypes.shape( + { + profile: {}, + followers: {}, + followees: {}, + articles: {}, + }, + ).isRequired, +}; + +const mapStatetoProps = ({ auth, userProfile }) => ({ + auth, + userProfile, +}); + +export default connect(mapStatetoProps)(UserProfile); diff --git a/src/components/Dashboard/index.jsx b/src/components/Dashboard/index.jsx new file mode 100644 index 0000000..def7ae1 --- /dev/null +++ b/src/components/Dashboard/index.jsx @@ -0,0 +1,55 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import PropTypes from 'prop-types'; +import './UserDashboard.scss'; +import UserProfile from './UserProfile'; +import UserArticles from './UserArticles'; +import { + authUserProfile, + getAuthUserFollowers, + getAuthUserFollowee, + getAuthUserArticles, +} from '../../actions/userActions'; + + +/** + * + * + * UserDashboard + * @extends {Component} + */ +export const UserDashboard = (props) => { + if (props.auth !== null) { + const { auth: { user: { userName, id: userId } } } = props; + props.authUserProfile(userId); + props.getAuthUserFollowers(userId); + props.getAuthUserFollowee(userId); + props.getAuthUserArticles(userName); + } + return ( +
+ + +
+ ); +}; + +UserDashboard.propTypes = { + authUserProfile: PropTypes.func.isRequired, + getAuthUserFollowers: PropTypes.func.isRequired, + getAuthUserFollowee: PropTypes.func.isRequired, + getAuthUserArticles: PropTypes.func.isRequired, + auth: PropTypes.shape().isRequired, +}; + +const mapStatetoProps = ({ auth }) => ({ + auth, +}); + + +export default connect(mapStatetoProps, { + authUserProfile, + getAuthUserFollowers, + getAuthUserFollowee, + getAuthUserArticles, +})(UserDashboard); diff --git a/src/components/NavigationBar/index.jsx b/src/components/NavigationBar/index.jsx index 5f29444..c2e9c9f 100644 --- a/src/components/NavigationBar/index.jsx +++ b/src/components/NavigationBar/index.jsx @@ -204,7 +204,7 @@ export class Navbar extends React.Component { >
  • Create Article
  • My Articles
  • -
  • Edit Profile
  • +
  • View Profile
  • Log Out diff --git a/src/components/article/CreateArticlePage.jsx b/src/components/article/CreateArticlePage.jsx index 666dca1..9c0aa0d 100644 --- a/src/components/article/CreateArticlePage.jsx +++ b/src/components/article/CreateArticlePage.jsx @@ -264,6 +264,7 @@ export class CreateArticle extends React.Component { ); } } + CreateArticle.defaultProps = { isLoading: false, newArticle: () => {}, diff --git a/src/config/axiosInstance.js b/src/config/axiosInstance.js index dd5092b..aaa765a 100644 --- a/src/config/axiosInstance.js +++ b/src/config/axiosInstance.js @@ -1,7 +1,13 @@ 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 f078ac1..cc6113a 100644 --- a/src/reducers/index.js +++ b/src/reducers/index.js @@ -14,6 +14,9 @@ import fetchCategoryReducer from './category/category'; import updateArticlesReducer from './article/update'; import profileReducer from './profileReducer'; + +import userReducer from './userReducer'; + export default combineReducers({ auth: authReducer, err: errorReducer, @@ -30,4 +33,5 @@ export default combineReducers({ fetchCategoryReducer, updateArticlesReducer, profile: profileReducer, + userProfile: userReducer, }); diff --git a/src/reducers/userReducer.js b/src/reducers/userReducer.js new file mode 100644 index 0000000..f2005e5 --- /dev/null +++ b/src/reducers/userReducer.js @@ -0,0 +1,155 @@ +import { + GET_AUTH_USER_PROFILE_START, + GET_AUTH_USER_PROFILE_SUCCESS, + GET_AUTH_USER_PROFILE_FAILURE, + GET_AUTH_USER_FOLLOWERS_START, + GET_AUTH_USER_FOLLOWERS_SUCCESS, + GET_AUTH_USER_FOLLOWERS_FAILURE, + GET_AUTH_USER_FOLLOWEE_START, + GET_AUTH_USER_FOLLOWEE_SUCCESS, + GET_AUTH_USER_FOLLOWEE_FAILURE, + GET_AUTH_USER_PUBLISHED_ARTICLES_START, + GET_AUTH_USER_PUBLISHED_ARTICLES_SUCCESS, + GET_AUTH_USER_PUBLISHED_ARTICLES_FAILURE, + GET_AUTH_USER_DRAFT_ARTICLES_START, + GET_AUTH_USER_DRAFT_ARTICLES_SUCCESS, + GET_AUTH_USER_DRAFT_ARTICLES_FAILURE, + GET_AUTH_USER_ARTICLES_START, + GET_AUTH_USER_ARTICLES_SUCCESS, + GET_AUTH_USER_ARTICLES_FAILURE, + EDIT_AUTH_USER_PROFILE_START, + EDIT_AUTH_USER_PROFILE_SUCCESS, + EDIT_AUTH_USER_PROFILE_FAILURE, +} from '../actions/types'; + + +const initialState = { + isLoading: false, + profile: {}, + followers: [], + followees: [], + published: [], + draft: [], + articles: [], +}; + +const reducer = (state = initialState, action) => { + switch (action.type) { + case GET_AUTH_USER_PROFILE_START: + return { + ...state, + isLoading: true, + }; + case GET_AUTH_USER_PROFILE_SUCCESS: + return { + ...state, + isLoading: false, + profile: action.payload, + }; + case GET_AUTH_USER_PROFILE_FAILURE: + return { + ...state, + isLoading: false, + }; + case GET_AUTH_USER_FOLLOWERS_START: + return { + ...state, + isLoading: true, + }; + case GET_AUTH_USER_FOLLOWERS_SUCCESS: + return { + ...state, + isLoading: false, + followers: action.payload, + }; + case GET_AUTH_USER_FOLLOWERS_FAILURE: + return { + ...state, + isLoading: false, + }; + case GET_AUTH_USER_FOLLOWEE_START: + return { + ...state, + isLoading: true, + }; + case GET_AUTH_USER_FOLLOWEE_SUCCESS: + return { + ...state, + isLoading: false, + followees: action.payload, + }; + case GET_AUTH_USER_FOLLOWEE_FAILURE: + return { + ...state, + isLoading: false, + }; + case GET_AUTH_USER_PUBLISHED_ARTICLES_START: + return { + ...state, + isLoading: true, + }; + case GET_AUTH_USER_PUBLISHED_ARTICLES_SUCCESS: + return { + ...state, + isLoading: false, + published: action.payload, + }; + case GET_AUTH_USER_PUBLISHED_ARTICLES_FAILURE: + return { + ...state, + isLoading: false, + }; + case GET_AUTH_USER_DRAFT_ARTICLES_START: + return { + ...state, + isLoading: true, + }; + case GET_AUTH_USER_DRAFT_ARTICLES_SUCCESS: + return { + ...state, + isLoading: false, + draft: action.payload, + }; + case GET_AUTH_USER_DRAFT_ARTICLES_FAILURE: + return { + ...state, + isLoading: false, + }; + case GET_AUTH_USER_ARTICLES_START: + return { + ...state, + isLoading: true, + }; + case GET_AUTH_USER_ARTICLES_SUCCESS: + return { + ...state, + isLoading: false, + articles: action.payload, + }; + case GET_AUTH_USER_ARTICLES_FAILURE: + return { + ...state, + isLoading: false, + }; + case EDIT_AUTH_USER_PROFILE_START: + return { + ...state, + isLoading: true, + }; + case EDIT_AUTH_USER_PROFILE_SUCCESS: + return { + ...state, + isLoading: false, + profile: action.payload, + }; + case EDIT_AUTH_USER_PROFILE_FAILURE: + return { + ...state, + isLoading: false, + }; + default: + return state; + } +}; + +export default reducer; diff --git a/src/views/Dashboard.jsx b/src/views/Dashboard.jsx index 6772545..4faac14 100644 --- a/src/views/Dashboard.jsx +++ b/src/views/Dashboard.jsx @@ -1,9 +1,9 @@ import React from 'react'; -import { Link } from 'react-router-dom'; import decodeToken from '../utils/decoded'; +import AuthUserProfile from '../components/Dashboard/index'; /** - * Dummy Dashboard Page, user should be redirected to this page after successful login + * User Dashboard Page, user should be redirected to this page after successful login */ const style = { color: 'red', @@ -12,31 +12,17 @@ const style = { const { isVerified } = decodeToken(); -const Dashboard = (props) => { - const { userId, userName } = props; - return ( -
    -

    Welcome To Authors Haven Login Page.

    - { - isVerified === false && ( -

    - Please verify your account. A verification link has been sent to your email -

    - ) - } +const Profile = () => ( +
    + { + isVerified === false && ( +

    + Please verify your account. A verification link has been sent to your email +

    + ) + } + +
    +); -
      -
    • - CLICK HERE TO GO BACK TO THE HOMEPAGE -
    • -
    -
      -
    • - Click to view another Profile -
    • -
    -
    - ); -}; - -export default Dashboard; +export default Profile; diff --git a/webpack.config.js b/webpack.config.js index 9c31e43..84b4f43 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -41,6 +41,10 @@ module.exports = { test: /\.(png|jpe?g|gif|svg)$/i, loader: ['url-loader?limit=8000&name=images/[name].[ext]', 'file-loader'], }, + { + test: /\.svg$/, + loader: 'svg-inline-loader', + }, ], }, plugins: [