diff --git a/src/actions/index.ts b/src/actions/index.ts index e2a181a..c0a01cf 100644 --- a/src/actions/index.ts +++ b/src/actions/index.ts @@ -134,3 +134,11 @@ export function setReview(current: IReviewCard, meta: IReviewMetadata) { meta, }; }; + +export const LEVELLIST_SET_LOADING = "LEVELLIST_SET_LOADING"; +export function setLevelListLoading(state: boolean) { + return { + type: LEVELLIST_SET_LOADING, + state, + }; +} diff --git a/src/components/app.tsx b/src/components/app.tsx index 9b149ae..28c1120 100644 --- a/src/components/app.tsx +++ b/src/components/app.tsx @@ -31,22 +31,28 @@ interface IProps { // TODO: Cache API-Calls // TODO: When mounting without a login, check if the sessionToken is still valid export default class Application extends React.Component { - getLevels(): ILevel[] { + getLevels(): Promise { console.log("STUB: Application::getLevels"); - // TODO: Actually fetch them from somewhere - const levels = [{ - name: "Der Bauer auf dem Feld", - desc: "So fängt alles an: Du bist ein einfacher Bauer und musst dich die Karriereleiter mit deinen freshen Latein-Skills hinaufarbeiten", - level: 1, - done: true, - }, { - name: "???", - desc: "Warum schreibe ich überhaupt was?dsd dddddddddddddddddddddd", - level: 2, - done: false, - }]; - return levels; + return new Promise((res, rej) => { + // TODO: Actually fetch them from somewhere + setTimeout(() => { + const levels = [{ + name: "Der Bauer auf dem Feld", + desc: "So fängt alles an: Du bist ein einfacher Bauer und musst dich die Karriereleiter mit deinen freshen Latein-Skills hinaufarbeiten", + level: 1, + done: true, + }, { + name: "???", + desc: "Warum schreibe ich überhaupt was?dsd dddddddddddddddddddddd", + level: 2, + done: false, + }]; + + res(levels); + + }, 2000); + }); } getLastReview = (): IReviewMetadata => { @@ -214,22 +220,6 @@ export default class Application extends React.Component { return true; } - closeDrawer = () => { - this.setState({ - drawerOpen: false, - }); - } - - drawerButtonState = (show: boolean) => { - // This is required as we would otherwise try to continously update - // the state, resulting in an infinte loop - if (show !== this.state.showDrawerButton) { - this.setState({ - showDrawerButton: show, - }); - } - } - render() { return @@ -258,7 +248,8 @@ export default class Application extends React.Component { } /> + component={() => } /> {/*We cannot use AuthRoute here, because match is undefined otherwise*/} { return { levels: state.levels, + loading: state.levelList.loading, + }; +}; +const mapDispatchToProps = dispatch => { + return { + setLoading: (state: boolean) => dispatch(setLevelListLoading(state)), + setLevels: (levels: ILevel[]) => dispatch(setLevels(levels)), }; }; -const mapDispatchToProps = dispatch => { return {} }; const LevelListContainer = connect(mapStateToProps, mapDispatchToProps)(LevelListPage); diff --git a/src/pages/level.tsx b/src/pages/level.tsx index 7f800fc..d48217d 100644 --- a/src/pages/level.tsx +++ b/src/pages/level.tsx @@ -3,7 +3,6 @@ import * as React from "react"; import List from "@material-ui/core/List"; import ListItem from "@material-ui/core/ListItem"; import ListItemText from "@material-ui/core/ListItemText"; -import Grid from "@material-ui/core/Grid"; import Typography from "@material-ui/core/Typography"; import Paper from "@material-ui/core/Paper"; import Grid from "@material-ui/core/Grid"; diff --git a/src/pages/levelList.tsx b/src/pages/levelList.tsx index e444051..4363240 100644 --- a/src/pages/levelList.tsx +++ b/src/pages/levelList.tsx @@ -6,21 +6,55 @@ import Button from "@material-ui/core/Button"; import Card from '@material-ui/core/Card'; import CardActions from '@material-ui/core/CardActions'; import CardContent from '@material-ui/core/CardContent'; +import Paper from "@material-ui/core/Paper"; +import CircularProgress from "@material-ui/core/CircularProgress"; import { Link } from "react-router-dom"; -import { ILevel } from "../models/lesson"; +import { ILevel } from "../models/level"; interface IProps { + getLevels: () => Promise; + + setLevels: (levels: ILevel[]) => void; + setLoading: (state: boolean) => void; + loading: boolean; levels: ILevel[]; } -export default class Dashboard extends React.Component<{}> { - constructor(props: any) { - super(props); +export default class Dashboard extends React.Component { + componentDidMount() { + this.props.setLoading(true); + + // Fetch the levels + this.props.getLevels().then(res => { + this.props.setLevels(res); + this.props.setLoading(false); + }); } render() { + if (this.props.loading) { + return
+ + + + + + + + + + +
; + } + const small = window.matchMedia("(max-width: 700px)").matches; const cName = small ? "lesson-card-xs" : "lesson-card-lg"; diff --git a/src/pages/login.tsx b/src/pages/login.tsx index 2891ce1..603ba04 100644 --- a/src/pages/login.tsx +++ b/src/pages/login.tsx @@ -8,13 +8,14 @@ import Button from "@material-ui/core/Button"; import LinearProgress from "@material-ui/core/LinearProgress"; import Snackbar from "@material-ui/core/Snackbar"; -import { Redirect } from "react-router-dom"; +import { withRouter } from "react-router-dom"; import { IUser } from "../models/user"; interface IProps { login: (username: string, password: string) => Promise; authenticated: boolean; + history: any; setLoading: (state: boolean) => void; setSnackbar: (state: boolean, msg: string) => void; @@ -22,84 +23,83 @@ interface IProps { snackOpen: boolean; snackMsg: string; } -/* - * interface IState { - * loading: boolean; - * - * snack: string; // The message - * open: boolean; - * } */ -export default class LoginPage extends React.Component { - private usernameRef: any = undefined; - private passwordRef: any = undefined; +const LoginPageWithRouter = withRouter( + class LoginPage extends React.Component { + private usernameRef: any = undefined; + private passwordRef: any = undefined; - performLogin = () => { - this.props.setLoading(true); + performLogin = () => { + this.props.setLoading(true); - const username = this.usernameRef.value || ""; - const password = this.passwordRef.value || ""; - this.props.login(username, password).then((res: IUser) => { - // Set the session key - window.sessionStorage.setItem("sessionToken", res.sessionToken); - }, (err) => { - this.props.setLoading(false); - this.props.setSnackbar(true, "Failed to log in"); - }); - } + const username = this.usernameRef.value || ""; + const password = this.passwordRef.value || ""; + this.props.login(username, password).then((res: IUser) => { + // Set the session key + window.sessionStorage.setItem("sessionToken", res.sessionToken); + this.props.history.push("/dashboard"); + }, (err) => { + this.props.setLoading(false); + this.props.setSnackbar(true, "Failed to log in"); + }); + } - render() { - return
- - - - Login - - - this.usernameRef = node} /> - - - this.passwordRef = node} /> - - - - { - this.props.loading ? ( - - ) : undefined - } - - - - - - this.props.setSnackbar(false, "")} - message={this.props.snackMsg} - autoHideDuration={6000} /> - { - this.props.authenticated ? ( - - ) : undefined + componentDidMount() { + // If we're already authenticated, we can skip the login page + if (this.props.authenticated) { + this.props.history.push("/dashboard"); } -
; + } + + render() { + return
+ + + + Login + + + this.usernameRef = node} /> + + + this.passwordRef = node} /> + + + + { + this.props.loading ? ( + + ) : undefined + } + + + + + + this.props.setSnackbar(false, "")} + message={this.props.snackMsg} + autoHideDuration={6000} /> +
; + } } -}; +); +export default LoginPageWithRouter; diff --git a/src/pages/review.tsx b/src/pages/review.tsx index 8b11333..2d0d663 100644 --- a/src/pages/review.tsx +++ b/src/pages/review.tsx @@ -73,30 +73,23 @@ const ReviewPageWithRouter = withRouter( // Get the correct vocabulary const { reviewType, vocabByLevel, levelId, vocabByQueue } = this.props; - switch (reviewType) { - case ReviewType.LEVEL: - if (!vocabByLevel || !levelId) { - alert("[ReviewPage::constructor] vocabByLevel or levelId undefined"); - } else { - vocabByLevel(levelId).then(res => { - this.vocab = res; - vocToQueue(); - }); - } - break; - case ReviewType.QUEUE: - if (!vocabByQueue) { - alert("[ReviewPage::constructor] vocabByQueue undefined"); - } else { - vocabByQueue().then(res => { - this.vocab = res; - vocToQueue(); - }); - } - - break; - } + // Just to make TSC shut up + const noopPromise = () => { + return new Promise((res, rej) => { + rej([]); + }); + }; + const vocabByLevelW = vocabByLevel || noopPromise; + const vocabByQueueW = vocabByQueue || noopPromise; + const getVocab = { + [ReviewType.LEVEL]: () => vocabByLevelW(levelId), + [ReviewType.QUEUE]: () => vocabByQueueW(), + }[reviewType]; + getVocab().then((res: IVocab[]) => { + this.vocab = res; + vocToQueue(); + }); } increaseMeta = (correct: number, wrong: number): IReviewMetadata => { diff --git a/src/reducers/index.ts b/src/reducers/index.ts index 8f0b17b..76e8593 100644 --- a/src/reducers/index.ts +++ b/src/reducers/index.ts @@ -31,6 +31,10 @@ interface IState { loading: boolean; }; + levelList: { + loading: boolean; + }; + review: { current: IReviewCard; @@ -75,6 +79,10 @@ const initialState: IState = { loading: true, }, + levelList: { + loading: true, + }, + review: { current: {} as IReviewCard, @@ -191,8 +199,16 @@ export function LateinicusApp(state: IState = initialState, action: any) { loading: action.state, }), }); + case Actions.LEVELLIST_SET_LOADING: + return Object.assign({}, state, { + levelList: Object.assign({}, state.levelList, { + loading: action.state, + }), + }); default: - if (action.type) { + // Ignore the initialization call to the reducer. By that we can + // catch all actions that are not implemented + if (action.type && !action.type.startsWith("@@redux/INIT")) { console.log("Reducer not implemented:", action.type); }