feat: Add a registration page

This commit is contained in:
Alexander Polynomdivision 2018-10-07 18:40:48 +02:00
parent 4278751837
commit 7dc4f92319
7 changed files with 247 additions and 3 deletions

View File

@ -208,3 +208,20 @@ export function setReviewHelp(state: boolean) {
state,
};
};
export const REGISTER_SET_SNACKBAR = "REGISTER_SET_SNACKBAR";
export function setRegisterSnackbar(state: boolean, msg: string) {
return {
type: REGISTER_SET_SNACKBAR,
state,
msg,
};
};
export const REGISTER_SET_LOADING = "REGISTER_SET_LOADING";
export function setRegisterLoading(state: boolean) {
return {
type: REGISTER_SET_LOADING,
state,
};
};

View File

@ -12,6 +12,7 @@ import LevelPage from "../containers/LevelPage";
import ReviewPage from "../containers/Review";
import SummaryPage from "../containers/SummaryPage";
import WelcomePage from "../pages/intro";
import RegisterPage from "../containers/Register";
import Drawer from "../containers/Drawer";
@ -351,6 +352,9 @@ export default class Application extends React.Component<IProps> {
return <Redirect to="/login" />;
}
}} />
<Route
path="/register"
component={() => <RegisterPage />} />
<Route
path="/review/level/:id"
component={({ match }) => {
@ -372,7 +376,6 @@ export default class Application extends React.Component<IProps> {
return <ReviewPage
reviewType={ReviewType.QUEUE}
vocabByQueue={this.getReviewQueue}
drawerButtonState={this.drawerButtonState}
setLastReview={this.setLastReview} />;
}} />
<AuthRoute

View File

@ -0,0 +1,25 @@
import { connect } from "react-redux";
import {
setRegisterSnackbar, setRegisterLoading
} from "../actions";
import RegisterPage from "../pages/register";
const mapStateToProps = state => {
return {
loading: state.register.loading,
snackOpen: state.register.snackOpen,
snackMsg: state.register.snackMsg,
};
};
const mapDispatchToProps = dispatch => {
return {
setLoading: (state: boolean) => dispatch(setRegisterLoading(state)),
setSnackbar: (state: boolean, msg: string) => dispatch(setRegisterSnackbar(state, msg)),
}
};
const RegisterContainer = connect(mapStateToProps,
mapDispatchToProps)(RegisterPage);
export default RegisterContainer;

View File

@ -8,7 +8,7 @@ import Button from "@material-ui/core/Button";
import LinearProgress from "@material-ui/core/LinearProgress";
import Snackbar from "@material-ui/core/Snackbar";
import { withRouter } from "react-router-dom";
import { withRouter, Link } from "react-router-dom";
import { IUser } from "../models/user";
import { IResponse } from "../models/server";
@ -115,6 +115,9 @@ const LoginPageWithRouter = withRouter(
onClick={() => this.performLogin()}>
Login
</Button>
<Link to="/register">
Registrieren
</Link>
{
this.props.loading ? (
<LinearProgress />

View File

@ -0,0 +1,160 @@
import * as React from "react";
import Typography from "@material-ui/core/Typography";
import Paper from "@material-ui/core/Paper";
import TextField from "@material-ui/core/TextField";
import Grid from "@material-ui/core/Grid";
import Button from "@material-ui/core/Button";
import LinearProgress from "@material-ui/core/LinearProgress";
import Snackbar from "@material-ui/core/Snackbar";
import { withRouter } from "react-router-dom";
import { BACKEND_URL } from "../config";
interface IProps {
history: any;
setLoading: (state: boolean) => void;
setSnackbar: (state: boolean, msg: string) => void;
loading: boolean;
snackOpen: boolean;
snackMsg: string;
}
const RegisterPageWithRouter = withRouter(
class RegisterPage extends React.Component<IProps> {
private usernameRef: HTMLInputElement = {} as HTMLInputElement;
private passwordRef: HTMLInputElement = {} as HTMLInputElement;
private passwordRepRef: HTMLInputElement = {} as HTMLInputElement;
private classRef: HTMLInputElement = {} as HTMLInputElement;
setSnackbar = (state: boolean, msg: string) => {
this.props.setSnackbar(state, msg);
}
performRegister = () => {
// Check the passwords
const password = this.passwordRef.value;
const repeat = this.passwordRepRef.value;
const username = this.usernameRef.value;
const classId = this.classRef.value;
if (!password || !repeat || !username || !classId) {
this.setSnackbar(true, "Nicht alle Felder sind ausgefüllt");
return;
}
if (password.length < 6) {
this.setSnackbar(true, "Das Passwort ist zu kurz (< 6)");
return;
}
if (password !== repeat) {
this.setSnackbar(true, "Die Passwörter stimmen nicht überein");
return;
}
this.props.setLoading(true);
fetch(`${BACKEND_URL}/api/register`, {
headers: new Headers({
"Content-Type": "application/json",
}),
method: "POST",
body: JSON.stringify({
username,
password,
classId,
}),
}).then(resp => resp.json(), err => {
console.log("RegisterPage::performRegister: Error trying to decode data");
this.props.setLoading(false);
}).then(data => {
this.props.setLoading(false);
// Check the error code
if (data.error === "200") {
this.props.history.push("/login");
} else {
this.setSnackbar(true, "Ein Fehler ist aufgetreten");
console.log(data);
}
});
}
render() {
// Trigger a login when return is pressed
const onEnter = (event: any) => {
if (event.key === "Enter") {
this.performRegister();
}
};
return <div>
<Grid
container
spacing={0}
direction="column"
alignItems="center"
justify="center"
style={{ minHeight: '100vh' }}>
<Grid item xs={12}>
<Paper className="paper">
<Typography variant="title">Registrierung</Typography>
<Grid container direction="column" spacing={8}>
<Grid item>
<TextField
label="Username"
onKeyPress={onEnter}
inputRef={node => this.usernameRef = node} />
</Grid>
<Grid item>
<TextField
label="Passwort"
type="password"
onKeyPress={onEnter}
inputRef={node => this.passwordRef = node} />
</Grid>
<Grid item>
<TextField
label="Passwort wiederholen"
type="password"
onKeyPress={onEnter}
inputRef={node => this.passwordRepRef = node} />
</Grid>
<Grid item>
<TextField
label="Klassencode"
onKeyPress={onEnter}
inputRef={node => this.classRef = node} />
</Grid>
<Grid item>
{
this.props.loading ? (
<LinearProgress />
) : undefined
}
<Button
variant="contained"
color="primary"
className="login-btn"
onClick={() => this.performRegister()}>
Registrieren
</Button>
</Grid>
</Grid>
</Paper>
</Grid>
</Grid>
<Snackbar
open={this.props.snackOpen}
onClose={() => this.props.setSnackbar(false, "")}
message={this.props.snackMsg}
autoHideDuration={6000} />
</div>;
}
}
);
export default RegisterPageWithRouter;

View File

@ -103,6 +103,17 @@ const ReviewPageWithRouter = withRouter(
}[reviewType];
getVocab().then((res: IVocab[]) => {
// Check if we received any vocabulary
if (res.length === 0) {
// TODO: Replace with a snackbar
alert("Du hast noch keine Vokabeln in der Review Queue");
// Reset the button state
this.props.drawerButtonState(true);
this.props.history.push("/dashboard");
return;
}
// Stop the loading
this.props.setLoading(false);
this.vocab = res;

View File

@ -55,6 +55,12 @@ interface IState {
showHelp: boolean;
};
register: {
loading: boolean;
snackMsg: string;
snackOpen: boolean;
};
topTen: ILearner[];
nextLevel: ILevel;
@ -114,6 +120,12 @@ const initialState: IState = {
showHelp: false,
},
register: {
loading: false,
snackOpen: false,
snackMsg: "",
},
nextLevel: {} as ILevel,
lastReview: {
correct: 0,
@ -264,6 +276,19 @@ export function LateinicusApp(state: IState = initialState, action: any) {
showHelp: action.state,
}),
});
case Actions.REGISTER_SET_SNACKBAR:
return Object.assign({}, state, {
register: Object.assign({}, state.register, {
snackMsg: action.msg,
snackOpen: action.state,
}),
});
case Actions.REGISTER_SET_LOADING:
return Object.assign({}, state, {
register: Object.assign({}, state.register, {
loading: action.state,
}),
});
default:
// Ignore the initialization call to the reducer. By that we can
// catch all actions that are not implemented