Finished functionally complete client registration workflow
This commit is contained in:
parent
aaad6ea3eb
commit
415aea74a3
|
@ -11,6 +11,15 @@ Generated using [create-react-app](https://github.com/facebookincubator/create-r
|
||||||
|
|
||||||
Now you can visit `localhost:3000` from your browser.
|
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
|
## Testing
|
||||||
|
|
||||||
To test the react app, call `yarn test`.
|
To test the react app, call `yarn test`.
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"axios": "^0.16.2",
|
||||||
|
"localStorage": "^1.0.3",
|
||||||
"react": "^15.6.1",
|
"react": "^15.6.1",
|
||||||
"react-dom": "^15.6.1",
|
"react-dom": "^15.6.1",
|
||||||
"react-redux": "^5.0.6",
|
"react-redux": "^5.0.6",
|
||||||
|
|
91
src/actions/auth/reducer.actions.js
Normal file
91
src/actions/auth/reducer.actions.js
Normal file
|
@ -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
|
||||||
|
};
|
||||||
|
}
|
10
src/actions/auth/saga.actions.js
Normal file
10
src/actions/auth/saga.actions.js
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import {
|
||||||
|
SEND_REGISTER_REQUEST
|
||||||
|
} from "../../constants/auth.constants";
|
||||||
|
|
||||||
|
export function sendRegisterRequest(postbody) {
|
||||||
|
return {
|
||||||
|
type: SEND_REGISTER_REQUEST,
|
||||||
|
data: postbody
|
||||||
|
}
|
||||||
|
}
|
13
src/actions/common.actions.js
Normal file
13
src/actions/common.actions.js
Normal file
|
@ -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
|
||||||
|
}
|
17
src/api/auth.api.js
Normal file
17
src/api/auth.api.js
Normal file
|
@ -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);
|
||||||
|
});
|
||||||
|
}
|
52
src/api/baseApi.js
Normal file
52
src/api/baseApi.js
Normal file
|
@ -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));
|
||||||
|
}
|
|
@ -1,23 +1,32 @@
|
||||||
import React, { Component } from "react";
|
import React, { Component } from "react";
|
||||||
import { Route, Switch } from "react-router-dom";
|
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 Navbar from "./Navbar";
|
||||||
import Footer from "./Footer";
|
|
||||||
import Home from "./Home";
|
|
||||||
import About from "./About";
|
|
||||||
import Topics from "./Topics";
|
|
||||||
|
|
||||||
class App extends Component {
|
class App extends Component {
|
||||||
render() {
|
render() {
|
||||||
|
const footSmash = {
|
||||||
|
display: "flex",
|
||||||
|
minHeight: "calc(100vh - 1px)",
|
||||||
|
flexDirection: "column"
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Navbar />
|
<Navbar />
|
||||||
<div style={{ display: "flex", minHeight: "calc(100vh - 1px)", flexDirection: "column" }}>
|
<div style={footSmash}>
|
||||||
<div style={{ flex: "1" }}>
|
<div style={{ flex: "1" }}>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route exact path="/" component={Home} />
|
<Route exact path="/" component={Home} />
|
||||||
<Route path="/about" component={About} />
|
<Route path="/about" component={About} />
|
||||||
<Route path="/topics" component={Topics} />
|
<Route path="/auth/login" component={Login} />
|
||||||
|
<Route path="/auth/register" component={Register} />
|
||||||
|
<Route component={NoMatch} />
|
||||||
</Switch>
|
</Switch>
|
||||||
</div>
|
</div>
|
||||||
<Footer />
|
<Footer />
|
||||||
|
|
16
src/components/Auth/Login.jsx
Normal file
16
src/components/Auth/Login.jsx
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import React, { Component } from "react";
|
||||||
|
import { Container } from "semantic-ui-react";
|
||||||
|
|
||||||
|
class Login extends Component {
|
||||||
|
render() {
|
||||||
|
return <LoginView />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const LoginView = () => (
|
||||||
|
<Container>
|
||||||
|
<p>Login</p>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Login;
|
135
src/components/Auth/Register.jsx
Normal file
135
src/components/Auth/Register.jsx
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
import React, { Component } from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { Container, Form, Header, Message } from "semantic-ui-react";
|
||||||
|
|
||||||
|
import {
|
||||||
|
clearAuthRequestError,
|
||||||
|
setFormEmail,
|
||||||
|
setFormPassword,
|
||||||
|
setFormPasswordConfirmation
|
||||||
|
} from "../../actions/auth/reducer.actions";
|
||||||
|
import { sendRegisterRequest } from "../../actions/auth/saga.actions";
|
||||||
|
import Error from "../Shared/Error";
|
||||||
|
|
||||||
|
class Register extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.props.dispatch(clearAuthRequestError());
|
||||||
|
}
|
||||||
|
|
||||||
|
changeEmail = event => {
|
||||||
|
this.props.dispatch(setFormEmail(event.target.value));
|
||||||
|
};
|
||||||
|
|
||||||
|
changePassword = event => {
|
||||||
|
this.props.dispatch(setFormPassword(event.target.value));
|
||||||
|
};
|
||||||
|
|
||||||
|
changePasswordConfirmation = event => {
|
||||||
|
this.props.dispatch(setFormPasswordConfirmation(event.target.value));
|
||||||
|
};
|
||||||
|
|
||||||
|
onSubmitRegistration = event => {
|
||||||
|
event.preventDefault();
|
||||||
|
const { dispatch, email, password, passwordConfirmation } = this.props;
|
||||||
|
dispatch(
|
||||||
|
sendRegisterRequest({
|
||||||
|
email,
|
||||||
|
password1: password,
|
||||||
|
password2: passwordConfirmation
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
isSendingAuthRequest,
|
||||||
|
authRequestError,
|
||||||
|
authRequestSuccess,
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
passwordConfirmation
|
||||||
|
} = this.props;
|
||||||
|
return (
|
||||||
|
<RegisterView
|
||||||
|
isSendingAuthRequest={isSendingAuthRequest}
|
||||||
|
authRequestError={authRequestError}
|
||||||
|
authRequestSuccess={authRequestSuccess}
|
||||||
|
email={email}
|
||||||
|
password={password}
|
||||||
|
passwordConfirmation={passwordConfirmation}
|
||||||
|
changeEmail={this.changeEmail}
|
||||||
|
changePassword={this.changePassword}
|
||||||
|
changePasswordConfirmation={this.changePasswordConfirmation}
|
||||||
|
onSubmitRegistration={this.onSubmitRegistration}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapStateToProps(state) {
|
||||||
|
return { ...state.auth };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Functional view component for Register logic
|
||||||
|
*/
|
||||||
|
const RegisterView = ({
|
||||||
|
isSendingAuthRequest,
|
||||||
|
authRequestError,
|
||||||
|
authRequestSuccess,
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
passwordConfirmation,
|
||||||
|
changeEmail,
|
||||||
|
changePassword,
|
||||||
|
changePasswordConfirmation,
|
||||||
|
onSubmitRegistration
|
||||||
|
}) => (
|
||||||
|
<Container>
|
||||||
|
<Header>Register</Header>
|
||||||
|
<Form
|
||||||
|
loading={isSendingAuthRequest}
|
||||||
|
onSubmit={onSubmitRegistration}
|
||||||
|
error={!!authRequestError}
|
||||||
|
success={!!authRequestSuccess}
|
||||||
|
>
|
||||||
|
<Form.Field>
|
||||||
|
<label>Email</label>
|
||||||
|
<input
|
||||||
|
placeholder="bob@gmail.com"
|
||||||
|
type="email"
|
||||||
|
value={email}
|
||||||
|
onChange={changeEmail}
|
||||||
|
/>
|
||||||
|
</Form.Field>
|
||||||
|
<Form.Field>
|
||||||
|
<label>Password</label>
|
||||||
|
<input
|
||||||
|
placeholder="••••••••"
|
||||||
|
type="password"
|
||||||
|
value={password}
|
||||||
|
onChange={changePassword}
|
||||||
|
/>
|
||||||
|
</Form.Field>
|
||||||
|
<Form.Field>
|
||||||
|
<label>Password Confirmation</label>
|
||||||
|
<input
|
||||||
|
placeholder="••••••••"
|
||||||
|
type="password"
|
||||||
|
value={passwordConfirmation}
|
||||||
|
onChange={changePasswordConfirmation}
|
||||||
|
/>
|
||||||
|
</Form.Field>
|
||||||
|
<Error header="Register failed!" error={authRequestError} />
|
||||||
|
<Message
|
||||||
|
success
|
||||||
|
header="Registration Sent"
|
||||||
|
content="A confirmation email has been sent to confirm your registration."
|
||||||
|
/>
|
||||||
|
<Form.Button>Submit</Form.Button>
|
||||||
|
</Form>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default connect(mapStateToProps)(Register);
|
|
@ -12,9 +12,14 @@ class Navbar extends Component {
|
||||||
<Menu.Item as={Link} to="/about">
|
<Menu.Item as={Link} to="/about">
|
||||||
About
|
About
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item as={Link} to="/topics">
|
<Menu.Menu position="right">
|
||||||
Topics
|
<Menu.Item as={Link} to="/auth/login">
|
||||||
</Menu.Item>
|
Login
|
||||||
|
</Menu.Item>
|
||||||
|
<Menu.Item as={Link} to="/auth/register">
|
||||||
|
Register
|
||||||
|
</Menu.Item>
|
||||||
|
</Menu.Menu>
|
||||||
</Menu>
|
</Menu>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
41
src/components/Shared/Error.jsx
Normal file
41
src/components/Shared/Error.jsx
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
import PropTypes from "prop-types";
|
||||||
|
import React from "react";
|
||||||
|
import { Message } from "semantic-ui-react";
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
error: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
||||||
|
header: PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultProps = {
|
||||||
|
error: "",
|
||||||
|
header: ""
|
||||||
|
};
|
||||||
|
|
||||||
|
const Error = ({ error, header }) => {
|
||||||
|
if (typeof error === "string") {
|
||||||
|
const hasError = !!error;
|
||||||
|
if (hasError) {
|
||||||
|
return <Message error={hasError} header={header} content={error} />;
|
||||||
|
}
|
||||||
|
} else if (typeof error === "object" && Object.keys(error).length > 0) {
|
||||||
|
const hasError = !!Object.keys(error);
|
||||||
|
if (hasError) {
|
||||||
|
return (
|
||||||
|
<Message
|
||||||
|
error={hasError}
|
||||||
|
header={header}
|
||||||
|
list={Object.keys(error).map(p => (
|
||||||
|
<Message.Item key={p}> {p}: {error[p]}</Message.Item>
|
||||||
|
))}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
Error.propTypes = propTypes;
|
||||||
|
Error.defaultProps = defaultProps;
|
||||||
|
|
||||||
|
export default Error;
|
13
src/components/Static/NoMatch.jsx
Normal file
13
src/components/Static/NoMatch.jsx
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import React from "react";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import { Container } from "semantic-ui-react";
|
||||||
|
|
||||||
|
const NoMatch = ({ location }) => (
|
||||||
|
<Container>
|
||||||
|
<h3>Page not found!</h3>
|
||||||
|
<p>No match found for <code>{location.pathname}</code></p>
|
||||||
|
<p><Link to="/">Go to the home page →</Link></p>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default NoMatch;
|
|
@ -1,41 +0,0 @@
|
||||||
import React from "react";
|
|
||||||
import { Route, Link } from "react-router-dom";
|
|
||||||
import { Container } from "semantic-ui-react";
|
|
||||||
|
|
||||||
const Topics = ({ match }) => (
|
|
||||||
<Container>
|
|
||||||
<h2>Topics</h2>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<Link to={`${match.url}/rendering`}>
|
|
||||||
Rendering with React
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Link to={`${match.url}/components`}>
|
|
||||||
Components
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Link to={`${match.url}/props-v-state`}>
|
|
||||||
Props v. State
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<Route path={`${match.url}/:topicId`} component={Topic} />
|
|
||||||
<Route
|
|
||||||
exact
|
|
||||||
path={match.url}
|
|
||||||
render={() => <h3>Please select a topic.</h3>}
|
|
||||||
/>
|
|
||||||
</Container>
|
|
||||||
);
|
|
||||||
|
|
||||||
const Topic = ({ match }) => (
|
|
||||||
<div>
|
|
||||||
<h3>{match.params.topicId}</h3>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default Topics;
|
|
13
src/constants/auth.constants.js
Normal file
13
src/constants/auth.constants.js
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
// Reducer Auth Action Constants
|
||||||
|
export const IS_SENDING_AUTH_REQUEST = "IS_SENDING_AUTH_REQUEST";
|
||||||
|
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_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";
|
||||||
|
|
||||||
|
// Saga Auth Action Constants
|
||||||
|
export const SEND_REGISTER_REQUEST = "SEND_REGISTER_REQUEST";
|
15
src/index.js
15
src/index.js
|
@ -1,26 +1,15 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import ReactDOM from "react-dom";
|
import ReactDOM from "react-dom";
|
||||||
import { BrowserRouter } from "react-router-dom";
|
import { BrowserRouter } from "react-router-dom";
|
||||||
import { createStore, applyMiddleware } from "redux";
|
|
||||||
import createSagaMiddleware from "redux-saga";
|
|
||||||
import { Provider } from "react-redux";
|
import { Provider } from "react-redux";
|
||||||
import { createLogger } from "redux-logger";
|
|
||||||
|
|
||||||
import rootReducer from "./reducers";
|
import configureStore from "./store";
|
||||||
import rootSaga from "./sagas";
|
|
||||||
import App from "./components/App";
|
import App from "./components/App";
|
||||||
import { unregister } from "./registerServiceWorker";
|
import { unregister } from "./registerServiceWorker";
|
||||||
|
|
||||||
const sagaMiddleware = createSagaMiddleware();
|
const store = configureStore();
|
||||||
const debugLogger = createLogger();
|
|
||||||
const store = createStore(
|
|
||||||
rootReducer,
|
|
||||||
applyMiddleware(debugLogger, sagaMiddleware)
|
|
||||||
);
|
|
||||||
const supportsHistory = "pushState" in window.history;
|
const supportsHistory = "pushState" in window.history;
|
||||||
|
|
||||||
sagaMiddleware.run(rootSaga);
|
|
||||||
|
|
||||||
const rootElement = document.getElementById("root");
|
const rootElement = document.getElementById("root");
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
|
|
|
@ -1,7 +1,72 @@
|
||||||
const initialState = {};
|
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";
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
isSendingAuthRequest: false,
|
||||||
|
authRequestError: "",
|
||||||
|
authRequestSuccess: "",
|
||||||
|
currentUser: {},
|
||||||
|
email: "",
|
||||||
|
password: "",
|
||||||
|
passwordConfirmation: ""
|
||||||
|
};
|
||||||
|
|
||||||
function authReducer(state = initialState, action) {
|
function authReducer(state = initialState, action) {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
|
case IS_SENDING_AUTH_REQUEST:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
isSendingAuthRequest: action.data
|
||||||
|
};
|
||||||
|
case SET_AUTH_REQUEST_ERROR:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
authRequestError: action.data
|
||||||
|
};
|
||||||
|
case CLEAR_AUTH_REQUEST_ERROR:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
authRequestError: ""
|
||||||
|
};
|
||||||
|
case SET_AUTH_REQUEST_SUCCESS:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
authRequestSuccess: action.data
|
||||||
|
};
|
||||||
|
case CLEAR_AUTH_REQUEST_SUCCESS:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
authRequestSuccess: ""
|
||||||
|
};
|
||||||
|
case SET_SELF_USER:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
currentUser: action.data
|
||||||
|
};
|
||||||
|
case SET_FORM_EMAIL:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
email: action.data
|
||||||
|
};
|
||||||
|
case SET_FORM_PASSWORD:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
password: action.data
|
||||||
|
};
|
||||||
|
case SET_FORM_PASSWORD_CONFIRMATION:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
passwordConfirmation: action.data
|
||||||
|
};
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
39
src/sagas/auth.sagas.js
Normal file
39
src/sagas/auth.sagas.js
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import { effects } from "redux-saga";
|
||||||
|
import {
|
||||||
|
isSendingAuthRequest,
|
||||||
|
setAuthRequestError,
|
||||||
|
setAuthRequestSuccess,
|
||||||
|
clearAuthRequestError,
|
||||||
|
setFormEmail,
|
||||||
|
setFormPassword,
|
||||||
|
setFormPasswordConfirmation
|
||||||
|
} from "../actions/auth/reducer.actions";
|
||||||
|
import { registerUser } 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;
|
||||||
|
try {
|
||||||
|
return yield effects.call(registerUser, email, password1, password2);
|
||||||
|
} catch (exception) {
|
||||||
|
yield effects.put(setAuthRequestError(exception));
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
yield effects.put(isSendingAuthRequest(false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function* registerUserFlow(request) {
|
||||||
|
const wasSucessful = yield effects.call(registerUserCall, request.data);
|
||||||
|
if (wasSucessful) {
|
||||||
|
yield effects.put(setAuthRequestSuccess(wasSucessful));
|
||||||
|
yield effects.put(clearAuthRequestError());
|
||||||
|
yield effects.put(setFormEmail(""));
|
||||||
|
yield effects.put(setFormPassword(""));
|
||||||
|
yield effects.put(setFormPasswordConfirmation(""));
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,7 @@
|
||||||
|
import { takeLatest } from "redux-saga/effects";
|
||||||
|
import { SEND_REGISTER_REQUEST } from "../constants/auth.constants";
|
||||||
|
import { registerUserFlow } from "./auth.sagas";
|
||||||
|
|
||||||
export default function* rootSaga() {
|
export default function* rootSaga() {
|
||||||
|
yield takeLatest(SEND_REGISTER_REQUEST, registerUserFlow);
|
||||||
}
|
}
|
||||||
|
|
22
src/store/index.js
Normal file
22
src/store/index.js
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import { createStore, applyMiddleware, compose } from "redux";
|
||||||
|
import createSagaMiddleware from "redux-saga";
|
||||||
|
import { createLogger } from "redux-logger";
|
||||||
|
import rootReducer from "../reducers";
|
||||||
|
import rootSaga from "../sagas";
|
||||||
|
|
||||||
|
export default function configureStore(initialState = {}) {
|
||||||
|
const sagaMiddleware = createSagaMiddleware();
|
||||||
|
const middlewares = [sagaMiddleware];
|
||||||
|
if (process.env.NODE_ENV === "development" && process.env.REACT_APP_REDUX_LOGGING) {
|
||||||
|
middlewares.push(createLogger());
|
||||||
|
}
|
||||||
|
|
||||||
|
const enhancers = [applyMiddleware(...middlewares)];
|
||||||
|
const store = createStore(rootReducer, initialState, compose(...enhancers));
|
||||||
|
|
||||||
|
// Extensions
|
||||||
|
store.asyncReducers = {}; // Async reducer registry
|
||||||
|
sagaMiddleware.run(rootSaga);
|
||||||
|
|
||||||
|
return store;
|
||||||
|
}
|
19
yarn.lock
19
yarn.lock
|
@ -316,6 +316,13 @@ aws4@^1.2.1:
|
||||||
version "1.6.0"
|
version "1.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e"
|
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e"
|
||||||
|
|
||||||
|
axios@^0.16.2:
|
||||||
|
version "0.16.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/axios/-/axios-0.16.2.tgz#ba4f92f17167dfbab40983785454b9ac149c3c6d"
|
||||||
|
dependencies:
|
||||||
|
follow-redirects "^1.2.3"
|
||||||
|
is-buffer "^1.1.5"
|
||||||
|
|
||||||
axobject-query@^0.1.0:
|
axobject-query@^0.1.0:
|
||||||
version "0.1.0"
|
version "0.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-0.1.0.tgz#62f59dbc59c9f9242759ca349960e7a2fe3c36c0"
|
resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-0.1.0.tgz#62f59dbc59c9f9242759ca349960e7a2fe3c36c0"
|
||||||
|
@ -1757,7 +1764,7 @@ date-now@^0.1.4:
|
||||||
version "0.1.4"
|
version "0.1.4"
|
||||||
resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b"
|
resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b"
|
||||||
|
|
||||||
debug@2.6.8, debug@^2.1.1, debug@^2.2.0, debug@^2.6.0, debug@^2.6.3, debug@^2.6.6, debug@^2.6.8:
|
debug@2.6.8, debug@^2.1.1, debug@^2.2.0, debug@^2.4.5, debug@^2.6.0, debug@^2.6.3, debug@^2.6.6, debug@^2.6.8:
|
||||||
version "2.6.8"
|
version "2.6.8"
|
||||||
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc"
|
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -2592,6 +2599,12 @@ flatten@^1.0.2:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782"
|
resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782"
|
||||||
|
|
||||||
|
follow-redirects@^1.2.3:
|
||||||
|
version "1.2.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.2.4.tgz#355e8f4d16876b43f577b0d5ce2668b9723214ea"
|
||||||
|
dependencies:
|
||||||
|
debug "^2.4.5"
|
||||||
|
|
||||||
for-in@^1.0.1:
|
for-in@^1.0.1:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
|
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
|
||||||
|
@ -3881,6 +3894,10 @@ loader-utils@^1.0.2, loader-utils@^1.1.0:
|
||||||
emojis-list "^2.0.0"
|
emojis-list "^2.0.0"
|
||||||
json5 "^0.5.0"
|
json5 "^0.5.0"
|
||||||
|
|
||||||
|
localStorage@^1.0.3:
|
||||||
|
version "1.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/localStorage/-/localStorage-1.0.3.tgz#e6b89a57bb760a156a38cc87e0f2550f6ed413d8"
|
||||||
|
|
||||||
locate-path@^2.0.0:
|
locate-path@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e"
|
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e"
|
||||||
|
|
Loading…
Reference in New Issue
Block a user