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 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; }; // 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 => { 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); } }); }); } setLastReview = (meta: IReviewMetadata) => { fetch(`${BACKEND_URL}/api/user/lastReview`, { headers: new Headers({ "Content-Type": "application/json", "Token": this.props.user.sessionToken, }), method: "POST", body: JSON.stringify({ meta, }), }).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[]> => { 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); } }); }); } // 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); } }); }); } 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 = () => { // TODO: Tell the server that we're logging ourselves out 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 /> }} /> <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} drawerButtonState={this.drawerButtonState} setLastReview={this.setLastReview} />; } else { return <Redirect to="/login" />; } }} /> <Route path="/review/level/:id" component={({ match }) => { if (this.isAuthenticated()) { return <ReviewPage reviewType={ReviewType.LEVEL} 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} drawerButtonState={this.drawerButtonState} setLastReview={this.setLastReview} />; }} /> <AuthRoute isAuth={this.isAuthenticated} path="/review/summary" component={() => { return <SummaryPage /> }} /> </div> </div > </BrowserRouter >; } };