diff --git a/src/actions/auth/reducer.actions.js b/src/actions/auth/reducer.actions.js index c5f9f9a..41a4f49 100644 --- a/src/actions/auth/reducer.actions.js +++ b/src/actions/auth/reducer.actions.js @@ -8,7 +8,7 @@ import { CLEAR_EMAIL_VERIFICATION_SUCCESS, SET_EMAIL_VERIFICATION_ERROR, CLEAR_EMAIL_VERIFICATION_ERROR, - SET_SELF_USER, + SET_SELF_USER_TOKEN, SET_FORM_EMAIL, SET_FORM_PASSWORD, SET_FORM_PASSWORD_CONFIRMATION, @@ -103,9 +103,9 @@ export function clearEmailVerificationSuccess() { }; } -export function setSelfUser(selfUser) { +export function setSelfUserToken(selfUser) { return { - type: SET_SELF_USER, + type: SET_SELF_USER_TOKEN, data: selfUser }; } diff --git a/src/actions/auth/saga.actions.js b/src/actions/auth/saga.actions.js index 523877a..b33eb3e 100644 --- a/src/actions/auth/saga.actions.js +++ b/src/actions/auth/saga.actions.js @@ -1,18 +1,33 @@ import { SEND_REGISTER_REQUEST, - SEND_EMAIL_VERIFICATION_REQUEST + SEND_EMAIL_VERIFICATION_REQUEST, + SEND_LOGIN_REQUEST, + SEND_LOGOUT_REQUEST } from "../../constants/auth.constants"; -export function sendRegisterRequest(postbody) { +export function sendRegisterRequest(postBody) { return { type: SEND_REGISTER_REQUEST, - data: postbody + data: postBody }; } -export function sendEmailVerificationRequest(postbody) { +export function sendEmailVerificationRequest(postBody) { return { type: SEND_EMAIL_VERIFICATION_REQUEST, - data: postbody + data: postBody }; } + +export function sendLoginRequest(postBody) { + return { + type: SEND_LOGIN_REQUEST, + data: postBody + }; +} + +export function sendLogoutRequest() { + return { + type: SEND_LOGOUT_REQUEST + } +} diff --git a/src/api/auth.api.js b/src/api/auth.api.js index 769e935..0b9e349 100644 --- a/src/api/auth.api.js +++ b/src/api/auth.api.js @@ -11,15 +11,33 @@ export function registerUser(email, password1, password2) { email, password1, password2 - }).then(response => { - return Promise.resolve(response); - }); + }).then(resp => Promise.resolve(resp)); } +/** + * Function wrapping POST request for email validation + * @param {string} emailKey - key for email validation + */ export function verifyEmail(emailKey) { return post("/rest-auth/registration/verify-email/", { key: emailKey - }).then(response => { - return Promise.resolve(response); - }); + }).then(resp => Promise.resolve(resp)); +} + +/** + * Function wrapping POST request for user login + * @param {string} email - email of user to login + * @param {string} password - password of user to login + */ +export function loginUser(email, password) { + return post("/rest-auth/login/", { email, password }).then(resp => + Promise.resolve(resp) + ); +} + +/** + * Function wrapping POST request for user logout + */ +export function logoutUser() { + return post("/rest-auth/logout/").then(resp => Promise.resolve(resp)); } diff --git a/src/api/baseApi.js b/src/api/baseApi.js index 1fe3634..a614810 100644 --- a/src/api/baseApi.js +++ b/src/api/baseApi.js @@ -9,7 +9,7 @@ const localStorage = global.process && process.env.NODE_ENV === "test" : global.window.localStorage; function headers() { - const token = localStorage.getItem("token") || ""; + const token = localStorage.getItem("userToken") || ""; return { Accept: "application/json", diff --git a/src/components/App.jsx b/src/components/App.jsx index 30a3ebf..e444629 100644 --- a/src/components/App.jsx +++ b/src/components/App.jsx @@ -3,7 +3,9 @@ import { Route, Switch } from "react-router-dom"; import Login from "./Auth/Login"; import Register from "./Auth/Register"; +import Settings from "./Auth/Settings"; import VerifyEmail from "./Auth/VerifyEmail"; +import PrivateRoute from "./Shared/PrivateRoute"; import About from "./Static/About"; import Footer from "./Static/Footer"; import Home from "./Static/Home"; @@ -31,6 +33,7 @@ class App extends Component { path="/auth/verify-email/:emailKey" component={VerifyEmail} /> + diff --git a/src/components/Auth/Login.jsx b/src/components/Auth/Login.jsx index ddc9ebd..793ecc1 100644 --- a/src/components/Auth/Login.jsx +++ b/src/components/Auth/Login.jsx @@ -1,16 +1,111 @@ import React, { Component } from "react"; -import { Container } from "semantic-ui-react"; +import { connect } from "react-redux"; +import { Redirect } from "react-router-dom"; +import { Container, Form, Header, Message } from "semantic-ui-react"; + +import { + clearAuthRequestError, + clearAuthRequestSuccess, + setFormEmail, + setFormPassword +} from "../../actions/auth/reducer.actions"; +import { sendLoginRequest } from "../../actions/auth/saga.actions"; +import Error from "../Shared/Error"; class Login extends Component { + constructor(props) { + super(props); + this.props.dispatch(clearAuthRequestError()); + this.props.dispatch(clearAuthRequestSuccess()); + } + + changeEmail = event => { + this.props.dispatch(setFormEmail(event.target.value)); + }; + + changePassword = event => { + this.props.dispatch(setFormPassword(event.target.value)); + }; + + onSubmitLogin = event => { + event.preventDefault(); + const { dispatch, email, password } = this.props; + dispatch(sendLoginRequest({ email, password })); + }; + render() { - return ; + const { + isSendingAuthRequest, + authRequestError, + authRequestSuccess, + email, + password, + userToken + } = this.props; + if (userToken) return ; + return ( + + ); } } -const LoginView = () => ( +function mapStateToProps(state) { + return { ...state.auth }; +} + +const LoginView = ({ + isSendingAuthRequest, + authRequestError, + authRequestSuccess, + email, + password, + changeEmail, + changePassword, + onSubmitLogin +}) => ( -

Login

+
Login
+
+ + + + + + + + + + + Login successful! +

Redirecting you now...

+
+ Login +
); -export default Login; +export default connect(mapStateToProps)(Login); diff --git a/src/components/Auth/Register.jsx b/src/components/Auth/Register.jsx index 3ac7730..7c4788e 100644 --- a/src/components/Auth/Register.jsx +++ b/src/components/Auth/Register.jsx @@ -1,5 +1,6 @@ import React, { Component } from "react"; import { connect } from "react-redux"; +import { Redirect } from "react-router-dom"; import { Container, Form, Header, Message } from "semantic-ui-react"; import { @@ -50,8 +51,10 @@ class Register extends Component { authRequestSuccess, email, password, - passwordConfirmation + passwordConfirmation, + userToken } = this.props; + if (userToken) return ; return ( ; + } +} + +function mapStateToProps(state) { + return { ...state.auth }; +} + +const SettingsView = () => ( + +

Settings

+

todo, change password

+
+); + +export default connect(mapStateToProps)(Settings); diff --git a/src/components/Navbar.jsx b/src/components/Navbar.jsx index dd168e9..018e8dc 100644 --- a/src/components/Navbar.jsx +++ b/src/components/Navbar.jsx @@ -1,28 +1,61 @@ import React, { Component } from "react"; +import { connect } from "react-redux"; import { Link } from "react-router-dom"; -import { Menu } from "semantic-ui-react"; +import { Dropdown, Menu } from "semantic-ui-react"; + +import { sendLogoutRequest } from "../actions/auth/saga.actions"; class Navbar extends Component { + dispatchLogoutRequest = () => { + this.props.dispatch(sendLogoutRequest()); + }; + render() { + const { userToken } = this.props; return ( - - - Caremyway - - - About - - - - Login - - - Register - - - + ); } } -export default Navbar; +function mapStateToProps(state) { + return { ...state.auth }; +} + +const NavbarView = ({ isAuthenticated, dispatchLogoutRequest }) => ( + + + Caremyway + + + About + + {!isAuthenticated && + + + Login + + + Register + + } + {!!isAuthenticated && + + + + + Settings + + + Logout + + + + } + +); + +export default connect(mapStateToProps)(Navbar); diff --git a/src/components/Shared/PrivateRoute.jsx b/src/components/Shared/PrivateRoute.jsx new file mode 100644 index 0000000..e7b9e62 --- /dev/null +++ b/src/components/Shared/PrivateRoute.jsx @@ -0,0 +1,29 @@ +import PropTypes from "prop-types"; +import React from "react"; +import { connect } from "react-redux"; +import { Redirect, Route } from "react-router-dom"; + +const propTypes = { + userToken: PropTypes.string, + component: PropTypes.oneOfType([PropTypes.element, PropTypes.func]).isRequired +}; + +const PrivateRoute = ({ userToken, component, ...rest }) => { + return ( + { + if (!!userToken) return React.createElement(component, props); + return ; + }} + /> + ); +}; + +PrivateRoute.propTypes = propTypes; + +const mapStateToProps = state => ({ + userToken: state.auth.userToken +}); + +export default connect(mapStateToProps)(PrivateRoute); diff --git a/src/constants/auth.constants.js b/src/constants/auth.constants.js index c40e6d2..1861b04 100644 --- a/src/constants/auth.constants.js +++ b/src/constants/auth.constants.js @@ -9,7 +9,7 @@ export const CLEAR_EMAIL_VERIFICATION_SUCCESS = "CLEAR_EMAIL_VERIFICATION_SUCCESS"; export const SET_EMAIL_VERIFICATION_ERROR = "SET_EMAIL_VERIFICATION_ERROR"; export const CLEAR_EMAIL_VERIFICATION_ERROR = "CLEAR_EMAIL_VERIFICATION_ERROR"; -export const SET_SELF_USER = "SET_SELF_USER"; +export const SET_SELF_USER_TOKEN = "SET_SELF_USER_TOKEN"; export const SET_FORM_EMAIL = "SET_FORM_EMAIL"; export const SET_FORM_PASSWORD = "SET_FORM_PASSWORD"; export const SET_FORM_PASSWORD_CONFIRMATION = "SET_FORM_PASSWORD_CONFIRMATION"; @@ -19,3 +19,5 @@ export const SET_FORM_EMAIL_VERIFICATION = "SET_FORM_EMAIL_VERIFICATION"; export const SEND_REGISTER_REQUEST = "SEND_REGISTER_REQUEST"; export const SEND_EMAIL_VERIFICATION_REQUEST = "SEND_EMAIL_VERIFICATION_REQUEST"; +export const SEND_LOGIN_REQUEST = "SEND_LOGIN_REQUEST"; +export const SEND_LOGOUT_REQUEST = "SEND_LOGOUT_REQUEST"; diff --git a/src/reducers/authReducer.js b/src/reducers/authReducer.js index 6bce905..9772bed 100644 --- a/src/reducers/authReducer.js +++ b/src/reducers/authReducer.js @@ -8,20 +8,32 @@ import { CLEAR_EMAIL_VERIFICATION_ERROR, SET_EMAIL_VERIFICATION_SUCCESS, CLEAR_EMAIL_VERIFICATION_SUCCESS, - SET_SELF_USER, + SET_SELF_USER_TOKEN, SET_FORM_EMAIL, SET_FORM_PASSWORD, SET_FORM_PASSWORD_CONFIRMATION, SET_FORM_EMAIL_VERIFICATION } from "../constants/auth.constants"; +/** + * Set the user's auth token, and return the value + * @param {string} newToken + */ +function me(newToken) { + if (typeof newToken === "string") { + localStorage.setItem("userToken", newToken); + } + const userToken = localStorage.getItem("userToken"); + return userToken ? userToken : ""; +} + const initialState = { isSendingAuthRequest: false, authRequestError: "", authRequestSuccess: "", emailVerificationRequestError: "", emailVerificationRequestSuccess: "", - currentUser: {}, + userToken: me(null), email: "", password: "", passwordConfirmation: "", @@ -75,10 +87,10 @@ function authReducer(state = initialState, action) { ...state, emailVerificationRequestSuccess: "" }; - case SET_SELF_USER: + case SET_SELF_USER_TOKEN: return { ...state, - currentUser: action.data + userToken: me(action.data) }; case SET_FORM_EMAIL: return { diff --git a/src/sagas/auth.sagas.js b/src/sagas/auth.sagas.js index e730650..04ac07c 100644 --- a/src/sagas/auth.sagas.js +++ b/src/sagas/auth.sagas.js @@ -12,9 +12,15 @@ import { setFormEmail, setFormPassword, setFormPasswordConfirmation, - setFormEmailVerification + setFormEmailVerification, + setSelfUserToken } from "../actions/auth/reducer.actions"; -import { registerUser, verifyEmail } from "../api/auth.api"; +import { + registerUser, + verifyEmail, + loginUser, + logoutUser +} from "../api/auth.api"; function* registerUserCall(postBody) { yield effects.put(isSendingAuthRequest(true)); @@ -42,12 +48,29 @@ function* verifyEmailCall(postBody) { } } +function* loginUserCall(postBody) { + yield effects.put(isSendingAuthRequest(true)); + const { email, password } = postBody; + try { + return yield effects.call(loginUser, email, password); + } catch (exception) { + yield effects.put(setAuthRequestError(exception)); + return false; + } finally { + yield effects.put(isSendingAuthRequest(false)); + } +} + +function* logoutUserCall() { + yield effects.call(logoutUser); +} + export function* registerUserFlow(request) { yield effects.put(clearAuthRequestSuccess()); yield effects.put(clearAuthRequestError()); - const wasSucessful = yield effects.call(registerUserCall, request.data); - if (wasSucessful) { - yield effects.put(setAuthRequestSuccess(wasSucessful)); + const wasSuccessful = yield effects.call(registerUserCall, request.data); + if (wasSuccessful) { + yield effects.put(setAuthRequestSuccess(wasSuccessful)); yield effects.put(clearAuthRequestError()); yield effects.put(setFormEmail("")); yield effects.put(setFormPassword("")); @@ -58,10 +81,31 @@ export function* registerUserFlow(request) { export function* verifyEmailFlow(request) { yield effects.put(clearEmailVerificationSuccess()); yield effects.put(clearEmailVerificationError()); - const wasSucessful = yield effects.call(verifyEmailCall, request.data); - if (wasSucessful) { - yield effects.put(setEmailVerificationSuccess(wasSucessful)); + const wasSuccessful = yield effects.call(verifyEmailCall, request.data); + if (wasSuccessful) { + yield effects.put(setEmailVerificationSuccess(wasSuccessful)); yield effects.put(clearEmailVerificationError()); yield effects.put(setFormEmailVerification("")); } } + +export function* loginUserFlow(request) { + yield effects.put(clearAuthRequestSuccess()); + yield effects.put(clearAuthRequestError()); + const wasSuccessful = yield effects.call(loginUserCall, request.data); + if (wasSuccessful) { + yield effects.put(setSelfUserToken(wasSuccessful.key)); + yield effects.put(setAuthRequestSuccess(wasSuccessful)); + yield effects.put(clearAuthRequestError()); + yield effects.put(setFormEmail("")); + yield effects.put(setFormPassword("")); + yield effects.put(setFormPasswordConfirmation("")); + } +} + +export function* logoutUserFlow(request) { + yield effects.put(clearAuthRequestSuccess()); + yield effects.put(clearAuthRequestError()); + yield effects.call(logoutUserCall); + yield effects.put(setSelfUserToken("")); +} diff --git a/src/sagas/index.js b/src/sagas/index.js index 6d852db..82c31b0 100644 --- a/src/sagas/index.js +++ b/src/sagas/index.js @@ -1,8 +1,20 @@ import { takeLatest } from "redux-saga/effects"; -import { SEND_REGISTER_REQUEST, SEND_EMAIL_VERIFICATION_REQUEST } from "../constants/auth.constants"; -import { registerUserFlow, verifyEmailFlow } from "./auth.sagas"; +import { + SEND_REGISTER_REQUEST, + SEND_EMAIL_VERIFICATION_REQUEST, + SEND_LOGIN_REQUEST, + SEND_LOGOUT_REQUEST +} from "../constants/auth.constants"; +import { + registerUserFlow, + verifyEmailFlow, + loginUserFlow, + logoutUserFlow +} from "./auth.sagas"; export default function* rootSaga() { yield takeLatest(SEND_REGISTER_REQUEST, registerUserFlow); yield takeLatest(SEND_EMAIL_VERIFICATION_REQUEST, verifyEmailFlow); + yield takeLatest(SEND_LOGIN_REQUEST, loginUserFlow); + yield takeLatest(SEND_LOGOUT_REQUEST, logoutUserFlow); }