refactor: Move the LevelList to Redux

This commit is contained in:
Alexander Polynomdivision 2018-09-19 18:00:38 +02:00
parent 2dc9aa3a53
commit 8a6ec0dbc7
8 changed files with 188 additions and 137 deletions

View File

@ -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,
};
}

View File

@ -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"

View File

@ -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);

View File

@ -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";

View File

@ -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";

View File

@ -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;

View File

@ -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 => {

View File

@ -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);
}