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,
|
||||
};
|
||||
};
|
||||
|
||||
export const LEVELLIST_SET_LOADING = "LEVELLIST_SET_LOADING";
|
||||
export function setLevelListLoading(state: boolean) {
|
||||
return {
|
||||
type: LEVELLIST_SET_LOADING,
|
||||
state,
|
||||
};
|
||||
}
|
||||
|
@ -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<IProps> {
|
||||
getLevels(): ILevel[] {
|
||||
getLevels(): Promise<ILevel[]> {
|
||||
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<IProps> {
|
||||
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 <BrowserRouter
|
||||
basename="/app/">
|
||||
@ -258,7 +248,8 @@ export default class Application extends React.Component<IProps> {
|
||||
<AuthRoute
|
||||
isAuth={this.isAuthenticated}
|
||||
path="/levelList"
|
||||
component={() => <LevelListPage />} />
|
||||
component={() => <LevelListPage
|
||||
getLevels={this.getLevels} />} />
|
||||
{/*We cannot use AuthRoute here, because match is undefined otherwise*/}
|
||||
<Route
|
||||
path="/level/:id"
|
||||
|
@ -1,13 +1,23 @@
|
||||
import { connect } from "react-redux";
|
||||
|
||||
import { setLevelListLoading, setLevels } from "../actions";
|
||||
|
||||
import { ILevel } from "../models/level";
|
||||
|
||||
import LevelListPage from "../pages/levelList";
|
||||
|
||||
const mapStateToProps = state => {
|
||||
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);
|
||||
|
@ -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";
|
||||
|
@ -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<ILevel[]>;
|
||||
|
||||
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<IProps> {
|
||||
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 <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 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 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<IUser | {}>;
|
||||
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<IProps> {
|
||||
private usernameRef: any = undefined;
|
||||
private passwordRef: any = undefined;
|
||||
const LoginPageWithRouter = withRouter(
|
||||
class LoginPage extends React.Component<IProps> {
|
||||
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 <div>
|
||||
<Grid
|
||||
container
|
||||
spacing={0}
|
||||
direction="column"
|
||||
alignItems="center"
|
||||
justify="center"
|
||||
style={{ minHeight: '100vh' }}>
|
||||
<Grid item xs={12}>
|
||||
<Paper className="paper">
|
||||
<Typography variant="title">Login</Typography>
|
||||
<Grid container direction="column" spacing={8}>
|
||||
<Grid item>
|
||||
<TextField
|
||||
label="Username"
|
||||
inputRef={node => this.usernameRef = node} />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<TextField
|
||||
label="Passwort"
|
||||
type="password"
|
||||
inputRef={node => this.passwordRef = node} />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
className="login-btn"
|
||||
onClick={() => this.performLogin()}>
|
||||
Login
|
||||
</Button>
|
||||
{
|
||||
this.props.loading ? (
|
||||
<LinearProgress />
|
||||
) : undefined
|
||||
}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Paper>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Snackbar
|
||||
open={this.props.snackOpen}
|
||||
onClose={() => this.props.setSnackbar(false, "")}
|
||||
message={this.props.snackMsg}
|
||||
autoHideDuration={6000} />
|
||||
{
|
||||
this.props.authenticated ? (
|
||||
<Redirect to="/dashboard" />
|
||||
) : undefined
|
||||
componentDidMount() {
|
||||
// If we're already authenticated, we can skip the login page
|
||||
if (this.props.authenticated) {
|
||||
this.props.history.push("/dashboard");
|
||||
}
|
||||
</div>;
|
||||
}
|
||||
|
||||
render() {
|
||||
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">Login</Typography>
|
||||
<Grid container direction="column" spacing={8}>
|
||||
<Grid item>
|
||||
<TextField
|
||||
label="Username"
|
||||
inputRef={node => this.usernameRef = node} />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<TextField
|
||||
label="Passwort"
|
||||
type="password"
|
||||
inputRef={node => this.passwordRef = node} />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
className="login-btn"
|
||||
onClick={() => this.performLogin()}>
|
||||
Login
|
||||
</Button>
|
||||
{
|
||||
this.props.loading ? (
|
||||
<LinearProgress />
|
||||
) : undefined
|
||||
}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Paper>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Snackbar
|
||||
open={this.props.snackOpen}
|
||||
onClose={() => this.props.setSnackbar(false, "")}
|
||||
message={this.props.snackMsg}
|
||||
autoHideDuration={6000} />
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
};
|
||||
);
|
||||
export default LoginPageWithRouter;
|
||||
|
@ -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<IVocab[]>((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 => {
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user