This repository has been archived on 2022-03-12. You can view files and clone it, but cannot push or open issues or pull requests.
Lateinicus/frontend/src/components/app.tsx
2018-10-07 18:40:48 +02:00

392 lines
14 KiB
TypeScript

import * as React from "react";
import { BrowserRouter, Route, Redirect } from "react-router-dom";
import AuthRoute from "../security/AuthRoute";
import { setSessionToken, removeSessionToken, getSessionToken } from "../security/Token";
import Dashboard from "../containers/Dashboard";
import LoginPage from "../containers/LoginPage";
import LevelListPage from "../containers/LevelList";
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";
import { BACKEND_URL } from "../config";
import { ILevel } from "../models/level";
import { TopTen } from "../models/learner";
import { IVocab } from "../models/vocab";
import { IReviewMetadata, ReviewType } from "../models/review";
import { IUser } from "../models/user";
import { IResponse } from "../models/server";
interface IProps {
authenticated: boolean;
user: IUser;
setAuthenticated: (status: boolean) => void;
setDidLogin: (status: boolean) => void;
setUser: (user: IUser) => void;
setLastReview: (meta: IReviewMetadata) => void;
setUserScoreDelta: (delta: number) => void;
};
// TODO: Replace the sessionStorage with localStorage?
export default class Application extends React.Component<IProps> {
componentDidMount() {
// TODO: When asking the server if our session is still valid, a spinner
// should be shown
const token = getSessionToken(window);
if (token !== null && !this.props.authenticated) {
this.checkAuthStatus(token).then(user => {
this.props.setUser(user);
this.props.setAuthenticated(true);
}).catch(err => {
// The token is invalid, so we should remove it
removeSessionToken(window);
this.props.setAuthenticated(false);
});
}
}
checkAuthStatus = (token: string): Promise<IUser> => {
return new Promise((res, rej) => {
fetch(`${BACKEND_URL}/api/user/me`, {
headers: new Headers({
"Content-Type": "application/json",
"Token": token,
}),
}).then(resp => resp.json(), err => rej(err))
.then(data => {
if (data.error === "0") {
res(data.data);
} else {
rej(data);
}
});
});
}
getLevels = (): Promise<ILevel[]> => {
return new Promise((res, rej) => {
fetch(`${BACKEND_URL}/api/levels`, {
headers: new Headers({
"Content-Type": "application/json",
"Token": this.props.user.sessionToken,
}),
}).then(resp => resp.json(), err => rej(err))
.then(data => {
if (data.error === "0") {
res(data.data.levels);
} else {
rej(data);
}
});
});
}
getLastReview = (): Promise<IReviewMetadata> => {
return new Promise((res, rej) => {
fetch(`${BACKEND_URL}/api/user/lastReview`, {
headers: new Headers({
"Content-Type": "application/json",
"Token": this.props.user.sessionToken,
}),
}).then(resp => resp.json(), err => rej(err))
.then(data => {
if (data.error === "0") {
res(data.data);
} else {
rej(data);
}
});
});
}
// TODO: Type?
setLastReview = (meta: IReviewMetadata, sm2: any, delta: number) => {
// Update the state
this.props.setLastReview(meta);
this.props.setUserScoreDelta(delta);
// Tell the server about the last review
fetch(`${BACKEND_URL}/api/user/lastReview`, {
headers: new Headers({
"Content-Type": "application/json",
"Token": this.props.user.sessionToken,
}),
method: "POST",
body: JSON.stringify({
meta,
sm2,
delta,
}),
}).then(resp => resp.json(), err => {
console.log("Application::setLastReview: POSTing last results failed");
});
}
getReviewQueue = (): Promise<IVocab[]> => {
return new Promise((res, rej) => {
fetch(`${BACKEND_URL}/api/user/queue`, {
headers: new Headers({
"Content-Type": "application/json",
"Token": this.props.user.sessionToken,
}),
}).then(resp => resp.json(), err => rej(err))
.then(data => {
if (data.error === "0") {
res(data.data.queue);
} else {
rej(data);
}
});
});
}
getTopTenLearners = (): Promise<TopTen[]> => {
// TODO: Deprecate?
const id = this.props.user.classId;
return new Promise((res, rej) => {
fetch(`${BACKEND_URL}/api/class/${id}/topTen`, {
headers: new Headers({
"Content-Type": "application/json",
"Token": this.props.user.sessionToken,
}),
}).then(resp => resp.json(),
err => rej(err))
.then(data => {
if (data.error === "0") {
res(data.data.topTen);
} else {
rej(data);
}
});
});
}
getNextLevel = (): Promise<ILevel> => {
return new Promise((res, rej) => {
fetch(`${BACKEND_URL}/api/user/nextLevel`, {
headers: new Headers({
"Content-Type": "application/json",
"Token": this.props.user.sessionToken,
}),
}).then(resp => resp.json(),
err => rej(err))
.then(data => {
if (data.error === "0") {
res(data.data);
} else {
rej(data);
}
});
});
}
getLevelVocab = (id: number): Promise<IVocab[]> => {
return new Promise((res, rej) => {
fetch(`${BACKEND_URL}/api/level/${id}/vocab`, {
method: "GET",
headers: new Headers({
"Content-Type": "application/json",
"Token": this.props.user.sessionToken,
}),
}).then(data => data.json(), err => {
rej(err);
}).then((resp: IResponse) => {
if (resp.error === "0") {
res(resp.data.vocab);
} else {
rej(resp);
}
});
});
}
introDontShowAgain = (): void => {
// NOTE: This is not a promise, as we do not care about any response
// being sent, since we don't need to update any client-side
// state.
fetch(`${BACKEND_URL}/api/user/showWelcome`, {
headers: new Headers({
"Content-Type": "application/json",
"Token": this.props.user.sessionToken,
}),
method: "POST",
body: JSON.stringify({
state: false,
}),
});
}
// TODO: Type?
getDashboard = (): Promise<any> => {
return new Promise((res, rej) => {
fetch(`${BACKEND_URL}/api/user/dashboard`, {
headers: new Headers({
"Content-Type": "application/json",
"Token": this.props.user.sessionToken,
}),
})
.then(resp => resp.json(), err => rej(err))
.then(data => {
if (data.error === "200") {
res(data.data);
} else {
console.log("Application::getDashboard: Failed to get dashboard");
rej(data);
}
});
});
}
updateDoneLevels = (id: string): void => {
fetch(`${BACKEND_URL}/api/user/level/${id}`, {
headers: new Headers({
"Content-Type": "application/json",
"Token": this.props.user.sessionToken,
}),
method: "POST",
});
}
login = (username: string, password: string): Promise<IUser | IResponse> => {
return new Promise((res, rej) => {
fetch(`${BACKEND_URL}/api/login`, {
method: "POST",
headers: new Headers({
"Content-Type": "application/json",
}),
body: JSON.stringify({
// NOTE: We will force HTTPS, so this should not be a
// problem
username,
password,
}),
}).then(data => data.json(), err => {
// The fetch failed
rej(err);
}).then((resp: IResponse) => {
if (resp.error === "0") {
// Successful login
this.props.setUser(resp.data);
this.props.setDidLogin(true);
setSessionToken(window, resp.data.sessionToken);
this.props.setAuthenticated(true);
res(resp.data);
} else {
rej(resp);
}
});
});
}
logout = () => {
// NOTE: No promise, since we don't care about the result
fetch(`${BACKEND_URL}/api/user/logout`, {
headers: new Headers({
"Content-Type": "application/json",
"Token": this.props.user.sessionToken,
}),
method: "GET",
});
// Remove the session locally
removeSessionToken(window);
this.props.setAuthenticated(false);
}
// Checks whether the user is logged in
isAuthenticated = () => {
return this.props.authenticated;
}
render() {
// TODO: Show a spinner before mounting the routes, so that we can
// check if were authenticated before doing any requests
return <BrowserRouter
basename="/app/">
<div className="flex" >
<Drawer logout={this.logout} />
<div className="content">
<Route exact path="/" component={() => <Redirect to="/login" />} />
<Route exact path="/login" component={() => {
return <LoginPage login={this.login} />
}} />
<AuthRoute
isAuth={this.isAuthenticated}
path="/dashboard"
component={() => {
return <Dashboard
getDashboard={this.getDashboard} />
}} />
<AuthRoute
isAuth={this.isAuthenticated}
path="/welcome"
component={() => {
return <WelcomePage
dontShowAgain={this.introDontShowAgain} />
}} />
<AuthRoute
isAuth={this.isAuthenticated}
path="/levelList"
component={() => <LevelListPage
getLevels={this.getLevels} />} />
{/*We cannot use AuthRoute here, because match is undefined otherwise*/}
<Route
path="/level/:id"
component={({ match }) => {
if (this.isAuthenticated()) {
return <LevelPage
id={match.params.id}
levelVocab={this.getLevelVocab}
setLastReview={this.setLastReview} />;
} else {
return <Redirect to="/login" />;
}
}} />
<Route
path="/register"
component={() => <RegisterPage />} />
<Route
path="/review/level/:id"
component={({ match }) => {
if (this.isAuthenticated()) {
return <ReviewPage
reviewType={ReviewType.LEVEL}
updateDoneLevels={this.updateDoneLevels}
levelId={match.params.id}
vocabByLevel={this.getLevelVocab}
setLastReview={this.setLastReview} />;
} else {
return <Redirect to="/login" />;
}
}} />
<AuthRoute
isAuth={this.isAuthenticated}
path="/review/queue"
component={() => {
return <ReviewPage
reviewType={ReviewType.QUEUE}
vocabByQueue={this.getReviewQueue}
setLastReview={this.setLastReview} />;
}} />
<AuthRoute
isAuth={this.isAuthenticated}
path="/review/summary"
component={() => {
return <SummaryPage />
}} />
</div>
</div >
</BrowserRouter >;
}
};