From 3b308263b1aa68fdf748ce46564a9e8ce324a7d4 Mon Sep 17 00:00:00 2001 From: Alpha1202 Date: Thu, 22 Aug 2019 08:29:42 +0100 Subject: [PATCH] ft-user-can-view-and-edit-own-profile create view authenticated user profile fetch user profile api fetch user followers api fetch user followee api fetch user article api fetch user published article api fetch user draft article api fetch user update profile api create edit profile form [Finishes #166840973] --- _test_/Dashboard.spec.js | 48 +- _test_/DraftArticles.spec.js | 60 ++ _test_/PublishedArticles.spec.js | 60 ++ _test_/authReducer.spec.js | 16 + _test_/dashboardView.spec.js | 15 + _test_/editProfileForm.spec.js | 50 ++ _test_/userAction.spec.js | 320 +++++++ _test_/userArticles.spec.js | 75 ++ _test_/userProfile.spec.js | 38 + _test_/userReduce.spec.js | 214 +++++ package-lock.json | 833 +++++++++++++++++- package.json | 9 +- src/actions/types.js | 21 + src/actions/userActions.js | 215 +++++ src/components/App.js | 5 +- src/components/Dashboard/DraftArticles.jsx | 84 ++ src/components/Dashboard/EditProfileForm.jsx | 145 +++ .../Dashboard/PublishedArticles.jsx | 88 ++ src/components/Dashboard/UserArticles.jsx | 29 + src/components/Dashboard/UserDashboard.scss | 123 +++ src/components/Dashboard/UserProfile.jsx | 99 +++ src/components/Dashboard/index.jsx | 55 ++ src/components/NavigationBar/index.jsx | 2 +- src/components/article/CreateArticlePage.jsx | 1 + src/config/axiosInstance.js | 6 + src/reducers/index.js | 4 + src/reducers/userReducer.js | 155 ++++ src/views/Dashboard.jsx | 44 +- webpack.config.js | 4 + 29 files changed, 2744 insertions(+), 74 deletions(-) create mode 100644 _test_/DraftArticles.spec.js create mode 100644 _test_/PublishedArticles.spec.js create mode 100644 _test_/dashboardView.spec.js create mode 100644 _test_/editProfileForm.spec.js create mode 100644 _test_/userAction.spec.js create mode 100644 _test_/userArticles.spec.js create mode 100644 _test_/userProfile.spec.js create mode 100644 _test_/userReduce.spec.js create mode 100644 src/actions/userActions.js create mode 100644 src/components/Dashboard/DraftArticles.jsx create mode 100644 src/components/Dashboard/EditProfileForm.jsx create mode 100644 src/components/Dashboard/PublishedArticles.jsx create mode 100644 src/components/Dashboard/UserArticles.jsx create mode 100644 src/components/Dashboard/UserDashboard.scss create mode 100644 src/components/Dashboard/UserProfile.jsx create mode 100644 src/components/Dashboard/index.jsx create mode 100644 src/reducers/userReducer.js 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: [