From 415aea74a3b7600ccd992dc3fb46071eaef1142e Mon Sep 17 00:00:00 2001 From: Alexander Wong Date: Tue, 29 Aug 2017 19:51:45 -0600 Subject: [PATCH] Finished functionally complete client registration workflow --- .env | 1 + README.md | 9 ++ package.json | 2 + src/actions/auth/reducer.actions.js | 91 +++++++++++++++++ src/actions/auth/saga.actions.js | 10 ++ src/actions/common.actions.js | 13 +++ src/api/auth.api.js | 17 ++++ src/api/baseApi.js | 52 ++++++++++ src/components/App.jsx | 21 ++-- src/components/Auth/Login.jsx | 16 +++ src/components/Auth/Register.jsx | 135 +++++++++++++++++++++++++ src/components/Navbar.jsx | 11 +- src/components/Shared/Error.jsx | 41 ++++++++ src/components/{ => Static}/About.jsx | 0 src/components/{ => Static}/Footer.jsx | 0 src/components/{ => Static}/Home.jsx | 0 src/components/Static/NoMatch.jsx | 13 +++ src/components/Topics.jsx | 41 -------- src/constants/auth.constants.js | 13 +++ src/index.js | 15 +-- src/reducers/authReducer.js | 67 +++++++++++- src/sagas/auth.sagas.js | 39 +++++++ src/sagas/index.js | 5 +- src/store/index.js | 22 ++++ yarn.lock | 19 +++- 25 files changed, 587 insertions(+), 66 deletions(-) create mode 100644 .env create mode 100644 src/actions/auth/reducer.actions.js create mode 100644 src/actions/auth/saga.actions.js create mode 100644 src/actions/common.actions.js create mode 100644 src/api/auth.api.js create mode 100644 src/api/baseApi.js create mode 100644 src/components/Auth/Login.jsx create mode 100644 src/components/Auth/Register.jsx create mode 100644 src/components/Shared/Error.jsx rename src/components/{ => Static}/About.jsx (100%) rename src/components/{ => Static}/Footer.jsx (100%) rename src/components/{ => Static}/Home.jsx (100%) create mode 100644 src/components/Static/NoMatch.jsx delete mode 100644 src/components/Topics.jsx create mode 100644 src/constants/auth.constants.js create mode 100644 src/sagas/auth.sagas.js create mode 100644 src/store/index.js diff --git a/.env b/.env new file mode 100644 index 0000000..22fc1c6 --- /dev/null +++ b/.env @@ -0,0 +1 @@ +REACT_APP_API_ENDPOINT="http://localhost:8000" diff --git a/README.md b/README.md index 6bbece7..d866719 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,15 @@ Generated using [create-react-app](https://github.com/facebookincubator/create-r 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. + +| Environment Variable | Default Value | Description | +| ------------------------- |:-------------------------:| -------------------:| +| `REACT_APP_API_ENDPOINT` | `"http://localhost:8000"` | Server API endpoint | +| `REACT_APP_REDUX_LOGGING` | `` | Set for Redux Log | + ## Testing To test the react app, call `yarn test`. diff --git a/package.json b/package.json index 1bfc12c..ed0c9c2 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,8 @@ "version": "0.1.0", "private": true, "dependencies": { + "axios": "^0.16.2", + "localStorage": "^1.0.3", "react": "^15.6.1", "react-dom": "^15.6.1", "react-redux": "^5.0.6", diff --git a/src/actions/auth/reducer.actions.js b/src/actions/auth/reducer.actions.js new file mode 100644 index 0000000..63d8e79 --- /dev/null +++ b/src/actions/auth/reducer.actions.js @@ -0,0 +1,91 @@ +import { + IS_SENDING_AUTH_REQUEST, + SET_AUTH_REQUEST_ERROR, + CLEAR_AUTH_REQUEST_ERROR, + SET_AUTH_REQUEST_SUCCESS, + CLEAR_AUTH_REQUEST_SUCCESS, + SET_SELF_USER, + SET_FORM_EMAIL, + SET_FORM_PASSWORD, + SET_FORM_PASSWORD_CONFIRMATION +} from "../../constants/auth.constants"; +import { parseError } from "../common.actions"; + +export function isSendingAuthRequest(sendingRequest) { + return { + type: IS_SENDING_AUTH_REQUEST, + data: sendingRequest + }; +} + +export function setAuthRequestError(exception) { + let rawError = parseError(exception); + if (rawError.email) { + rawError["Email"] = rawError.email; + delete rawError["email"]; + } + if (rawError.password1) { + rawError["Password"] = rawError.password1; + delete rawError["password1"]; + } + if (rawError.password2) { + rawError["Password Confirmation"] = rawError.password2; + delete rawError["password2"]; + } + if (rawError.non_field_errors) { + rawError["Non Field Errors"] = rawError.non_field_errors; + delete rawError["non_field_errors"]; + } + + return { + type: SET_AUTH_REQUEST_ERROR, + data: parseError(exception) + }; +} + +export function clearAuthRequestError() { + return { + type: CLEAR_AUTH_REQUEST_ERROR + }; +} + +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 setSelfUser(selfUser) { + return { + type: SET_SELF_USER, + data: selfUser + }; +} + +export function setFormEmail(email) { + return { + type: SET_FORM_EMAIL, + data: email + }; +} + +export function setFormPassword(password) { + return { + type: SET_FORM_PASSWORD, + data: password + }; +} + +export function setFormPasswordConfirmation(passwordConfirmation) { + return { + type: SET_FORM_PASSWORD_CONFIRMATION, + data: passwordConfirmation + }; +} diff --git a/src/actions/auth/saga.actions.js b/src/actions/auth/saga.actions.js new file mode 100644 index 0000000..c887c72 --- /dev/null +++ b/src/actions/auth/saga.actions.js @@ -0,0 +1,10 @@ +import { + SEND_REGISTER_REQUEST +} from "../../constants/auth.constants"; + +export function sendRegisterRequest(postbody) { + return { + type: SEND_REGISTER_REQUEST, + data: postbody + } +} diff --git a/src/actions/common.actions.js b/src/actions/common.actions.js new file mode 100644 index 0000000..99e9015 --- /dev/null +++ b/src/actions/common.actions.js @@ -0,0 +1,13 @@ +/** + * Given an exception return the list of errors, the singular error, or generic error + * @param {object|string} exception - axios returned exception + */ +export function parseError(exception) { + let response = exception.response || {}; + let data = response.data || {}; + let err = "" + exception; + if (response.status) { + err = `${response.status} ${response.statusText}`; + } + return data || err +} diff --git a/src/api/auth.api.js b/src/api/auth.api.js new file mode 100644 index 0000000..ca11449 --- /dev/null +++ b/src/api/auth.api.js @@ -0,0 +1,17 @@ +import { post } from "./baseApi"; + +/** + * Function wrapping POST request for user registration + * @param {string} email - email of user to register + * @param {string} password1 - password of user to register + * @param {string} password2 - server side password confirmation + */ +export function registerUser(email, password1, password2) { + return post("/rest-auth/registration/", { + email, + password1, + password2 + }).then(response => { + return Promise.resolve(response); + }); +} diff --git a/src/api/baseApi.js b/src/api/baseApi.js new file mode 100644 index 0000000..1fe3634 --- /dev/null +++ b/src/api/baseApi.js @@ -0,0 +1,52 @@ +import axios from "axios"; + +const API_ENDPOINT = process.env.REACT_APP_API_ENDPOINT; + +// If testing, use localStorage polyfill, else use browser localStorage +const localStorage = global.process && process.env.NODE_ENV === "test" + ? // eslint-disable-next-line import/no-extraneous-dependencies + require("localStorage") + : global.window.localStorage; + +function headers() { + const token = localStorage.getItem("token") || ""; + + return { + Accept: "application/json", + "Content-Type": "application/json", + Authorization: `Token: ${token}` + }; +} + +const apiInstance = axios.create({ + baseURL: API_ENDPOINT, + timeout: 3000, +}); + +export function get(url, params = {}) { + return apiInstance + .get(url, {params, headers: headers()}) + .then(response => response.data) + .catch(error => Promise.reject(error)); +} + +export function post(url, data) { + return apiInstance + .post(url, data, {headers: headers()}) + .then(response => response.data) + .catch(error => Promise.reject(error)); +} + +export function patch(url, data) { + return apiInstance + .patch(url, data, {headers: headers()}) + .then(response => response.data) + .catch(error => Promise.reject(error)); +} + +export function del(url) { + return apiInstance + .delete(url, {headers: headers()}) + .then(response => response.data) + .catch(error => Promise.reject(error)); +} diff --git a/src/components/App.jsx b/src/components/App.jsx index 91c3a07..b5d96e6 100644 --- a/src/components/App.jsx +++ b/src/components/App.jsx @@ -1,23 +1,32 @@ import React, { Component } from "react"; import { Route, Switch } from "react-router-dom"; +import Login from "./Auth/Login"; +import Register from "./Auth/Register"; +import About from "./Static/About"; +import Footer from "./Static/Footer"; +import Home from "./Static/Home"; +import NoMatch from "./Static/NoMatch"; import Navbar from "./Navbar"; -import Footer from "./Footer"; -import Home from "./Home"; -import About from "./About"; -import Topics from "./Topics"; class App extends Component { render() { + const footSmash = { + display: "flex", + minHeight: "calc(100vh - 1px)", + flexDirection: "column" + }; return (
-
+
- + + +