feat: Implement the new /dashboard API
This commit is contained in:
		
							parent
							
								
									7339e1ccac
								
							
						
					
					
						commit
						24f35be058
					
				| @ -114,16 +114,35 @@ userRouter.post("/level/:id", async (req, res) => { | ||||
| }); | ||||
| 
 | ||||
| // Get the data needed for the dashboard 
 | ||||
| userRouter.post("/dashboard", async (req, res) => { | ||||
| userRouter.get("/dashboard", async (req, res) => { | ||||
|     console.log("STUB(post): /user/dashboard"); | ||||
| 
 | ||||
|     let users: any[] = []; | ||||
|     let nr = 10; | ||||
|     for (let i = 0; i < 10; i++) | ||||
|         users = users.concat({ | ||||
|             username: `Test User ${i}`, | ||||
|             score: 100 * i, | ||||
|             level: Math.floor(Math.random() * Math.floor(10)), | ||||
|             nr: nr--, | ||||
|         }); | ||||
| 
 | ||||
|     // TODO: Stub
 | ||||
|     res.send({ | ||||
|         error: "0", | ||||
|         error: "200", | ||||
|         data: { | ||||
|             nextLevel: {}, | ||||
|             topTen: {}, | ||||
|             lastReview: {}, | ||||
|             nextLevel: { | ||||
|                 name: "Test level", | ||||
|                 desc: "Just a test", | ||||
|                 level: 3, | ||||
| 
 | ||||
|                 done: false, | ||||
|             }, | ||||
|             topTen: users, | ||||
|             lastReview: { | ||||
|                 correct: 0, | ||||
|                 wrong: 0, | ||||
|             }, | ||||
|         }, | ||||
|     }); | ||||
| }); | ||||
|  | ||||
| @ -153,14 +153,6 @@ export function setNextLevel(level: ILevel) { | ||||
|     }; | ||||
| }; | ||||
| 
 | ||||
| export const DASHBOARD_SET_NL_LOADING = "DASHBOARD_SET_NL_LOADING"; | ||||
| export function setDashboardNLLoading(state: boolean) { | ||||
|     return { | ||||
|         type: DASHBOARD_SET_NL_LOADING, | ||||
|         state, | ||||
|     }; | ||||
| }; | ||||
| 
 | ||||
| export const SET_TOP_TEN = "SET_TOP_TEN"; | ||||
| export function setTopTen(topTen: TopTen[]) { | ||||
|     return { | ||||
| @ -169,14 +161,6 @@ export function setTopTen(topTen: TopTen[]) { | ||||
|     }; | ||||
| }; | ||||
| 
 | ||||
| export const DASHBOARD_SET_TT_LOADING = "DASHBOARD_SET_TT_LOADING"; | ||||
| export function setDashboardTTLoading(state: boolean) { | ||||
|     return { | ||||
|         type: DASHBOARD_SET_TT_LOADING, | ||||
|         state, | ||||
|     }; | ||||
| }; | ||||
| 
 | ||||
| export const SET_DID_LOGIN = "SET_DID_LOGIN"; | ||||
| export function setDidLogin(state: boolean) { | ||||
|     return { | ||||
| @ -185,14 +169,6 @@ export function setDidLogin(state: boolean) { | ||||
|     }; | ||||
| }; | ||||
| 
 | ||||
| export const DASHBOARD_SET_LR_LOADING = "DASHBOARD_SET_LR_LOADING"; | ||||
| export function setDashboardLRLoading(state: boolean) { | ||||
|     return { | ||||
|         type: DASHBOARD_SET_LR_LOADING, | ||||
|         state, | ||||
|     }; | ||||
| }; | ||||
| 
 | ||||
| export const REVIEW_SET_DIALOG = "REVIEW_SET_DIALOG"; | ||||
| export function setReviewDialog(state: boolean) { | ||||
|     return { | ||||
| @ -200,3 +176,11 @@ export function setReviewDialog(state: boolean) { | ||||
|         state, | ||||
|     }; | ||||
| }; | ||||
| 
 | ||||
| export const DASHBOARD_SET_LOADING = "DASHBOARD_SET_LOADING"; | ||||
| export function setDashboardLoading(state: boolean) { | ||||
|     return { | ||||
|         type: DASHBOARD_SET_LOADING, | ||||
|         state, | ||||
|     }; | ||||
| }; | ||||
|  | ||||
| @ -173,6 +173,27 @@ export default class Application extends React.Component<IProps> { | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     // 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`, { | ||||
| @ -233,9 +254,7 @@ export default class Application extends React.Component<IProps> { | ||||
|                         path="/dashboard" | ||||
|                         component={() => { | ||||
|                             return <Dashboard | ||||
|                                 getNextLevel={this.getNextLevel} | ||||
|                                 getLastReview={this.getLastReview} | ||||
|                                 getTopTen={this.getTopTenLearners} /> | ||||
|                                 getDashboard={this.getDashboard} /> | ||||
|                         }} /> | ||||
|                     <AuthRoute | ||||
|                         isAuth={this.isAuthenticated} | ||||
|  | ||||
| @ -1,8 +1,7 @@ | ||||
| import { connect } from "react-redux"; | ||||
| 
 | ||||
| import { | ||||
|     setNextLevel, setDashboardNLLoading, setTopTen, setDashboardTTLoading, | ||||
|     setDashboardLRLoading, setLastReview, | ||||
|     setNextLevel, setTopTen, setLastReview, setDashboardLoading | ||||
| } from "../actions"; | ||||
| 
 | ||||
| import { ILevel } from "../models/level"; | ||||
| @ -13,22 +12,18 @@ import DashboardPage from "../pages/dashboard"; | ||||
| 
 | ||||
| const mapStateToProps = state => { | ||||
|     return { | ||||
|         loading: state.review.loading, | ||||
|         nextLevel: state.nextLevel, | ||||
|         loadingNextLevel: state.dashboard.loadingNL, | ||||
|         loadingTopTen: state.dashboard.loadingTT, | ||||
|         topTen: state.topTen, | ||||
|         loadingLastReview: state.dashboard.loadingLR, | ||||
|         lastReview: state.lastReview, | ||||
|     }; | ||||
| }; | ||||
| const mapDispatchToProps = dispatch => { | ||||
|     return { | ||||
|         setLoadingNL: (state: boolean) => dispatch(setDashboardNLLoading(state)), | ||||
|         setLoading: (state: boolean) => dispatch(setDashboardLoading(state)), | ||||
|         setNextLevel: (level: ILevel) => dispatch(setNextLevel(level)), | ||||
|         setTopTen: (topTen: ILearner[]) => dispatch(setTopTen(topTen)), | ||||
|         setLoadingTT: (state: boolean) => dispatch(setDashboardTTLoading(state)), | ||||
|         setLastReview: (review: IReviewMetadata) => dispatch(setLastReview(review)), | ||||
|         setLoadingLR: (state: boolean) => dispatch(setDashboardLRLoading(state)), | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
|  | ||||
| @ -16,51 +16,50 @@ import { ILearner, TopTen } from "../models/learner"; | ||||
| import { IReviewMetadata } from "../models/review"; | ||||
| 
 | ||||
| interface IProps { | ||||
|     getNextLevel: () => Promise<ILevel>; | ||||
|     getLastReview: () => Promise<IReviewMetadata>; | ||||
|     getTopTen: () => Promise<ILearner[]>; | ||||
|     getDashboard: () => Promise<any>; | ||||
| 
 | ||||
|     loading: boolean; | ||||
|     setLoading: (state: boolean) => void; | ||||
|     nextLevel: ILevel; | ||||
|     loadingNextLevel: boolean; | ||||
|     setLoadingNL: (state: boolean) => void; | ||||
|     setNextLevel: (level: ILevel) => void; | ||||
|     topTen: TopTen[]; | ||||
|     loadingTopTen: boolean; | ||||
|     setLoadingTT: (state: boolean) => void; | ||||
|     setTopTen: (topten: TopTen[]) => void; | ||||
|     lastReview: IReviewMetadata; | ||||
|     loadingLastReview: boolean; | ||||
|     setLoadingLR: (state: boolean) => void; | ||||
|     setLastReview: (review: IReviewMetadata) => void; | ||||
| } | ||||
| 
 | ||||
| export default class Dashboard extends React.Component<IProps> { | ||||
|     componentDidMount() { | ||||
|         this.props.setLoadingNL(true); | ||||
| 
 | ||||
|         this.props.getNextLevel().then(res => { | ||||
|             this.props.setLoadingNL(false); | ||||
|             this.props.setNextLevel(res); | ||||
|         }, err => { | ||||
|             console.log("Failed to fetch next level!", err); | ||||
|         }); | ||||
| 
 | ||||
|         this.props.getTopTen().then(res => { | ||||
|             this.props.setLoadingTT(false); | ||||
|             this.props.setTopTen(res); | ||||
|         }, err => { | ||||
|             console.log("Failed to fetch Top Ten"); | ||||
|         }); | ||||
| 
 | ||||
|         this.props.getLastReview().then(res => { | ||||
|             this.props.setLoadingLR(false); | ||||
|             this.props.setLastReview(res); | ||||
|         }, err => { | ||||
|             console.log("Failed to fetch Last Review"); | ||||
|         }); | ||||
|         this.props.setLoading(true); | ||||
|         this.props.getDashboard().then(res => { | ||||
|             this.props.setNextLevel(res.nextLevel); | ||||
|             this.props.setTopTen(res.topTen); | ||||
|             this.props.setLastReview(res.lastReview); | ||||
|             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 direction = small ? "column" : "row"; | ||||
| 
 | ||||
| @ -70,63 +69,48 @@ export default class Dashboard extends React.Component<IProps> { | ||||
|             <Grid container direction={direction} spacing={16}> | ||||
|                 <Grid item lg={4}> | ||||
|                     <Paper className="paper"> | ||||
|                         {this.props.loadingNextLevel ? ( | ||||
|                             <CircularProgress /> | ||||
|                         ) : ( | ||||
|                                 <div> | ||||
|                                     <Typography variant="title">{`Level ${level.level}`}</Typography> | ||||
|                                     <Typography variant="title" component="p">{level.name}</Typography> | ||||
|                                     <br /> | ||||
|                                     <Typography component="p"> | ||||
|                                         {level.desc} | ||||
|                                     </Typography> | ||||
|                                     <Button | ||||
|                                         component={Link} | ||||
|                                         to={`/level/${level.level}`} | ||||
|                                         className="lesson-card-btn"> | ||||
|                                         Zum Level | ||||
|                                     </Button> | ||||
|                                 </div> | ||||
| 
 | ||||
|                             )} | ||||
|                         <div> | ||||
|                             <Typography variant="title">{`Level ${level.level}`}</Typography> | ||||
|                             <Typography variant="title" component="p">{level.name}</Typography> | ||||
|                             <br /> | ||||
|                             <Typography component="p"> | ||||
|                                 {level.desc} | ||||
|                             </Typography> | ||||
|                             <Button | ||||
|                                 component={Link} | ||||
|                                 to={`/level/${level.level}`} | ||||
|                                 className="lesson-card-btn"> | ||||
|                                 Zum Level | ||||
|                             </Button> | ||||
|                         </div> | ||||
|                     </Paper> | ||||
|                 </Grid> | ||||
|                 <Grid item lg={4}> | ||||
|                     <Paper className="paper"> | ||||
|                         {this.props.loadingTopTen ? ( | ||||
|                             <CircularProgress /> | ||||
|                         ) : ( | ||||
|                                 <div> | ||||
|                                     <Typography variant="title" component="p"> | ||||
|                                         Rangliste: Top 10 | ||||
|                                   </Typography> | ||||
|                         <div> | ||||
|                             <Typography variant="title" component="p"> | ||||
|                                 Rangliste: Top 10 | ||||
|                             </Typography> | ||||
| 
 | ||||
|                                     <Scoreboard topTen={this.props.topTen} /> | ||||
|                                 </div> | ||||
|                             )} | ||||
|                             <Scoreboard topTen={this.props.topTen} /> | ||||
|                         </div> | ||||
|                     </Paper> | ||||
|                 </Grid> | ||||
|                 <Grid item lg={4}> | ||||
|                     <Paper className="paper"> | ||||
|                         { | ||||
|                             this.props.loadingLastReview ? ( | ||||
|                                 <CircularProgress /> | ||||
|                             ) : ( | ||||
|                                     <div> | ||||
|                                         <Typography variant="title"> | ||||
|                                             Letzte Wiederholung | ||||
|                                         </Typography> | ||||
|                                         <SummaryTable reviewMeta={this.props.lastReview} /> | ||||
|                         <div> | ||||
|                             <Typography variant="title"> | ||||
|                                 Letzte Wiederholung | ||||
|                             </Typography> | ||||
|                             <SummaryTable reviewMeta={this.props.lastReview} /> | ||||
| 
 | ||||
|                                         <Button | ||||
|                                             component={Link} | ||||
|                                             to="/review/queue" | ||||
|                                             className="lesson-card-btn"> | ||||
|                                             Vokabeln üben | ||||
|                                         </Button> | ||||
|                                     </div> | ||||
|                                 ) | ||||
|                         } | ||||
|                             <Button | ||||
|                                 component={Link} | ||||
|                                 to="/review/queue" | ||||
|                                 className="lesson-card-btn"> | ||||
|                                 Vokabeln üben | ||||
|                             </Button> | ||||
|                         </div> | ||||
|                     </Paper> | ||||
|                 </Grid> | ||||
|             </Grid> | ||||
|  | ||||
| @ -51,7 +51,6 @@ export default class Dashboard extends React.Component<IProps> { | ||||
|                         </Paper> | ||||
|                     </Grid> | ||||
|                 </Grid> | ||||
| 
 | ||||
|             </div>; | ||||
|         } | ||||
| 
 | ||||
|  | ||||
| @ -37,9 +37,7 @@ interface IState { | ||||
|     }; | ||||
| 
 | ||||
|     dashboard: { | ||||
|         loadingNL: boolean; | ||||
|         loadingTT: boolean; | ||||
|         loadingLR: boolean; | ||||
|         loading: boolean; | ||||
|     }; | ||||
| 
 | ||||
|     review: { | ||||
| @ -97,9 +95,7 @@ const initialState: IState = { | ||||
|     }, | ||||
| 
 | ||||
|     dashboard: { | ||||
|         loadingNL: true, | ||||
|         loadingTT: true, | ||||
|         loadingLR: true, | ||||
|         loading: true, | ||||
|     }, | ||||
| 
 | ||||
|     review: { | ||||
| @ -227,38 +223,26 @@ export function LateinicusApp(state: IState = initialState, action: any) { | ||||
|             return Object.assign({}, state, { | ||||
|                 nextLevel: action.level, | ||||
|             }); | ||||
|         case Actions.DASHBOARD_SET_NL_LOADING: | ||||
|             return Object.assign({}, state, { | ||||
|                 dashboard: Object.assign({}, state.dashboard, { | ||||
|                     loadingNL: action.state, | ||||
|                 }), | ||||
|             }); | ||||
|         case Actions.SET_TOP_TEN: | ||||
|             return Object.assign({}, state, { | ||||
|                 topTen: action.topTen, | ||||
|             }); | ||||
|         case Actions.DASHBOARD_SET_TT_LOADING: | ||||
|             return Object.assign({}, state, { | ||||
|                 dashboard: Object.assign({}, state.dashboard, { | ||||
|                     loadingTT: action.state, | ||||
|                 }), | ||||
|             }); | ||||
|         case Actions.SET_DID_LOGIN: | ||||
|             return Object.assign({}, state, { | ||||
|                 didLogin: state, | ||||
|             }); | ||||
|         case Actions.DASHBOARD_SET_LR_LOADING: | ||||
|             return Object.assign({}, state, { | ||||
|                 dashboard: Object.assign({}, state.dashboard, { | ||||
|                     loadingLR: action.state, | ||||
|                 }), | ||||
|             }); | ||||
|         case Actions.REVIEW_SET_DIALOG: | ||||
|             return Object.assign({}, state, { | ||||
|                 review: Object.assign({}, state.review, { | ||||
|                     dialogOpen: action.state, | ||||
|                 }), | ||||
|             }); | ||||
|         case Actions.DASHBOARD_SET_LOADING: | ||||
|             return Object.assign({}, state, { | ||||
|                 review: Object.assign({}, state.review, { | ||||
|                     loading: action.state, | ||||
|                 }), | ||||
|             }); | ||||
|         default: | ||||
|             // Ignore the initialization call to the reducer. By that we can
 | ||||
|             // catch all actions that are not implemented
 | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 Alexander Polynomdivision
						Alexander Polynomdivision