refactor: Move the LevelList to Redux
This commit is contained in:
parent
2dc9aa3a53
commit
8a6ec0dbc7
@ -134,3 +134,11 @@ export function setReview(current: IReviewCard, meta: IReviewMetadata) {
|
|||||||
meta,
|
meta,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const LEVELLIST_SET_LOADING = "LEVELLIST_SET_LOADING";
|
||||||
|
export function setLevelListLoading(state: boolean) {
|
||||||
|
return {
|
||||||
|
type: LEVELLIST_SET_LOADING,
|
||||||
|
state,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
@ -31,9 +31,12 @@ interface IProps {
|
|||||||
// TODO: Cache API-Calls
|
// TODO: Cache API-Calls
|
||||||
// TODO: When mounting without a login, check if the sessionToken is still valid
|
// TODO: When mounting without a login, check if the sessionToken is still valid
|
||||||
export default class Application extends React.Component<IProps> {
|
export default class Application extends React.Component<IProps> {
|
||||||
getLevels(): ILevel[] {
|
getLevels(): Promise<ILevel[]> {
|
||||||
console.log("STUB: Application::getLevels");
|
console.log("STUB: Application::getLevels");
|
||||||
|
|
||||||
|
return new Promise((res, rej) => {
|
||||||
// TODO: Actually fetch them from somewhere
|
// TODO: Actually fetch them from somewhere
|
||||||
|
setTimeout(() => {
|
||||||
const levels = [{
|
const levels = [{
|
||||||
name: "Der Bauer auf dem Feld",
|
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",
|
desc: "So fängt alles an: Du bist ein einfacher Bauer und musst dich die Karriereleiter mit deinen freshen Latein-Skills hinaufarbeiten",
|
||||||
@ -46,7 +49,10 @@ export default class Application extends React.Component<IProps> {
|
|||||||
done: false,
|
done: false,
|
||||||
}];
|
}];
|
||||||
|
|
||||||
return levels;
|
res(levels);
|
||||||
|
|
||||||
|
}, 2000);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getLastReview = (): IReviewMetadata => {
|
getLastReview = (): IReviewMetadata => {
|
||||||
@ -214,22 +220,6 @@ export default class Application extends React.Component<IProps> {
|
|||||||
return true;
|
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() {
|
render() {
|
||||||
return <BrowserRouter
|
return <BrowserRouter
|
||||||
basename="/app/">
|
basename="/app/">
|
||||||
@ -258,7 +248,8 @@ export default class Application extends React.Component<IProps> {
|
|||||||
<AuthRoute
|
<AuthRoute
|
||||||
isAuth={this.isAuthenticated}
|
isAuth={this.isAuthenticated}
|
||||||
path="/levelList"
|
path="/levelList"
|
||||||
component={() => <LevelListPage />} />
|
component={() => <LevelListPage
|
||||||
|
getLevels={this.getLevels} />} />
|
||||||
{/*We cannot use AuthRoute here, because match is undefined otherwise*/}
|
{/*We cannot use AuthRoute here, because match is undefined otherwise*/}
|
||||||
<Route
|
<Route
|
||||||
path="/level/:id"
|
path="/level/:id"
|
||||||
|
@ -1,13 +1,23 @@
|
|||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
|
|
||||||
|
import { setLevelListLoading, setLevels } from "../actions";
|
||||||
|
|
||||||
|
import { ILevel } from "../models/level";
|
||||||
|
|
||||||
import LevelListPage from "../pages/levelList";
|
import LevelListPage from "../pages/levelList";
|
||||||
|
|
||||||
const mapStateToProps = state => {
|
const mapStateToProps = state => {
|
||||||
return {
|
return {
|
||||||
levels: state.levels,
|
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,
|
const LevelListContainer = connect(mapStateToProps,
|
||||||
mapDispatchToProps)(LevelListPage);
|
mapDispatchToProps)(LevelListPage);
|
||||||
|
@ -3,7 +3,6 @@ import * as React from "react";
|
|||||||
import List from "@material-ui/core/List";
|
import List from "@material-ui/core/List";
|
||||||
import ListItem from "@material-ui/core/ListItem";
|
import ListItem from "@material-ui/core/ListItem";
|
||||||
import ListItemText from "@material-ui/core/ListItemText";
|
import ListItemText from "@material-ui/core/ListItemText";
|
||||||
import Grid from "@material-ui/core/Grid";
|
|
||||||
import Typography from "@material-ui/core/Typography";
|
import Typography from "@material-ui/core/Typography";
|
||||||
import Paper from "@material-ui/core/Paper";
|
import Paper from "@material-ui/core/Paper";
|
||||||
import Grid from "@material-ui/core/Grid";
|
import Grid from "@material-ui/core/Grid";
|
||||||
|
@ -6,21 +6,55 @@ import Button from "@material-ui/core/Button";
|
|||||||
import Card from '@material-ui/core/Card';
|
import Card from '@material-ui/core/Card';
|
||||||
import CardActions from '@material-ui/core/CardActions';
|
import CardActions from '@material-ui/core/CardActions';
|
||||||
import CardContent from '@material-ui/core/CardContent';
|
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 { Link } from "react-router-dom";
|
||||||
|
|
||||||
import { ILevel } from "../models/lesson";
|
import { ILevel } from "../models/level";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
|
getLevels: () => Promise<ILevel[]>;
|
||||||
|
|
||||||
|
setLevels: (levels: ILevel[]) => void;
|
||||||
|
setLoading: (state: boolean) => void;
|
||||||
|
loading: boolean;
|
||||||
levels: ILevel[];
|
levels: ILevel[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class Dashboard extends React.Component<{}> {
|
export default class Dashboard extends React.Component<IProps> {
|
||||||
constructor(props: any) {
|
componentDidMount() {
|
||||||
super(props);
|
this.props.setLoading(true);
|
||||||
|
|
||||||
|
// Fetch the levels
|
||||||
|
this.props.getLevels().then(res => {
|
||||||
|
this.props.setLevels(res);
|
||||||
|
this.props.setLoading(false);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
if (this.props.loading) {
|
||||||
|
return <div>
|
||||||
|
<Grid
|
||||||
|
container
|
||||||
|
spacing={0}
|
||||||
|
direction="column"
|
||||||
|
alignItems="center"
|
||||||
|
justify="center"
|
||||||
|
style={{ minHeight: '100vh' }}>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Paper className="paper">
|
||||||
|
<Grid container direction="column" spacing={8}>
|
||||||
|
<CircularProgress />
|
||||||
|
</Grid>
|
||||||
|
</Paper>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
const small = window.matchMedia("(max-width: 700px)").matches;
|
const small = window.matchMedia("(max-width: 700px)").matches;
|
||||||
const cName = small ? "lesson-card-xs" : "lesson-card-lg";
|
const cName = small ? "lesson-card-xs" : "lesson-card-lg";
|
||||||
|
|
||||||
|
@ -8,13 +8,14 @@ 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 { Redirect } from "react-router-dom";
|
import { withRouter } from "react-router-dom";
|
||||||
|
|
||||||
import { IUser } from "../models/user";
|
import { IUser } from "../models/user";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
login: (username: string, password: string) => Promise<IUser | {}>;
|
login: (username: string, password: string) => Promise<IUser | {}>;
|
||||||
authenticated: boolean;
|
authenticated: boolean;
|
||||||
|
history: any;
|
||||||
|
|
||||||
setLoading: (state: boolean) => void;
|
setLoading: (state: boolean) => void;
|
||||||
setSnackbar: (state: boolean, msg: string) => void;
|
setSnackbar: (state: boolean, msg: string) => void;
|
||||||
@ -22,15 +23,9 @@ interface IProps {
|
|||||||
snackOpen: boolean;
|
snackOpen: boolean;
|
||||||
snackMsg: string;
|
snackMsg: string;
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
* interface IState {
|
|
||||||
* loading: boolean;
|
|
||||||
*
|
|
||||||
* snack: string; // The message
|
|
||||||
* open: boolean;
|
|
||||||
* } */
|
|
||||||
|
|
||||||
export default class LoginPage extends React.Component<IProps> {
|
const LoginPageWithRouter = withRouter(
|
||||||
|
class LoginPage extends React.Component<IProps> {
|
||||||
private usernameRef: any = undefined;
|
private usernameRef: any = undefined;
|
||||||
private passwordRef: any = undefined;
|
private passwordRef: any = undefined;
|
||||||
|
|
||||||
@ -42,12 +37,20 @@ export default class LoginPage extends React.Component<IProps> {
|
|||||||
this.props.login(username, password).then((res: IUser) => {
|
this.props.login(username, password).then((res: IUser) => {
|
||||||
// Set the session key
|
// Set the session key
|
||||||
window.sessionStorage.setItem("sessionToken", res.sessionToken);
|
window.sessionStorage.setItem("sessionToken", res.sessionToken);
|
||||||
|
this.props.history.push("/dashboard");
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
this.props.setLoading(false);
|
this.props.setLoading(false);
|
||||||
this.props.setSnackbar(true, "Failed to log in");
|
this.props.setSnackbar(true, "Failed to log in");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
// If we're already authenticated, we can skip the login page
|
||||||
|
if (this.props.authenticated) {
|
||||||
|
this.props.history.push("/dashboard");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <div>
|
return <div>
|
||||||
<Grid
|
<Grid
|
||||||
@ -95,11 +98,8 @@ export default class LoginPage extends React.Component<IProps> {
|
|||||||
onClose={() => this.props.setSnackbar(false, "")}
|
onClose={() => this.props.setSnackbar(false, "")}
|
||||||
message={this.props.snackMsg}
|
message={this.props.snackMsg}
|
||||||
autoHideDuration={6000} />
|
autoHideDuration={6000} />
|
||||||
{
|
|
||||||
this.props.authenticated ? (
|
|
||||||
<Redirect to="/dashboard" />
|
|
||||||
) : undefined
|
|
||||||
}
|
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
);
|
||||||
|
export default LoginPageWithRouter;
|
||||||
|
@ -73,32 +73,25 @@ const ReviewPageWithRouter = withRouter(
|
|||||||
|
|
||||||
// Get the correct vocabulary
|
// Get the correct vocabulary
|
||||||
const { reviewType, vocabByLevel, levelId, vocabByQueue } = this.props;
|
const { reviewType, vocabByLevel, levelId, vocabByQueue } = this.props;
|
||||||
switch (reviewType) {
|
|
||||||
case ReviewType.LEVEL:
|
// Just to make TSC shut up
|
||||||
if (!vocabByLevel || !levelId) {
|
const noopPromise = () => {
|
||||||
alert("[ReviewPage::constructor] vocabByLevel or levelId undefined");
|
return new Promise<IVocab[]>((res, rej) => {
|
||||||
} else {
|
rej([]);
|
||||||
vocabByLevel(levelId).then(res => {
|
});
|
||||||
|
};
|
||||||
|
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;
|
this.vocab = res;
|
||||||
vocToQueue();
|
vocToQueue();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
|
||||||
case ReviewType.QUEUE:
|
|
||||||
if (!vocabByQueue) {
|
|
||||||
alert("[ReviewPage::constructor] vocabByQueue undefined");
|
|
||||||
} else {
|
|
||||||
vocabByQueue().then(res => {
|
|
||||||
this.vocab = res;
|
|
||||||
vocToQueue();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
increaseMeta = (correct: number, wrong: number): IReviewMetadata => {
|
increaseMeta = (correct: number, wrong: number): IReviewMetadata => {
|
||||||
const { metadata } = this;
|
const { metadata } = this;
|
||||||
|
|
||||||
|
@ -31,6 +31,10 @@ interface IState {
|
|||||||
loading: boolean;
|
loading: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
levelList: {
|
||||||
|
loading: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
review: {
|
review: {
|
||||||
current: IReviewCard;
|
current: IReviewCard;
|
||||||
|
|
||||||
@ -75,6 +79,10 @@ const initialState: IState = {
|
|||||||
loading: true,
|
loading: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
levelList: {
|
||||||
|
loading: true,
|
||||||
|
},
|
||||||
|
|
||||||
review: {
|
review: {
|
||||||
current: {} as IReviewCard,
|
current: {} as IReviewCard,
|
||||||
|
|
||||||
@ -191,8 +199,16 @@ export function LateinicusApp(state: IState = initialState, action: any) {
|
|||||||
loading: action.state,
|
loading: action.state,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
case Actions.LEVELLIST_SET_LOADING:
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
levelList: Object.assign({}, state.levelList, {
|
||||||
|
loading: action.state,
|
||||||
|
}),
|
||||||
|
});
|
||||||
default:
|
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);
|
console.log("Reducer not implemented:", action.type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user