Finished functionally complete client registration workflow
This commit is contained in:
@@ -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 (
|
||||
<div>
|
||||
<Navbar />
|
||||
<div style={{ display: "flex", minHeight: "calc(100vh - 1px)", flexDirection: "column" }}>
|
||||
<div style={footSmash}>
|
||||
<div style={{ flex: "1" }}>
|
||||
<Switch>
|
||||
<Route exact path="/" component={Home} />
|
||||
<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>
|
||||
</div>
|
||||
<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">
|
||||
About
|
||||
</Menu.Item>
|
||||
<Menu.Item as={Link} to="/topics">
|
||||
Topics
|
||||
</Menu.Item>
|
||||
<Menu.Menu position="right">
|
||||
<Menu.Item as={Link} to="/auth/login">
|
||||
Login
|
||||
</Menu.Item>
|
||||
<Menu.Item as={Link} to="/auth/register">
|
||||
Register
|
||||
</Menu.Item>
|
||||
</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;
|
Reference in New Issue
Block a user