feat: Add a registration page
This commit is contained in:
parent
4278751837
commit
7dc4f92319
@ -208,3 +208,20 @@ export function setReviewHelp(state: boolean) {
|
|||||||
state,
|
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,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
@ -12,6 +12,7 @@ import LevelPage from "../containers/LevelPage";
|
|||||||
import ReviewPage from "../containers/Review";
|
import ReviewPage from "../containers/Review";
|
||||||
import SummaryPage from "../containers/SummaryPage";
|
import SummaryPage from "../containers/SummaryPage";
|
||||||
import WelcomePage from "../pages/intro";
|
import WelcomePage from "../pages/intro";
|
||||||
|
import RegisterPage from "../containers/Register";
|
||||||
|
|
||||||
import Drawer from "../containers/Drawer";
|
import Drawer from "../containers/Drawer";
|
||||||
|
|
||||||
@ -351,6 +352,9 @@ export default class Application extends React.Component<IProps> {
|
|||||||
return <Redirect to="/login" />;
|
return <Redirect to="/login" />;
|
||||||
}
|
}
|
||||||
}} />
|
}} />
|
||||||
|
<Route
|
||||||
|
path="/register"
|
||||||
|
component={() => <RegisterPage />} />
|
||||||
<Route
|
<Route
|
||||||
path="/review/level/:id"
|
path="/review/level/:id"
|
||||||
component={({ match }) => {
|
component={({ match }) => {
|
||||||
@ -372,7 +376,6 @@ export default class Application extends React.Component<IProps> {
|
|||||||
return <ReviewPage
|
return <ReviewPage
|
||||||
reviewType={ReviewType.QUEUE}
|
reviewType={ReviewType.QUEUE}
|
||||||
vocabByQueue={this.getReviewQueue}
|
vocabByQueue={this.getReviewQueue}
|
||||||
drawerButtonState={this.drawerButtonState}
|
|
||||||
setLastReview={this.setLastReview} />;
|
setLastReview={this.setLastReview} />;
|
||||||
}} />
|
}} />
|
||||||
<AuthRoute
|
<AuthRoute
|
||||||
|
25
frontend/src/containers/Register.ts
Normal file
25
frontend/src/containers/Register.ts
Normal 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;
|
@ -8,7 +8,7 @@ import Button from "@material-ui/core/Button";
|
|||||||
import LinearProgress from "@material-ui/core/LinearProgress";
|
import LinearProgress from "@material-ui/core/LinearProgress";
|
||||||
import Snackbar from "@material-ui/core/Snackbar";
|
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 { IUser } from "../models/user";
|
||||||
import { IResponse } from "../models/server";
|
import { IResponse } from "../models/server";
|
||||||
@ -114,7 +114,10 @@ const LoginPageWithRouter = withRouter(
|
|||||||
className="login-btn"
|
className="login-btn"
|
||||||
onClick={() => this.performLogin()}>
|
onClick={() => this.performLogin()}>
|
||||||
Login
|
Login
|
||||||
</Button>
|
</Button>
|
||||||
|
<Link to="/register">
|
||||||
|
Registrieren
|
||||||
|
</Link>
|
||||||
{
|
{
|
||||||
this.props.loading ? (
|
this.props.loading ? (
|
||||||
<LinearProgress />
|
<LinearProgress />
|
||||||
|
160
frontend/src/pages/register.tsx
Normal file
160
frontend/src/pages/register.tsx
Normal 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;
|
@ -103,6 +103,17 @@ const ReviewPageWithRouter = withRouter(
|
|||||||
}[reviewType];
|
}[reviewType];
|
||||||
|
|
||||||
getVocab().then((res: IVocab[]) => {
|
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
|
// Stop the loading
|
||||||
this.props.setLoading(false);
|
this.props.setLoading(false);
|
||||||
this.vocab = res;
|
this.vocab = res;
|
||||||
|
@ -55,6 +55,12 @@ interface IState {
|
|||||||
showHelp: boolean;
|
showHelp: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
register: {
|
||||||
|
loading: boolean;
|
||||||
|
snackMsg: string;
|
||||||
|
snackOpen: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
topTen: ILearner[];
|
topTen: ILearner[];
|
||||||
|
|
||||||
nextLevel: ILevel;
|
nextLevel: ILevel;
|
||||||
@ -114,6 +120,12 @@ const initialState: IState = {
|
|||||||
showHelp: false,
|
showHelp: false,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
register: {
|
||||||
|
loading: false,
|
||||||
|
snackOpen: false,
|
||||||
|
snackMsg: "",
|
||||||
|
},
|
||||||
|
|
||||||
nextLevel: {} as ILevel,
|
nextLevel: {} as ILevel,
|
||||||
lastReview: {
|
lastReview: {
|
||||||
correct: 0,
|
correct: 0,
|
||||||
@ -264,6 +276,19 @@ export function LateinicusApp(state: IState = initialState, action: any) {
|
|||||||
showHelp: action.state,
|
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:
|
default:
|
||||||
// Ignore the initialization call to the reducer. By that we can
|
// Ignore the initialization call to the reducer. By that we can
|
||||||
// catch all actions that are not implemented
|
// catch all actions that are not implemented
|
||||||
|
Reference in New Issue
Block a user