diff --git a/.gitignore b/.gitignore
index 7676453..bbf13f8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,3 +20,9 @@ yarn-error.log
#test coverage
coverage
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
index 3eae78b..a4b7826 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -12,7 +12,7 @@ install:
- yarn
script:
- - yarn test
+ - yarn test --updateSnapshot
notification:
- email: false
diff --git a/debug.log b/debug.log
new file mode 100644
index 0000000..19d2fb8
--- /dev/null
+++ b/debug.log
@@ -0,0 +1,2 @@
+[0609/102203.692:ERROR:process_info.cc(98)] ReadProcessMemory UNICODE_STRING: Only part of a ReadProcessMemory or WriteProcessMemory request was completed. (0x12B)
+[0609/102204.114:ERROR:process_info.cc(551)] ReadProcessData failed
diff --git a/package.json b/package.json
index 467f554..4345f0b 100644
--- a/package.json
+++ b/package.json
@@ -13,8 +13,7 @@
"coverage": "codeclimate-test-reporter < ./coverage/lcov.info"
},
"engines": {
- "yarn": "1.x",
- "node": "14.x"
+ "yarn": "1.x"
},
"repository": {
"type": "git",
@@ -34,14 +33,24 @@
"homepage": "https://github.com/Stackup-Rwanda/stackup2-barefoot-frontend#readme",
"dependencies": {
"@babel/core": "^7.9.6",
+ "@babel/plugin-transform-runtime": "^7.10.1",
+ "@babel/polyfill": "^7.10.1",
+ "@babel/preset-env": "^7.9.6",
"@babel/preset-react": "^7.9.4",
+ "@babel/runtime": "^7.10.2",
+ "@fortawesome/fontawesome-svg-core": "^1.2.28",
+ "@fortawesome/free-brands-svg-icons": "^5.13.0",
+ "@fortawesome/free-solid-svg-icons": "^5.13.0",
+ "@fortawesome/react-fontawesome": "^0.1.9",
"@material-ui/core": "^4.10.0",
"@material-ui/icons": "^4.9.1",
+ "@material-ui/styles": "^4.10.0",
"@storybook/addon-actions": "^5.3.18",
"@storybook/addon-links": "^5.3.18",
"@storybook/addons": "^5.3.18",
"@storybook/react": "^5.3.18",
"@testing-library/react": "^10.0.4",
+ "axios": "^0.19.2",
"babel-eslint": "^10.1.0",
"babel-jest": "^26.0.1",
"babel-loader": "^8.1.0",
@@ -49,6 +58,7 @@
"codeclimate-test-reporter": "^0.5.1",
"css-loader": "^3.5.3",
"dotenv": "^8.2.0",
+ "dotenv-webpack": "^1.8.0",
"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.2",
"enzyme-to-json": "^3.4.4",
@@ -64,12 +74,14 @@
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-redux": "^7.2.0",
+ "react-router": "^5.2.0",
"react-router-dom": "^5.2.0",
"react-test-renderer": "^16.13.1",
"redux": "^4.0.5",
"redux-devtools-extension": "^2.13.8",
"redux-mock-store": "^1.5.4",
"redux-thunk": "^2.3.0",
+ "regenerator-runtime": "^0.13.5",
"style-loader": "^1.2.1",
"webpack": "^4.43.0",
"webpack-cli": "^3.3.11",
diff --git a/src/__tests__/App.test.js b/src/__tests__/App.test.js
index 3edf03d..32e515a 100644
--- a/src/__tests__/App.test.js
+++ b/src/__tests__/App.test.js
@@ -2,13 +2,14 @@
/* eslint-disable no-undef */
import { mount, shallow } from 'enzyme';
import React from 'react';
+import 'regenerator-runtime/runtime';
import { Provider } from 'react-redux';
import renderer from 'react-test-renderer';
import configureStore from 'redux-mock-store';
import App from '../entry/App';
import LandingPage from '../views/LandingPage/LandingPage';
-import reducer from '../reducers/reducer';
-import firstMessage from '../actions/actions';
+import reducer from '../redux/login/reducer';
+import firstMessage from '../redux/actions/actions';
const mockStore = configureStore([]);
const store = mockStore({
@@ -20,6 +21,12 @@ const component = renderer.create(
,
);
+const initialObj = {
+ errors: '',
+ message: 'Welcome',
+ success: false,
+ token: '',
+};
describe('App tests', () => {
it('Will prove that the app is rendered from App component', () => {
const appRender = shallow();
@@ -27,7 +34,7 @@ describe('App tests', () => {
});
it('should return welcome when no action provided', () => {
- expect(reducer(undefined, {})).toEqual({ message: 'Welcome' });
+ expect(reducer(undefined, {})).toEqual(initialObj);
});
it('should return Redux when action is provided with value', () => {
expect(reducer(undefined, { ...firstMessage, value: 'Redux' })).toEqual({ message: 'Redux' });
diff --git a/src/__tests__/NavBar.test.js b/src/__tests__/NavBar.test.js
index d1b6ec4..dd15639 100644
--- a/src/__tests__/NavBar.test.js
+++ b/src/__tests__/NavBar.test.js
@@ -1,13 +1,14 @@
/* eslint-disable no-undef */
import React from 'react';
import renderer from 'react-test-renderer';
+import { BrowserRouter as Router } from 'react-router-dom';
import { cleanup } from '@testing-library/react';
import NavBar from '../views/NavBar/NavBar';
describe('', () => {
afterEach(cleanup);
it('Should match the NavBar component snapshot', () => {
- const tree = renderer.create().toJSON();
+ const tree = renderer.create().toJSON();
expect(tree).toMatchSnapshot();
});
});
diff --git a/src/__tests__/__snapshots__/NavBar.test.js.snap b/src/__tests__/__snapshots__/NavBar.test.js.snap
index 56d5cf0..70dac74 100644
--- a/src/__tests__/__snapshots__/NavBar.test.js.snap
+++ b/src/__tests__/__snapshots__/NavBar.test.js.snap
@@ -2,7 +2,7 @@
exports[` Should match the NavBar component snapshot 1`] = `
Should match the NavBar component snapshot 1`] = `
className="MuiTypography-root MuiLink-root MuiLink-underlineHover navlink MuiTypography-colorPrimary"
href="/login"
onBlur={[Function]}
+ onClick={[Function]}
onFocus={[Function]}
>
Login
diff --git a/src/__tests__/action/__snapshots__/loginAction.test.js.snap b/src/__tests__/action/__snapshots__/loginAction.test.js.snap
new file mode 100644
index 0000000..7aa8b07
--- /dev/null
+++ b/src/__tests__/action/__snapshots__/loginAction.test.js.snap
@@ -0,0 +1,14 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`login actions Dispatches the correct action and payload 1`] = `Array []`;
+
+exports[`login actions Dispatches the correct action and payload of wrong email 1`] = `
+Array [
+ Object {
+ "payload": Object {
+ "error": "Invalid email",
+ },
+ "type": "LOGIN_HANDLE",
+ },
+]
+`;
diff --git a/src/__tests__/action/loginAction.test.js b/src/__tests__/action/loginAction.test.js
new file mode 100644
index 0000000..2e463ca
--- /dev/null
+++ b/src/__tests__/action/loginAction.test.js
@@ -0,0 +1,29 @@
+/* eslint-disable no-undef */
+import 'regenerator-runtime/runtime';
+import configureStore from 'redux-mock-store';
+import thunk from 'redux-thunk';
+import handleLogin from '../../redux/login/loginActions';
+
+const mockStore = configureStore([thunk]);
+const store = mockStore();
+const dispatchLogin = (data) => store.dispatch(handleLogin(data));
+export default global.fetch = jest.fn(() => Promise.resolve({
+ json: () => Promise.resolve({}),
+}));
+describe('login actions', () => {
+ beforeEach(() => {
+ store.clearActions();
+ });
+ test('Dispatches the correct action and payload', () => {
+ const data = {
+ email: 'diny@gmail.com',
+ password: 'Diny@2020',
+ };
+ dispatchLogin(data);
+ expect(store.getActions()).toMatchSnapshot();
+ });
+ test('Dispatches the correct action and payload of wrong email', () => {
+ dispatchLogin({ email: 'din', password: 'Diny@2020' });
+ expect(store.getActions()).toMatchSnapshot();
+ });
+});
diff --git a/src/__tests__/component/login.test.js b/src/__tests__/component/login.test.js
new file mode 100644
index 0000000..1a19674
--- /dev/null
+++ b/src/__tests__/component/login.test.js
@@ -0,0 +1,69 @@
+/* eslint-disable no-undef */
+import { mount } from 'enzyme';
+import React from 'react';
+import 'regenerator-runtime/runtime';
+import { Provider } from 'react-redux';
+import configureStore from 'redux-mock-store';
+import TextField from '@material-ui/core/TextField';
+import Login, {
+ mapDispatchToProps,
+ mapStateToProps,
+} from '../../components/forms/UserLogin';
+import InputField from '../../components/forms/InputField';
+import Lines from '../../components/forms/Lines';
+import Links from '../../components/forms/Link';
+import SubmitButton from '../../components/forms/SubmitButton';
+import SocialLogin from '../../components/forms/SocialLogin';
+
+const defaultProps = {
+ errors: '',
+ success: false,
+ token: '',
+};
+const mockStore = configureStore([]);
+const store = mockStore({
+ message: 'Welcome',
+ errors: '',
+ success: false,
+ token: '',
+});
+store.dispatch = jest.fn();
+
+describe('App tests', () => {
+ it('rendering the user login component', () => {
+ const submit = jest.fn();
+ const change = jest.fn();
+ const chi = 'forget Password?';
+ const login = mount(
);
+ expect(login.contains(
+
,
+
Login,
+
{chi},
+
,
+
,
+ ));
+ });
+ it('login', () => {
+ const event = {
+ preventDefault() {},
+ target: { value: 'example@gmail.com' },
+ };
+ const dispatch = jest.fn();
+ mapDispatchToProps(dispatch).login();
+ mapStateToProps({ defaultProps });
+ const wrapper = mount(
);
+
+ wrapper
+ .find(TextField)
+ .at(0)
+ .simulate('change', event);
+ wrapper
+ .find(TextField)
+ .at(1)
+ .simulate('change', { target: { value: 'Example@2020' } });
+ wrapper
+ .find(SubmitButton)
+ .simulate('click');
+ expect(dispatch).toBeCalledTimes(1);
+ });
+});
diff --git a/src/__tests__/helpers/__snapshots__/loginHelper.test.js.snap b/src/__tests__/helpers/__snapshots__/loginHelper.test.js.snap
new file mode 100644
index 0000000..7aa8b07
--- /dev/null
+++ b/src/__tests__/helpers/__snapshots__/loginHelper.test.js.snap
@@ -0,0 +1,14 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`login actions Dispatches the correct action and payload 1`] = `Array []`;
+
+exports[`login actions Dispatches the correct action and payload of wrong email 1`] = `
+Array [
+ Object {
+ "payload": Object {
+ "error": "Invalid email",
+ },
+ "type": "LOGIN_HANDLE",
+ },
+]
+`;
diff --git a/src/__tests__/helpers/loginHelper.test.js b/src/__tests__/helpers/loginHelper.test.js
new file mode 100644
index 0000000..058bd4a
--- /dev/null
+++ b/src/__tests__/helpers/loginHelper.test.js
@@ -0,0 +1,24 @@
+/* eslint-disable no-undef */
+import 'regenerator-runtime/runtime';
+import userLogin from '../../helpers/userLogin';
+import global from '../action/loginAction.test';
+
+describe('login helpers', () => {
+ it('login functionality success', async () => {
+ const credentials = {
+ email: 'travel@gmail.com',
+ password: 'TravelAdmin2@',
+ };
+ await userLogin(credentials);
+ expect(fetch).toHaveBeenCalledTimes(1);
+ });
+ it('login functionality fail', async () => {
+ const credentials = {
+ email: 'travel',
+ password: 'Travel',
+ };
+ global.fetch = jest.fn(() => Promise.reject());
+ await userLogin(credentials);
+ expect(fetch).toHaveBeenCalledTimes(1);
+ });
+});
diff --git a/src/__tests__/reducer/loginReducer.test.js b/src/__tests__/reducer/loginReducer.test.js
new file mode 100644
index 0000000..6c7e342
--- /dev/null
+++ b/src/__tests__/reducer/loginReducer.test.js
@@ -0,0 +1,18 @@
+/* eslint-disable no-undef */
+import 'regenerator-runtime/runtime';
+import reducer, { initialState } from '../../redux/login/reducer';
+import LOGIN_HANDLE from '../../redux/login/loginTypes';
+
+
+describe('', () => {
+ it('should return Redux when action is provided with value', () => {
+ const payload = {
+ error: 'invalid email',
+ };
+ const expected = {
+ ...initialState,
+ errors: payload.error,
+ };
+ expect(reducer(undefined, { type: LOGIN_HANDLE, payload })).toEqual(expected);
+ });
+});
diff --git a/src/assets/HomePic.jpg b/src/assets/HomePic.jpg
new file mode 100644
index 0000000..291eb3a
Binary files /dev/null and b/src/assets/HomePic.jpg differ
diff --git a/src/assets/styles/css/index.css b/src/assets/styles/css/index.css
index 5b35643..05136e5 100644
--- a/src/assets/styles/css/index.css
+++ b/src/assets/styles/css/index.css
@@ -1,5 +1,28 @@
h1 {
font-family: Arial, Helvetica, sans-serif;
text-align: center;
- padding: 20px;
+ font-size: larger;
+}
+
+body {
+ height: 100%;
+}
+
+.span {
+ color: red;
+}
+
+.main {
+ width: 100%;
+ padding-top: 1.5%;
+ box-sizing: border-box;
+ background-image: url("../../HomePic.jpg");
+ height: 100%;
+ background-position: center;
+ background-repeat: no-repeat;
+ background-size: cover;
+}
+
+.main h1 {
+ color: white;
}
diff --git a/src/components/forms/InputField.js b/src/components/forms/InputField.js
new file mode 100644
index 0000000..adb1bc7
--- /dev/null
+++ b/src/components/forms/InputField.js
@@ -0,0 +1,44 @@
+import React from 'react';
+import TextField from '@material-ui/core/TextField';
+import { makeStyles } from '@material-ui/core/styles';
+import PropTypes from 'prop-types';
+
+const useStyles = makeStyles({
+ textField: {
+ marginBottom: '7%',
+ marginTop: '1%',
+ '& div': {
+ borderRadius: 18,
+ },
+ },
+ input: {
+ color: 'black',
+ backgroundColor: 'white',
+ },
+});
+
+const InputField = ({ handleLoginChange, name }) => {
+ const classes = useStyles();
+ return (
+
+ );
+};
+InputField.propTypes = {
+ name: PropTypes.string.isRequired,
+ handleLoginChange: PropTypes.func.isRequired,
+};
+export default InputField;
diff --git a/src/components/forms/Lines.js b/src/components/forms/Lines.js
new file mode 100644
index 0000000..eb788f5
--- /dev/null
+++ b/src/components/forms/Lines.js
@@ -0,0 +1,29 @@
+import React from 'react';
+import Divider from '@material-ui/core/Divider';
+import Grid from '@material-ui/core/Grid';
+import { makeStyles } from '@material-ui/core/styles';
+
+const useStyles = makeStyles({
+ divider: {
+ marginTop: '7%',
+ backgroundColor: 'white',
+ },
+});
+const Lines = () => {
+ const classes = useStyles();
+ return (
+
+
+
+
+
+ OR
+
+
+
+
+
+ );
+};
+
+export default Lines;
diff --git a/src/components/forms/Link.js b/src/components/forms/Link.js
new file mode 100644
index 0000000..4e57b02
--- /dev/null
+++ b/src/components/forms/Link.js
@@ -0,0 +1,25 @@
+import React from 'react';
+import Link from '@material-ui/core/Link';
+import { makeStyles } from '@material-ui/core/styles';
+import PropTypes from 'prop-types';
+
+const useStyles = makeStyles({
+ link: {
+ textAlign: 'center',
+ margin: '5%',
+ },
+});
+const Links = ({ children }) => {
+ const classes = useStyles();
+ return (
+
+
+ {children}
+
+
+ );
+};
+Links.propTypes = {
+ children: PropTypes.string.isRequired,
+};
+export default Links;
diff --git a/src/components/forms/SocialLogin.js b/src/components/forms/SocialLogin.js
new file mode 100644
index 0000000..8495fc0
--- /dev/null
+++ b/src/components/forms/SocialLogin.js
@@ -0,0 +1,47 @@
+import React from 'react';
+import FacebookIcon from '@material-ui/icons/Facebook';
+import Button from '@material-ui/core/Button';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { faGoogle } from '@fortawesome/free-brands-svg-icons';
+import { makeStyles } from '@material-ui/core/styles';
+import Aux from '../../hoc/Auxiliary/Auxiliary';
+
+const useStyles = makeStyles({
+ textField: {
+ marginBottom: '7%',
+ marginTop: '1%',
+ textTransform: 'none',
+ borderRadius: 15,
+ color: 'white',
+ },
+});
+const element =
;
+const SocialLogin = () => {
+ const classes = useStyles();
+ return (
+
+ }
+ type="button"
+ fullWidth
+ >
+ Login With Facebook
+
+
+
+ );
+};
+
+export default SocialLogin;
diff --git a/src/components/forms/SubmitButton.js b/src/components/forms/SubmitButton.js
new file mode 100644
index 0000000..cc7104d
--- /dev/null
+++ b/src/components/forms/SubmitButton.js
@@ -0,0 +1,34 @@
+import React from 'react';
+import Button from '@material-ui/core/Button';
+import PropTypes from 'prop-types';
+import { makeStyles } from '@material-ui/core/styles';
+
+const useStyles = makeStyles({
+ textField: {
+ marginBottom: '7%',
+ marginTop: '1%',
+ textTransform: 'none',
+ borderRadius: 15,
+ },
+});
+const SubmitButton = ({ submitForm, children }) => {
+ const classes = useStyles();
+ return (
+
+ );
+};
+SubmitButton.propTypes = {
+ children: PropTypes.string.isRequired,
+ submitForm: PropTypes.func.isRequired,
+};
+export default SubmitButton;
diff --git a/src/components/forms/UserLogin.js b/src/components/forms/UserLogin.js
new file mode 100644
index 0000000..d7e3b9f
--- /dev/null
+++ b/src/components/forms/UserLogin.js
@@ -0,0 +1,101 @@
+import React, { Component } from 'react';
+import { connect } from 'react-redux';
+import { Redirect } from 'react-router-dom';
+import { withStyles } from '@material-ui/core/styles';
+import PropTypes from 'prop-types';
+import InputField from './InputField';
+import SubmitButton from './SubmitButton';
+import Links from './Link';
+import Lines from './Lines';
+import SocialLogin from './SocialLogin';
+import handleLogin from '../../redux/login/loginActions';
+
+const useStyles = () => ({
+ root: {
+ backgroundColor: 'rgba(0, 167, 153, 0.15)',
+ width: '24%',
+ margin: '4% 38%',
+ padding: '1% 3%',
+ boxSizing: 'border-box',
+ borderRadius: 15,
+ '@media (max-width: 500px)': {
+ width: '80%',
+ margin: '4% 10%',
+ },
+ },
+});
+export class UserLogin extends Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ email: '',
+ password: '',
+ };
+ }
+
+ handleLoginChange = (event) => {
+ const { name, value } = event.target;
+ this.setState({
+ [name]: value,
+ });
+ }
+
+ submitForm = async (event) => {
+ const { login } = this.props;
+ const { email, password } = this.state;
+ event.preventDefault();
+ const user = {
+ email,
+ password,
+ };
+ login(user);
+ }
+
+ render() {
+ const { classes } = this.props;
+ const { success, errors } = this.props;
+ if (success === true) {
+ return
;
+ }
+ return (
+
+
+ );
+ }
+}
+export const mapStateToProps = (state) => ({
+ errors: state.errors,
+ success: state.success,
+});
+export const mapDispatchToProps = (dispatch) => ({
+ login: (credentials) => dispatch(handleLogin(credentials)),
+});
+UserLogin.propTypes = {
+ login: PropTypes.func.isRequired,
+ success: PropTypes.bool,
+ errors: PropTypes.string,
+ classes: PropTypes.instanceOf(Object).isRequired,
+};
+UserLogin.defaultProps = {
+ success: PropTypes.bool,
+ errors: PropTypes.string,
+
+};
+export default connect(mapStateToProps, mapDispatchToProps)(withStyles(useStyles)(UserLogin));
diff --git a/src/entry/App.js b/src/entry/App.js
index 2f18277..e45ee7b 100644
--- a/src/entry/App.js
+++ b/src/entry/App.js
@@ -5,6 +5,7 @@ import CssBaseline from '@material-ui/core/CssBaseline';
import theme from '../assets/styles/theme';
import LandingPage from '../views/LandingPage/LandingPage';
import NavBar from '../views/NavBar/NavBar';
+import Login from '../components/forms/UserLogin';
const App = () => (
@@ -13,6 +14,7 @@ const App = () => (
+
diff --git a/src/entry/index.js b/src/entry/index.js
index cdff617..b7c7082 100644
--- a/src/entry/index.js
+++ b/src/entry/index.js
@@ -1,14 +1,17 @@
import React from 'react';
import ReactDOM from 'react-dom';
-import { createStore } from 'redux';
+// import { createStore, applyMiddleware } from 'redux';y
import { Provider } from 'react-redux';
-import { composeWithDevTools } from 'redux-devtools-extension';
+// import { composeWithDevTools } from 'redux-devtools-extension';
+// import thunk from 'redux-thunk';
import App from './App';
import '../assets/styles/sass/index.scss';
import '../assets/styles/css/index.css';
-import reducer from '../reducers/reducer';
+import configureStore from '../store/configureStore';
-const store = createStore(reducer, composeWithDevTools());
+const store = configureStore();
+
+// const store = createStore(reducer, applyMiddleware(thunk));
ReactDOM.render(
, document.getElementById('root'));
diff --git a/src/helpers/userLogin.js b/src/helpers/userLogin.js
new file mode 100644
index 0000000..a5ad5f4
--- /dev/null
+++ b/src/helpers/userLogin.js
@@ -0,0 +1,20 @@
+import dotenv from 'dotenv';
+
+dotenv.config();
+const baseUrl = process.env.BASE_URL;
+const login = async (user) => {
+ let result = '';
+ const url = `${baseUrl}/auth/login`;
+ result = await fetch(url, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(user),
+ })
+ .then((response) => response.json())
+ .then((data) => data)
+ .catch((error) => error);
+ return result;
+};
+export default login;
diff --git a/src/hoc/Auxiliary/Auxiliary.js b/src/hoc/Auxiliary/Auxiliary.js
new file mode 100644
index 0000000..e3a1289
--- /dev/null
+++ b/src/hoc/Auxiliary/Auxiliary.js
@@ -0,0 +1,2 @@
+const aux = (props) => (props.children);
+export default aux;
diff --git a/src/reducers/reducer.js b/src/reducers/reducer.js
deleted file mode 100644
index 0fbe134..0000000
--- a/src/reducers/reducer.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import firstMessage from '../actions/actions';
-
-const initialState = {
- message: 'Welcome',
-};
-const reducer = (state = initialState, action) => {
- switch (action.type) {
- case firstMessage.type:
- return {
- message: `${action.value}`,
- };
- default:
- return state;
- }
-};
-
-export default reducer;
diff --git a/src/actions/actions.js b/src/redux/actions/actions.js
similarity index 100%
rename from src/actions/actions.js
rename to src/redux/actions/actions.js
diff --git a/src/redux/login/loginActions.js b/src/redux/login/loginActions.js
new file mode 100644
index 0000000..79cdc4d
--- /dev/null
+++ b/src/redux/login/loginActions.js
@@ -0,0 +1,18 @@
+import LOGIN_HANDLE from './loginTypes';
+import login from '../../helpers/userLogin';
+
+const loginError = (data) => ({ type: LOGIN_HANDLE, payload: data });
+
+const handleLogin = (credentials) => async (dispatch) => {
+ if (credentials.email.length === 0 || !(new RegExp(/[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,15}/g).test(credentials.email))) {
+ dispatch(loginError({ error: 'Invalid email' }));
+ return;
+ }
+ const result = await login(credentials);
+ const { error, token } = result;
+ const output = token
+ ? { success: true, error: '', token } : { error };
+ dispatch(loginError(output));
+};
+
+export default handleLogin;
diff --git a/src/redux/login/loginTypes.js b/src/redux/login/loginTypes.js
new file mode 100644
index 0000000..2f7b022
--- /dev/null
+++ b/src/redux/login/loginTypes.js
@@ -0,0 +1,3 @@
+const LOGIN_HANDLE = 'LOGIN_HANDLE';
+
+export default LOGIN_HANDLE;
diff --git a/src/redux/login/reducer.js b/src/redux/login/reducer.js
new file mode 100644
index 0000000..cd245ca
--- /dev/null
+++ b/src/redux/login/reducer.js
@@ -0,0 +1,28 @@
+import firstMessage from '../actions/actions';
+import LOGIN_HANDLE from './loginTypes';
+
+export const initialState = {
+ message: 'Welcome',
+ errors: '',
+ success: false,
+ token: '',
+};
+const reducer = (state = initialState, action) => {
+ switch (action.type) {
+ case firstMessage.type:
+ return {
+ message: `${action.value}`,
+ };
+ case LOGIN_HANDLE:
+ return {
+ ...state,
+ errors: action.payload.error,
+ success: action.payload.success || state.success,
+ token: action.payload.token || state.token,
+ };
+ default:
+ return state;
+ }
+};
+
+export default reducer;
diff --git a/src/redux/rootReducer.js b/src/redux/rootReducer.js
new file mode 100644
index 0000000..e69de29
diff --git a/src/store/configureStore.js b/src/store/configureStore.js
new file mode 100644
index 0000000..311b9e0
--- /dev/null
+++ b/src/store/configureStore.js
@@ -0,0 +1,18 @@
+import { applyMiddleware, compose, createStore } from 'redux';
+import thunk from 'redux-thunk';
+import { composeWithDevTools } from 'redux-devtools-extension';
+import reducer from '../redux/login/reducer';
+
+const configureStore = () => {
+ const middlewares = [thunk];
+ const middlewareEnhancer = applyMiddleware(...middlewares);
+
+ const enhancers = [middlewareEnhancer, composeWithDevTools()];
+ const composedEnhancers = compose(...enhancers);
+
+ const store = createStore(reducer, undefined, composedEnhancers);
+
+ return store;
+};
+
+export default configureStore;
diff --git a/src/views/NavBar/NavBar.js b/src/views/NavBar/NavBar.js
index 3a2bed4..444e9f8 100644
--- a/src/views/NavBar/NavBar.js
+++ b/src/views/NavBar/NavBar.js
@@ -1,4 +1,5 @@
import React from 'react';
+import { Link as RouterLink } from 'react-router-dom';
import { makeStyles } from '@material-ui/core/styles';
import AppBar from '@material-ui/core/AppBar';
import Toolbar from '@material-ui/core/Toolbar';
@@ -25,7 +26,7 @@ const NavBar = () => {
Nomad