Finished functionally complete email confirmation workflow

master
Alexander Wong 7 years ago
parent 415aea74a3
commit 83ddd34c7c
  1. 4
      README.md
  2. 80
      src/actions/auth/reducer.actions.js
  3. 12
      src/actions/auth/saga.actions.js
  4. 8
      src/api/auth.api.js
  5. 5
      src/components/App.jsx
  6. 15
      src/components/Auth/Register.jsx
  7. 97
      src/components/Auth/VerifyEmail.jsx
  8. 8
      src/constants/auth.constants.js
  9. 37
      src/reducers/authReducer.js
  10. 40
      src/sagas/auth.sagas.js
  11. 5
      src/sagas/index.js

@ -13,12 +13,12 @@ Now you can visit `localhost:3000` from your browser.
## Environment Variables ## 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 | | Environment Variable | Default Value | Description |
| ------------------------- |:-------------------------:| -------------------:| | ------------------------- |:-------------------------:| -------------------:|
| `REACT_APP_API_ENDPOINT` | `"http://localhost:8000"` | Server API endpoint | | `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 ## Testing

@ -4,10 +4,15 @@ import {
CLEAR_AUTH_REQUEST_ERROR, CLEAR_AUTH_REQUEST_ERROR,
SET_AUTH_REQUEST_SUCCESS, SET_AUTH_REQUEST_SUCCESS,
CLEAR_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_SELF_USER,
SET_FORM_EMAIL, SET_FORM_EMAIL,
SET_FORM_PASSWORD, SET_FORM_PASSWORD,
SET_FORM_PASSWORD_CONFIRMATION SET_FORM_PASSWORD_CONFIRMATION,
SET_FORM_EMAIL_VERIFICATION
} from "../../constants/auth.constants"; } from "../../constants/auth.constants";
import { parseError } from "../common.actions"; import { parseError } from "../common.actions";
@ -19,27 +24,27 @@ export function isSendingAuthRequest(sendingRequest) {
} }
export function setAuthRequestError(exception) { export function setAuthRequestError(exception) {
let rawError = parseError(exception); let error = parseError(exception);
if (rawError.email) { if (error.email) {
rawError["Email"] = rawError.email; error["Email"] = error.email;
delete rawError["email"]; delete error["email"];
} }
if (rawError.password1) { if (error.password1) {
rawError["Password"] = rawError.password1; error["Password"] = error.password1;
delete rawError["password1"]; delete error["password1"];
} }
if (rawError.password2) { if (error.password2) {
rawError["Password Confirmation"] = rawError.password2; error["Password Confirmation"] = error.password2;
delete rawError["password2"]; delete error["password2"];
} }
if (rawError.non_field_errors) { if (error.non_field_errors) {
rawError["Non Field Errors"] = rawError.non_field_errors; error["Non Field Errors"] = error.non_field_errors;
delete rawError["non_field_errors"]; delete error["non_field_errors"];
} }
return { return {
type: SET_AUTH_REQUEST_ERROR, type: SET_AUTH_REQUEST_ERROR,
data: parseError(exception) data: error
}; };
} }
@ -53,13 +58,49 @@ export function setAuthRequestSuccess(response) {
return { return {
type: SET_AUTH_REQUEST_SUCCESS, type: SET_AUTH_REQUEST_SUCCESS,
data: response.detail || response data: response.detail || response
} };
} }
export function clearAuthRequestSuccess() { export function clearAuthRequestSuccess() {
return { return {
type: CLEAR_AUTH_REQUEST_SUCCESS 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) { export function setSelfUser(selfUser) {
@ -89,3 +130,10 @@ export function setFormPasswordConfirmation(passwordConfirmation) {
data: passwordConfirmation data: passwordConfirmation
}; };
} }
export function setFormEmailVerification(emailKey) {
return {
type: SET_FORM_EMAIL_VERIFICATION,
data: emailKey
};
}

@ -1,10 +1,18 @@
import { import {
SEND_REGISTER_REQUEST SEND_REGISTER_REQUEST,
SEND_EMAIL_VERIFICATION_REQUEST
} from "../../constants/auth.constants"; } from "../../constants/auth.constants";
export function sendRegisterRequest(postbody) { export function sendRegisterRequest(postbody) {
return { return {
type: SEND_REGISTER_REQUEST, type: SEND_REGISTER_REQUEST,
data: postbody data: postbody
} };
}
export function sendEmailVerificationRequest(postbody) {
return {
type: SEND_EMAIL_VERIFICATION_REQUEST,
data: postbody
};
} }

@ -15,3 +15,11 @@ export function registerUser(email, password1, password2) {
return Promise.resolve(response); return Promise.resolve(response);
}); });
} }
export function verifyEmail(emailKey) {
return post("/rest-auth/registration/verify-email/", {
key: emailKey
}).then(response => {
return Promise.resolve(response);
});
}

@ -3,6 +3,7 @@ import { Route, Switch } from "react-router-dom";
import Login from "./Auth/Login"; import Login from "./Auth/Login";
import Register from "./Auth/Register"; import Register from "./Auth/Register";
import VerifyEmail from "./Auth/VerifyEmail";
import About from "./Static/About"; import About from "./Static/About";
import Footer from "./Static/Footer"; import Footer from "./Static/Footer";
import Home from "./Static/Home"; import Home from "./Static/Home";
@ -26,6 +27,10 @@ class App extends Component {
<Route path="/about" component={About} /> <Route path="/about" component={About} />
<Route path="/auth/login" component={Login} /> <Route path="/auth/login" component={Login} />
<Route path="/auth/register" component={Register} /> <Route path="/auth/register" component={Register} />
<Route
path="/auth/verify-email/:emailKey"
component={VerifyEmail}
/>
<Route component={NoMatch} /> <Route component={NoMatch} />
</Switch> </Switch>
</div> </div>

@ -1,9 +1,10 @@
import React, { Component } from "react"; import React, { Component } from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Container, Form, Header, Message } from "semantic-ui-react"; import { Container, Form, Header, Message } from "semantic-ui-react";
import { import {
clearAuthRequestError, clearAuthRequestError,
clearAuthRequestSuccess,
setFormEmail, setFormEmail,
setFormPassword, setFormPassword,
setFormPasswordConfirmation setFormPasswordConfirmation
@ -15,6 +16,7 @@ class Register extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.props.dispatch(clearAuthRequestError()); this.props.dispatch(clearAuthRequestError());
this.props.dispatch(clearAuthRequestSuccess());
} }
changeEmail = event => { changeEmail = event => {
@ -122,12 +124,11 @@ const RegisterView = ({
/> />
</Form.Field> </Form.Field>
<Error header="Register failed!" error={authRequestError} /> <Error header="Register failed!" error={authRequestError} />
<Message <Message success>
success <Message.Header>Confirmation Email Sent!</Message.Header>
header="Registration Sent" <p>Please check your email to confirm your registration.</p>
content="A confirmation email has been sent to confirm your registration." </Message>
/> <Form.Button>Register</Form.Button>
<Form.Button>Submit</Form.Button>
</Form> </Form>
</Container> </Container>
); );

@ -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 (
<VerifyEmailView
isSendingAuthRequest={isSendingAuthRequest}
emailVerificationRequestError={emailVerificationRequestError}
emailVerificationRequestSuccess={emailVerificationRequestSuccess}
emailVerificationString={emailVerificationString}
changeEmailKey={this.changeEmailKey}
onSubmitEmailVerification={this.onSubmitEmailVerification}
/>
);
}
}
function mapStateToProps(state) {
return { ...state.auth };
}
const VerifyEmailView = ({
isSendingAuthRequest,
emailVerificationRequestError,
emailVerificationRequestSuccess,
emailVerificationString,
changeEmailKey,
onSubmitEmailVerification
}) => (
<Container>
<Header>Verify Email</Header>
<Form
loading={isSendingAuthRequest}
onSubmit={onSubmitEmailVerification}
error={!!emailVerificationRequestError}
success={!!emailVerificationRequestSuccess}
>
<Form.Field>
<label>Email Verification Key</label>
<input
placeholder=""
type="text"
value={emailVerificationString}
onChange={changeEmailKey}
/>
</Form.Field>
<Error
header="Email Verification failed!"
error={emailVerificationRequestError}
/>
<Message success>
<Message.Header>Email Verified!</Message.Header>
<p>Please proceed to <Link to="/auth/login">log in</Link>.</p>
</Message>
<Form.Button>Verify Email</Form.Button>
</Form>
</Container>
);
export default connect(mapStateToProps)(VerifyEmail);

@ -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 CLEAR_AUTH_REQUEST_ERROR = "CLEAR_AUTH_REQUEST_ERROR";
export const SET_AUTH_REQUEST_SUCCESS = "SET_AUTH_REQUEST_SUCCESS"; export const SET_AUTH_REQUEST_SUCCESS = "SET_AUTH_REQUEST_SUCCESS";
export const CLEAR_AUTH_REQUEST_SUCCESS = "CLEAR_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_SELF_USER = "SET_SELF_USER";
export const SET_FORM_EMAIL = "SET_FORM_EMAIL"; export const SET_FORM_EMAIL = "SET_FORM_EMAIL";
export const SET_FORM_PASSWORD = "SET_FORM_PASSWORD"; export const SET_FORM_PASSWORD = "SET_FORM_PASSWORD";
export const SET_FORM_PASSWORD_CONFIRMATION = "SET_FORM_PASSWORD_CONFIRMATION"; export const SET_FORM_PASSWORD_CONFIRMATION = "SET_FORM_PASSWORD_CONFIRMATION";
export const SET_FORM_EMAIL_VERIFICATION = "SET_FORM_EMAIL_VERIFICATION";
// Saga Auth Action Constants // Saga Auth Action Constants
export const SEND_REGISTER_REQUEST = "SEND_REGISTER_REQUEST"; export const SEND_REGISTER_REQUEST = "SEND_REGISTER_REQUEST";
export const SEND_EMAIL_VERIFICATION_REQUEST =
"SEND_EMAIL_VERIFICATION_REQUEST";

@ -4,20 +4,28 @@ import {
CLEAR_AUTH_REQUEST_ERROR, CLEAR_AUTH_REQUEST_ERROR,
SET_AUTH_REQUEST_SUCCESS, SET_AUTH_REQUEST_SUCCESS,
CLEAR_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_SELF_USER,
SET_FORM_EMAIL, SET_FORM_EMAIL,
SET_FORM_PASSWORD, SET_FORM_PASSWORD,
SET_FORM_PASSWORD_CONFIRMATION SET_FORM_PASSWORD_CONFIRMATION,
SET_FORM_EMAIL_VERIFICATION
} from "../constants/auth.constants"; } from "../constants/auth.constants";
const initialState = { const initialState = {
isSendingAuthRequest: false, isSendingAuthRequest: false,
authRequestError: "", authRequestError: "",
authRequestSuccess: "", authRequestSuccess: "",
emailVerificationRequestError: "",
emailVerificationRequestSuccess: "",
currentUser: {}, currentUser: {},
email: "", email: "",
password: "", password: "",
passwordConfirmation: "" passwordConfirmation: "",
emailVerificationString: ""
}; };
function authReducer(state = initialState, action) { function authReducer(state = initialState, action) {
@ -47,6 +55,26 @@ function authReducer(state = initialState, action) {
...state, ...state,
authRequestSuccess: "" 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: case SET_SELF_USER:
return { return {
...state, ...state,
@ -67,6 +95,11 @@ function authReducer(state = initialState, action) {
...state, ...state,
passwordConfirmation: action.data passwordConfirmation: action.data
}; };
case SET_FORM_EMAIL_VERIFICATION:
return {
...state,
emailVerificationString: action.data
};
default: default:
return state; return state;
} }

@ -3,17 +3,19 @@ import {
isSendingAuthRequest, isSendingAuthRequest,
setAuthRequestError, setAuthRequestError,
setAuthRequestSuccess, setAuthRequestSuccess,
setEmailVerificationError,
setEmailVerificationSuccess,
clearAuthRequestError, clearAuthRequestError,
clearEmailVerificationError,
clearAuthRequestSuccess,
clearEmailVerificationSuccess,
setFormEmail, setFormEmail,
setFormPassword, setFormPassword,
setFormPasswordConfirmation setFormPasswordConfirmation,
setFormEmailVerification
} from "../actions/auth/reducer.actions"; } 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) { function* registerUserCall(postBody) {
yield effects.put(isSendingAuthRequest(true)); yield effects.put(isSendingAuthRequest(true));
const { email, password1, password2 } = postBody; 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) { export function* registerUserFlow(request) {
yield effects.put(clearAuthRequestSuccess());
yield effects.put(clearAuthRequestError());
const wasSucessful = yield effects.call(registerUserCall, request.data); const wasSucessful = yield effects.call(registerUserCall, request.data);
if (wasSucessful) { if (wasSucessful) {
yield effects.put(setAuthRequestSuccess(wasSucessful)); yield effects.put(setAuthRequestSuccess(wasSucessful));
@ -37,3 +54,14 @@ export function* registerUserFlow(request) {
yield effects.put(setFormPasswordConfirmation("")); 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(""));
}
}

@ -1,7 +1,8 @@
import { takeLatest } from "redux-saga/effects"; import { takeLatest } from "redux-saga/effects";
import { SEND_REGISTER_REQUEST } from "../constants/auth.constants"; import { SEND_REGISTER_REQUEST, SEND_EMAIL_VERIFICATION_REQUEST } from "../constants/auth.constants";
import { registerUserFlow } from "./auth.sagas"; import { registerUserFlow, verifyEmailFlow } from "./auth.sagas";
export default function* rootSaga() { export default function* rootSaga() {
yield takeLatest(SEND_REGISTER_REQUEST, registerUserFlow); yield takeLatest(SEND_REGISTER_REQUEST, registerUserFlow);
yield takeLatest(SEND_EMAIL_VERIFICATION_REQUEST, verifyEmailFlow);
} }

Loading…
Cancel
Save