From 83ddd34c7c168e847a3794ffe838de56d2cc6e55 Mon Sep 17 00:00:00 2001 From: Alexander Wong Date: Tue, 29 Aug 2017 21:12:34 -0600 Subject: [PATCH] Finished functionally complete email confirmation workflow --- README.md | 4 +- src/actions/auth/reducer.actions.js | 80 +++++++++++++++++++----- src/actions/auth/saga.actions.js | 12 +++- src/api/auth.api.js | 8 +++ src/components/App.jsx | 5 ++ src/components/Auth/Register.jsx | 15 ++--- src/components/Auth/VerifyEmail.jsx | 97 +++++++++++++++++++++++++++++ src/constants/auth.constants.js | 8 +++ src/reducers/authReducer.js | 37 ++++++++++- src/sagas/auth.sagas.js | 40 ++++++++++-- src/sagas/index.js | 5 +- 11 files changed, 274 insertions(+), 37 deletions(-) create mode 100644 src/components/Auth/VerifyEmail.jsx diff --git a/README.md b/README.md index d866719..c5ec467 100644 --- a/README.md +++ b/README.md @@ -13,12 +13,12 @@ Now you can visit `localhost:3000` from your browser. ## Environment Variables -The environment variables are embedded during the build time. For more information, please refer to the [docs](https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#adding-custom-environment-variables. +The environment variables are embedded during the build time. For more information, please refer to the [docs](https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#adding-custom-environment-variables). | Environment Variable | Default Value | Description | | ------------------------- |:-------------------------:| -------------------:| | `REACT_APP_API_ENDPOINT` | `"http://localhost:8000"` | Server API endpoint | -| `REACT_APP_REDUX_LOGGING` | `` | Set for Redux Log | +| `REACT_APP_REDUX_LOGGING` | ` ` | Set for Redux Log | ## Testing diff --git a/src/actions/auth/reducer.actions.js b/src/actions/auth/reducer.actions.js index 63d8e79..c5f9f9a 100644 --- a/src/actions/auth/reducer.actions.js +++ b/src/actions/auth/reducer.actions.js @@ -4,10 +4,15 @@ import { CLEAR_AUTH_REQUEST_ERROR, SET_AUTH_REQUEST_SUCCESS, CLEAR_AUTH_REQUEST_SUCCESS, + SET_EMAIL_VERIFICATION_SUCCESS, + CLEAR_EMAIL_VERIFICATION_SUCCESS, + SET_EMAIL_VERIFICATION_ERROR, + CLEAR_EMAIL_VERIFICATION_ERROR, SET_SELF_USER, SET_FORM_EMAIL, SET_FORM_PASSWORD, - SET_FORM_PASSWORD_CONFIRMATION + SET_FORM_PASSWORD_CONFIRMATION, + SET_FORM_EMAIL_VERIFICATION } from "../../constants/auth.constants"; import { parseError } from "../common.actions"; @@ -19,27 +24,27 @@ export function isSendingAuthRequest(sendingRequest) { } export function setAuthRequestError(exception) { - let rawError = parseError(exception); - if (rawError.email) { - rawError["Email"] = rawError.email; - delete rawError["email"]; + let error = parseError(exception); + if (error.email) { + error["Email"] = error.email; + delete error["email"]; } - if (rawError.password1) { - rawError["Password"] = rawError.password1; - delete rawError["password1"]; + if (error.password1) { + error["Password"] = error.password1; + delete error["password1"]; } - if (rawError.password2) { - rawError["Password Confirmation"] = rawError.password2; - delete rawError["password2"]; + if (error.password2) { + error["Password Confirmation"] = error.password2; + delete error["password2"]; } - if (rawError.non_field_errors) { - rawError["Non Field Errors"] = rawError.non_field_errors; - delete rawError["non_field_errors"]; + if (error.non_field_errors) { + error["Non Field Errors"] = error.non_field_errors; + delete error["non_field_errors"]; } return { type: SET_AUTH_REQUEST_ERROR, - data: parseError(exception) + data: error }; } @@ -53,13 +58,49 @@ export function setAuthRequestSuccess(response) { return { type: SET_AUTH_REQUEST_SUCCESS, data: response.detail || response - } + }; } export function clearAuthRequestSuccess() { return { type: CLEAR_AUTH_REQUEST_SUCCESS + }; +} + +export function setEmailVerificationError(exception) { + let error = parseError(exception); + if (error.detail) { + error["Email"] = error.detail; + delete error["detail"]; + } + if (error.key) { + error["Verification Key"] = error.key; + delete error["key"]; } + + return { + type: SET_EMAIL_VERIFICATION_ERROR, + data: error + }; +} + +export function clearEmailVerificationError() { + return { + type: CLEAR_EMAIL_VERIFICATION_ERROR + }; +} + +export function setEmailVerificationSuccess(response) { + return { + type: SET_EMAIL_VERIFICATION_SUCCESS, + data: response.detail || response + }; +} + +export function clearEmailVerificationSuccess() { + return { + type: CLEAR_EMAIL_VERIFICATION_SUCCESS + }; } export function setSelfUser(selfUser) { @@ -89,3 +130,10 @@ export function setFormPasswordConfirmation(passwordConfirmation) { data: passwordConfirmation }; } + +export function setFormEmailVerification(emailKey) { + return { + type: SET_FORM_EMAIL_VERIFICATION, + data: emailKey + }; +} diff --git a/src/actions/auth/saga.actions.js b/src/actions/auth/saga.actions.js index c887c72..523877a 100644 --- a/src/actions/auth/saga.actions.js +++ b/src/actions/auth/saga.actions.js @@ -1,10 +1,18 @@ import { - SEND_REGISTER_REQUEST + SEND_REGISTER_REQUEST, + SEND_EMAIL_VERIFICATION_REQUEST } from "../../constants/auth.constants"; export function sendRegisterRequest(postbody) { return { type: SEND_REGISTER_REQUEST, data: postbody - } + }; +} + +export function sendEmailVerificationRequest(postbody) { + return { + type: SEND_EMAIL_VERIFICATION_REQUEST, + data: postbody + }; } diff --git a/src/api/auth.api.js b/src/api/auth.api.js index ca11449..769e935 100644 --- a/src/api/auth.api.js +++ b/src/api/auth.api.js @@ -15,3 +15,11 @@ export function registerUser(email, password1, password2) { return Promise.resolve(response); }); } + +export function verifyEmail(emailKey) { + return post("/rest-auth/registration/verify-email/", { + key: emailKey + }).then(response => { + return Promise.resolve(response); + }); +} diff --git a/src/components/App.jsx b/src/components/App.jsx index b5d96e6..30a3ebf 100644 --- a/src/components/App.jsx +++ b/src/components/App.jsx @@ -3,6 +3,7 @@ import { Route, Switch } from "react-router-dom"; import Login from "./Auth/Login"; import Register from "./Auth/Register"; +import VerifyEmail from "./Auth/VerifyEmail"; import About from "./Static/About"; import Footer from "./Static/Footer"; import Home from "./Static/Home"; @@ -26,6 +27,10 @@ class App extends Component { + diff --git a/src/components/Auth/Register.jsx b/src/components/Auth/Register.jsx index 490eac6..3ac7730 100644 --- a/src/components/Auth/Register.jsx +++ b/src/components/Auth/Register.jsx @@ -1,9 +1,10 @@ import React, { Component } from "react"; import { connect } from "react-redux"; -import { Container, Form, Header, Message } from "semantic-ui-react"; +import { Container, Form, Header, Message } from "semantic-ui-react"; import { clearAuthRequestError, + clearAuthRequestSuccess, setFormEmail, setFormPassword, setFormPasswordConfirmation @@ -15,6 +16,7 @@ class Register extends Component { constructor(props) { super(props); this.props.dispatch(clearAuthRequestError()); + this.props.dispatch(clearAuthRequestSuccess()); } changeEmail = event => { @@ -122,12 +124,11 @@ const RegisterView = ({ /> - - Submit + + Confirmation Email Sent! +

