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

392 lines
14 KiB
TypeScript
Raw Normal View History

2018-08-24 17:03:08 +00:00
import * as React from "react";
2018-09-18 16:06:08 +00:00
import { BrowserRouter, Route, Redirect } from "react-router-dom";
2018-08-26 14:23:48 +00:00
import AuthRoute from "../security/AuthRoute";
import { setSessionToken, removeSessionToken, getSessionToken } from "../security/Token";
2018-08-26 14:23:48 +00:00
import Dashboard from "../containers/Dashboard";
2018-09-18 16:59:15 +00:00
import LoginPage from "../containers/LoginPage";
import LevelListPage from "../containers/LevelList";
2018-09-18 18:20:26 +00:00
import LevelPage from "../containers/LevelPage";
import ReviewPage from "../containers/Review";
2018-09-19 18:30:42 +00:00
import SummaryPage from "../containers/SummaryPage";
2018-09-15 13:28:27 +00:00
import WelcomePage from "../pages/intro";
2018-10-07 16:40:48 +00:00
import RegisterPage from "../containers/Register";
2018-08-26 14:23:48 +00:00
2018-09-18 16:06:08 +00:00
import Drawer from "../containers/Drawer";
2018-09-16 15:16:24 +00:00
import { BACKEND_URL } from "../config";
2018-09-06 18:13:29 +00:00
import { ILevel } from "../models/level";
2018-09-28 21:29:05 +00:00
import { TopTen } from "../models/learner";
import { IVocab } from "../models/vocab";
import { IReviewMetadata, ReviewType } from "../models/review";
2018-09-16 15:16:24 +00:00
import { IUser } from "../models/user";
import { IResponse } from "../models/server";
2018-08-24 17:03:08 +00:00
2018-09-18 16:59:15 +00:00
interface IProps {
authenticated: boolean;
2018-09-20 19:03:46 +00:00
user: IUser;
2018-09-18 16:59:15 +00:00
setAuthenticated: (status: boolean) => void;
setDidLogin: (status: boolean) => void;
2018-09-18 16:59:15 +00:00
setUser: (user: IUser) => void;
2018-10-01 17:27:27 +00:00
setLastReview: (meta: IReviewMetadata) => void;
2018-10-01 17:21:57 +00:00
setUserScoreDelta: (delta: number) => void;
2018-09-18 16:59:15 +00:00
};
2018-09-14 16:53:01 +00:00
// TODO: Replace the sessionStorage with localStorage?
2018-09-18 16:59:15 +00:00
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
2018-09-29 12:23:09 +00:00
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);
2018-09-29 12:23:09 +00:00
this.props.setAuthenticated(false);
});
}
}
2018-09-29 12:23:09 +00:00
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);
}
});
});
}
2018-09-24 11:36:42 +00:00
getLevels = (): Promise<ILevel[]> => {
2018-09-19 16:00:38 +00:00
return new Promise((res, rej) => {
2018-09-24 11:36:42 +00:00
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);
}
});
2018-09-19 16:00:38 +00:00
});
2018-08-24 17:03:08 +00:00
}
2018-09-24 11:36:42 +00:00
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);
}
});
});
}
2018-09-30 14:17:54 +00:00
// TODO: Type?
2018-10-01 17:21:57 +00:00
setLastReview = (meta: IReviewMetadata, sm2: any, delta: number) => {
// Update the state
this.props.setLastReview(meta);
2018-10-01 17:21:57 +00:00
this.props.setUserScoreDelta(delta);
// Tell the server about the last review
2018-09-24 11:53:20 +00:00
fetch(`${BACKEND_URL}/api/user/lastReview`, {
headers: new Headers({
"Content-Type": "application/json",
"Token": this.props.user.sessionToken,
}),
method: "POST",
body: JSON.stringify({
meta,
2018-09-30 14:17:54 +00:00
sm2,
2018-10-01 17:21:57 +00:00
delta,
2018-09-24 11:53:20 +00:00
}),
}).then(resp => resp.json(), err => {
console.log("Application::setLastReview: POSTing last results failed");
});
2018-09-12 17:23:00 +00:00
}
2018-09-19 16:06:59 +00:00
getReviewQueue = (): Promise<IVocab[]> => {
return new Promise((res, rej) => {
2018-09-24 11:53:20 +00:00
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);
}
});
2018-09-19 16:06:59 +00:00
});
}
getTopTenLearners = (): Promise<TopTen[]> => {
// TODO: Deprecate?
const id = this.props.user.classId;
return new Promise((res, rej) => {
2018-09-23 20:22:22 +00:00
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);
}
});
});
2018-08-26 15:12:07 +00:00
}
getNextLevel = (): Promise<ILevel> => {
return new Promise((res, rej) => {
2018-09-23 20:22:22 +00:00
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);
}
});
});
2018-09-06 18:05:21 +00:00
}
2018-09-20 19:03:46 +00:00
getLevelVocab = (id: number): Promise<IVocab[]> => {
2018-09-18 18:20:26 +00:00
return new Promise((res, rej) => {
2018-09-23 20:22:22 +00:00
fetch(`${BACKEND_URL}/api/level/${id}/vocab`, {
2018-09-20 19:03:46 +00:00
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);
}
});
2018-09-18 18:20:26 +00:00
});
2018-09-06 18:05:21 +00:00
}
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,
}),
});
}
2018-09-24 16:29:29 +00:00
// 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> => {
2018-08-24 17:03:08 +00:00
return new Promise((res, rej) => {
2018-09-23 20:22:22 +00:00
fetch(`${BACKEND_URL}/api/login`, {
2018-09-16 15:16:24 +00:00
method: "POST",
headers: new Headers({
2018-09-18 16:59:15 +00:00
"Content-Type": "application/json",
2018-09-16 15:16:24 +00:00
}),
body: JSON.stringify({
2018-09-18 16:59:15 +00:00
// NOTE: We will force HTTPS, so this should not be a
// problem
username,
password,
2018-09-16 15:16:24 +00:00
}),
}).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);
}
});
2018-08-24 17:03:08 +00:00
});
}
logout = () => {
2018-09-29 20:10:06 +00:00
// 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);
}
2018-08-26 14:23:48 +00:00
// Checks whether the user is logged in
2018-09-18 16:59:15 +00:00
isAuthenticated = () => {
return this.props.authenticated;
2018-08-26 14:23:48 +00:00
}
2018-08-24 17:03:08 +00:00
render() {
// TODO: Show a spinner before mounting the routes, so that we can
// check if were authenticated before doing any requests
2018-08-26 14:23:48 +00:00
return <BrowserRouter
2018-08-26 17:27:21 +00:00
basename="/app/">
<div className="flex" >
<Drawer logout={this.logout} />
2018-08-26 14:23:48 +00:00
<div className="content">
<Route exact path="/" component={() => <Redirect to="/login" />} />
<Route exact path="/login" component={() => {
2018-09-18 16:59:15 +00:00
return <LoginPage login={this.login} />
2018-08-26 14:23:48 +00:00
}} />
2018-08-26 15:12:07 +00:00
<AuthRoute
isAuth={this.isAuthenticated}
path="/dashboard"
2018-09-12 17:23:00 +00:00
component={() => {
return <Dashboard
2018-09-24 16:29:29 +00:00
getDashboard={this.getDashboard} />
2018-09-12 17:23:00 +00:00
}} />
2018-09-15 13:28:27 +00:00
<AuthRoute
isAuth={this.isAuthenticated}
path="/welcome"
component={() => {
return <WelcomePage
dontShowAgain={this.introDontShowAgain} />
2018-09-15 13:28:27 +00:00
}} />
2018-08-26 15:12:07 +00:00
<AuthRoute
isAuth={this.isAuthenticated}
2018-09-06 18:05:21 +00:00
path="/levelList"
2018-09-19 16:00:38 +00:00
component={() => <LevelListPage
getLevels={this.getLevels} />} />
2018-09-06 18:05:21 +00:00
{/*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} />;
2018-09-06 18:05:21 +00:00
} else {
return <Redirect to="/login" />;
}
}} />
2018-10-07 16:40:48 +00:00
<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} />;
}} />
2018-09-12 17:23:00 +00:00
<AuthRoute
isAuth={this.isAuthenticated}
path="/review/summary"
component={() => {
2018-09-19 18:30:42 +00:00
return <SummaryPage />
2018-09-12 17:23:00 +00:00
}} />
2018-08-26 14:23:48 +00:00
</div>
2018-09-18 16:59:15 +00:00
</div >
</BrowserRouter >;
2018-08-24 17:03:08 +00:00
}
};