From 7dc4f9231993896faa392e6e274e68c3e73fc0c8 Mon Sep 17 00:00:00 2001 From: Alexander Polynomdivision Date: Sun, 7 Oct 2018 18:40:48 +0200 Subject: [PATCH] feat: Add a registration page --- frontend/src/actions/index.ts | 17 +++ frontend/src/components/app.tsx | 5 +- frontend/src/containers/Register.ts | 25 +++++ frontend/src/pages/login.tsx | 7 +- frontend/src/pages/register.tsx | 160 ++++++++++++++++++++++++++++ frontend/src/pages/review.tsx | 11 ++ frontend/src/reducers/index.ts | 25 +++++ 7 files changed, 247 insertions(+), 3 deletions(-) create mode 100644 frontend/src/containers/Register.ts create mode 100644 frontend/src/pages/register.tsx diff --git a/frontend/src/actions/index.ts b/frontend/src/actions/index.ts index 779250a..ee10f9a 100644 --- a/frontend/src/actions/index.ts +++ b/frontend/src/actions/index.ts @@ -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, + }; +}; diff --git a/frontend/src/components/app.tsx b/frontend/src/components/app.tsx index 82b756f..7f476de 100644 --- a/frontend/src/components/app.tsx +++ b/frontend/src/components/app.tsx @@ -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 { return ; } }} /> + } /> { @@ -372,7 +376,6 @@ export default class Application extends React.Component { return ; }} /> { + 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; diff --git a/frontend/src/pages/login.tsx b/frontend/src/pages/login.tsx index 5781aad..17fd35b 100644 --- a/frontend/src/pages/login.tsx +++ b/frontend/src/pages/login.tsx @@ -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"; @@ -114,7 +114,10 @@ const LoginPageWithRouter = withRouter( className="login-btn" onClick={() => this.performLogin()}> Login - + + + Registrieren + { this.props.loading ? ( diff --git a/frontend/src/pages/register.tsx b/frontend/src/pages/register.tsx new file mode 100644 index 0000000..c3d6c45 --- /dev/null +++ b/frontend/src/pages/register.tsx @@ -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 { + 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
+ + + + Registrierung + + + this.usernameRef = node} /> + + + this.passwordRef = node} /> + + + this.passwordRepRef = node} /> + + + this.classRef = node} /> + + + + { + this.props.loading ? ( + + ) : undefined + } + + + + + + + this.props.setSnackbar(false, "")} + message={this.props.snackMsg} + autoHideDuration={6000} /> +
; + } + } +); +export default RegisterPageWithRouter; diff --git a/frontend/src/pages/review.tsx b/frontend/src/pages/review.tsx index 9e4119c..2c3afe4 100644 --- a/frontend/src/pages/review.tsx +++ b/frontend/src/pages/review.tsx @@ -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; diff --git a/frontend/src/reducers/index.ts b/frontend/src/reducers/index.ts index a4c92fd..10f4ba6 100644 --- a/frontend/src/reducers/index.ts +++ b/frontend/src/reducers/index.ts @@ -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