Please check your email to confirm your registration.

+
+ Register ); diff --git a/src/components/Auth/VerifyEmail.jsx b/src/components/Auth/VerifyEmail.jsx new file mode 100644 index 0000000..c962873 --- /dev/null +++ b/src/components/Auth/VerifyEmail.jsx @@ -0,0 +1,97 @@ +import React, { Component } from "react"; +import { connect } from "react-redux"; +import { Link } from "react-router-dom"; +import { Container, Form, Header, Message } from "semantic-ui-react"; + +import { + clearEmailVerificationError, + clearEmailVerificationSuccess, + setFormEmailVerification +} from "../../actions/auth/reducer.actions"; +import { sendEmailVerificationRequest } from "../../actions/auth/saga.actions"; +import Error from "../Shared/Error"; + +class VerifyEmail extends Component { + constructor(props) { + super(props); + const emailKey = this.props.match.params.emailKey; + this.props.dispatch(clearEmailVerificationError()); + this.props.dispatch(clearEmailVerificationSuccess()); + this.props.dispatch(setFormEmailVerification(emailKey)); + } + + changeEmailKey = event => { + this.props.dispatch(setFormEmailVerification(event.target.value)); + }; + + onSubmitEmailVerification = event => { + event.preventDefault(); + const { dispatch, emailVerificationString } = this.props; + dispatch( + sendEmailVerificationRequest({ emailKey: emailVerificationString }) + ); + }; + + render() { + const { + isSendingAuthRequest, + emailVerificationRequestError, + emailVerificationRequestSuccess, + emailVerificationString + } = this.props; + return ( + + ); + } +} + +function mapStateToProps(state) { + return { ...state.auth }; +} + +const VerifyEmailView = ({ + isSendingAuthRequest, + emailVerificationRequestError, + emailVerificationRequestSuccess, + emailVerificationString, + changeEmailKey, + onSubmitEmailVerification +}) => ( + +
Verify Email
+
+ + + + + + + Email Verified! +

Please proceed to log in.

+
+ Verify Email + +
+); + +export default connect(mapStateToProps)(VerifyEmail); diff --git a/src/constants/auth.constants.js b/src/constants/auth.constants.js index 22b1946..c40e6d2 100644 --- a/src/constants/auth.constants.js +++ b/src/constants/auth.constants.js @@ -4,10 +4,18 @@ export const SET_AUTH_REQUEST_ERROR = "SET_AUTH_REQUEST_ERROR"; export const CLEAR_AUTH_REQUEST_ERROR = "CLEAR_AUTH_REQUEST_ERROR"; export const SET_AUTH_REQUEST_SUCCESS = "SET_AUTH_REQUEST_SUCCESS"; export const CLEAR_AUTH_REQUEST_SUCCESS = "CLEAR_AUTH_REQUEST_SUCCESS"; +export const SET_EMAIL_VERIFICATION_SUCCESS = "SET_EMAIL_VERIFICATION_SUCCESS"; +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_FORM_EMAIL = "SET_FORM_EMAIL"; export const SET_FORM_PASSWORD = "SET_FORM_PASSWORD"; export const SET_FORM_PASSWORD_CONFIRMATION = "SET_FORM_PASSWORD_CONFIRMATION"; +export const SET_FORM_EMAIL_VERIFICATION = "SET_FORM_EMAIL_VERIFICATION"; // Saga Auth Action Constants export const SEND_REGISTER_REQUEST = "SEND_REGISTER_REQUEST"; +export const SEND_EMAIL_VERIFICATION_REQUEST = + "SEND_EMAIL_VERIFICATION_REQUEST"; diff --git a/src/reducers/authReducer.js b/src/reducers/authReducer.js index 21325f2..6bce905 100644 --- a/src/reducers/authReducer.js +++ b/src/reducers/authReducer.js @@ -4,20 +4,28 @@ import { CLEAR_AUTH_REQUEST_ERROR, SET_AUTH_REQUEST_SUCCESS, CLEAR_AUTH_REQUEST_SUCCESS, + SET_EMAIL_VERIFICATION_ERROR, + CLEAR_EMAIL_VERIFICATION_ERROR, + SET_EMAIL_VERIFICATION_SUCCESS, + CLEAR_EMAIL_VERIFICATION_SUCCESS, SET_SELF_USER, SET_FORM_EMAIL, SET_FORM_PASSWORD, - SET_FORM_PASSWORD_CONFIRMATION + SET_FORM_PASSWORD_CONFIRMATION, + SET_FORM_EMAIL_VERIFICATION } from "../constants/auth.constants"; const initialState = { isSendingAuthRequest: false, authRequestError: "", authRequestSuccess: "", + emailVerificationRequestError: "", + emailVerificationRequestSuccess: "", currentUser: {}, email: "", password: "", - passwordConfirmation: "" + passwordConfirmation: "", + emailVerificationString: "" }; function authReducer(state = initialState, action) { @@ -47,6 +55,26 @@ function authReducer(state = initialState, action) { ...state, authRequestSuccess: "" }; + case SET_EMAIL_VERIFICATION_ERROR: + return { + ...state, + emailVerificationRequestError: action.data + }; + case CLEAR_EMAIL_VERIFICATION_ERROR: + return { + ...state, + emailVerificationRequestError: "" + }; + case SET_EMAIL_VERIFICATION_SUCCESS: + return { + ...state, + emailVerificationRequestSuccess: action.data + }; + case CLEAR_EMAIL_VERIFICATION_SUCCESS: + return { + ...state, + emailVerificationRequestSuccess: "" + }; case SET_SELF_USER: return { ...state, @@ -67,6 +95,11 @@ function authReducer(state = initialState, action) { ...state, passwordConfirmation: action.data }; + case SET_FORM_EMAIL_VERIFICATION: + return { + ...state, + emailVerificationString: action.data + }; default: return state; } diff --git a/src/sagas/auth.sagas.js b/src/sagas/auth.sagas.js index 2ec22fd..e730650 100644 --- a/src/sagas/auth.sagas.js +++ b/src/sagas/auth.sagas.js @@ -3,17 +3,19 @@ import { isSendingAuthRequest, setAuthRequestError, setAuthRequestSuccess, + setEmailVerificationError, + setEmailVerificationSuccess, clearAuthRequestError, + clearEmailVerificationError, + clearAuthRequestSuccess, + clearEmailVerificationSuccess, setFormEmail, setFormPassword, - setFormPasswordConfirmation + setFormPasswordConfirmation, + setFormEmailVerification } from "../actions/auth/reducer.actions"; -import { registerUser } from "../api/auth.api"; +import { registerUser, verifyEmail } from "../api/auth.api"; -/** - * Saga for registering a new user. - * @param {*} postBody - */ function* registerUserCall(postBody) { yield effects.put(isSendingAuthRequest(true)); const { email, password1, password2 } = postBody; @@ -27,7 +29,22 @@ function* registerUserCall(postBody) { } } +function* verifyEmailCall(postBody) { + yield effects.put(isSendingAuthRequest(true)); + const { emailKey } = postBody; + try { + return yield effects.call(verifyEmail, emailKey); + } catch (exception) { + yield effects.put(setEmailVerificationError(exception)); + return false; + } finally { + yield effects.put(isSendingAuthRequest(false)); + } +} + 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)); @@ -37,3 +54,14 @@ export function* registerUserFlow(request) { yield effects.put(setFormPasswordConfirmation("")); } } + +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)); + yield effects.put(clearEmailVerificationError()); + yield effects.put(setFormEmailVerification("")); + } +} diff --git a/src/sagas/index.js b/src/sagas/index.js index 4a981c6..6d852db 100644 --- a/src/sagas/index.js +++ b/src/sagas/index.js @@ -1,7 +1,8 @@ import { takeLatest } from "redux-saga/effects"; -import { SEND_REGISTER_REQUEST } from "../constants/auth.constants"; -import { registerUserFlow } from "./auth.sagas"; +import { SEND_REGISTER_REQUEST, SEND_EMAIL_VERIFICATION_REQUEST } from "../constants/auth.constants"; +import { registerUserFlow, verifyEmailFlow } from "./auth.sagas"; export default function* rootSaga() { yield takeLatest(SEND_REGISTER_REQUEST, registerUserFlow); + yield takeLatest(SEND_EMAIL_VERIFICATION_REQUEST, verifyEmailFlow); }