diff --git a/src/actions/index.ts b/src/actions/index.ts index 9b0847b..613868a 100644 --- a/src/actions/index.ts +++ b/src/actions/index.ts @@ -13,3 +13,36 @@ export function setDrawerButton(state: boolean) { show: state, }; }; + +export const LOGIN_SET_SNACKBAR = "LOGIN_SET_SNACKBAR"; +export function setLoginSnackbar(visibility: boolean, msg: string = "") { + return { + type: LOGIN_SET_SNACKBAR, + show: visibility, + msg, + }; +}; + +export const LOGIN_SET_LOADING = "LOGIN_SET_LOADING"; +export function setLoginLoading(visibility: boolean) { + return { + type: LOGIN_SET_LOADING, + show: visibility, + }; +}; + +export const SET_AUTHENTICATED = "SET_AUTHENTICATED"; +export function setAuthenticated(state: boolean) { + return { + type: SET_AUTHENTICATED, + status: state, + }; +} + +export const SET_USER = "SET_USER"; +export function setUser(user: IUser) { + return { + type: SET_USER, + user, + }; +} diff --git a/src/components/app.tsx b/src/components/app.tsx index 60ea397..123e00b 100644 --- a/src/components/app.tsx +++ b/src/components/app.tsx @@ -5,7 +5,7 @@ import { BrowserRouter, Route, Redirect } from "react-router-dom"; import AuthRoute from "../security/AuthRoute"; import Dashboard from "../pages/dashboard"; -import LoginPage from "../pages/login"; +import LoginPage from "../containers/LoginPage"; import LevelListPage from "../pages/levelList"; import LevelPage from "../pages/level"; import ReviewPage from "../pages/review"; @@ -22,22 +22,15 @@ import { IVocab, VocabType } from "../models/vocab"; import { IReviewMetadata, ReviewType } from "../models/review"; import { IUser } from "../models/user"; +interface IProps { + setAuthenticated: (status: boolean) => void; + setUser: (user: IUser) => void; +}; + // TODO: Replace the sessionStorage with localStorage? // TODO: Cache API-Calls // TODO: When mounting without a login, check if the sessionToken is still valid -export default class Application extends React.Component<{}> { - constructor(props: any) { - super(props); - - // Load a key from the SessionStorage - const authKey = window.sessionStorage.getItem("sessionToken") || null; - // TODO - const loggedIn = authKey !== null; - - this.login = this.login.bind(this); - this.isAuthenticated = this.isAuthenticated.bind(this); - } - +export default class Application extends React.Component { getLevels(): ILevel[] { console.log("STUB: Application::getLevels"); // TODO: Actually fetch them from somewhere @@ -184,27 +177,25 @@ export default class Application extends React.Component<{}> { } */] as IVocab[]; } - login(username: string, password: string): Promise { + login = (username: string, password: string): Promise => { return new Promise((res, rej) => { - // TODO: First login? Redirect to /welcome fetch(`${BACKEND_URL}/login`, { method: "POST", headers: new Headers({ - 'Content-Type': "application/json", + "Content-Type": "application/json", }), body: JSON.stringify({ - // NOTE: We will force HTTPS and hash using pbkdf2 on the - // server - username: username, - hash: password, + // NOTE: We will force HTTPS, so this should not be a + // problem + username, + password, }), }).then(data => data.json()) - .then((resp) => { + .then(resp => { if (resp.error === "0") { - this.setState({ - loggedIn: true, - user: resp.data, - }); + // Successful login + // TODO: Set the auth token here + this.props.setUser(resp.data); res(resp.data); } else { rej({}); @@ -214,7 +205,7 @@ export default class Application extends React.Component<{}> { } // Checks whether the user is logged in - isAuthenticated() { + isAuthenticated = () => { // TODO: Security? // TODO: Implement return true; @@ -239,12 +230,12 @@ export default class Application extends React.Component<{}> { render() { return -
+ < div className="flex" >
} /> { - return + return }} /> { reviewMeta={this.getLastReview} /> }} />
-
-
; + + ; } }; diff --git a/src/containers/Application.ts b/src/containers/Application.ts new file mode 100644 index 0000000..cac2253 --- /dev/null +++ b/src/containers/Application.ts @@ -0,0 +1,22 @@ +import { connect } from "react-redux"; + +import { IUser } from "../models/user"; + +import Application from "../components/app"; + +import { setAuthenticated, setUser } from "../actions"; + +const mapStateToProps = state => { + return {}; +}; +const mapDispatchToProps = dispatch => { + return { + setAuthenticated: (status: boolean) => dispatch(setAuthenticated(status)), + setUser: (user: IUser) => dispatch(setUser(user)), + }; +}; + +const ApplicationContainer = connect(mapStateToProps, + mapDispatchToProps)(Application); + +export default ApplicationContainer; diff --git a/src/containers/LoginPage.ts b/src/containers/LoginPage.ts new file mode 100644 index 0000000..6a3fa0e --- /dev/null +++ b/src/containers/LoginPage.ts @@ -0,0 +1,23 @@ +import { connect } from "react-redux"; + +import { setLoginSnackbar, setLoginLoading } from "../actions"; + +import LoginPage from "../pages/login"; + +const mapStateToProps = state => { + return { + loading: state.login.loading, + snackOpen: state.login.snackOpen, + snackMsg: state.login.snackMsg, + authenticated: state.authenticated, + }; +}; +const mapDispatchToProps = dispatch => { + return { + setLoading: (state: boolean) => dispatch(setLoginLoading(state)), + setSnackbar: (state: boolean, msg: string) => dispatch(setLoginSnackbar(state, msg)), + }; +}; + +const LoginPageContainer = connect(mapStateToProps, mapDispatchToProps)(LoginPage); +export default LoginPageContainer; diff --git a/src/index.tsx b/src/index.tsx index 71b467a..d159382 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -6,7 +6,7 @@ import { Provider } from "react-redux"; import { LateinicusApp } from "./reducers"; -import Application from "./components/app"; +import Application from "./containers/Application"; const store = createStore(LateinicusApp); diff --git a/src/pages/login.tsx b/src/pages/login.tsx index a2ea83d..2891ce1 100644 --- a/src/pages/login.tsx +++ b/src/pages/login.tsx @@ -14,74 +14,41 @@ import { IUser } from "../models/user"; interface IProps { login: (username: string, password: string) => Promise; - loggedIn: boolean; -} - -interface IState { - username: string; - password: string; + authenticated: boolean; + setLoading: (state: boolean) => void; + setSnackbar: (state: boolean, msg: string) => void; loading: boolean; - - snack: string; // The message - open: boolean; + snackOpen: boolean; + snackMsg: string; } +/* + * interface IState { + * loading: boolean; + * + * snack: string; // The message + * open: boolean; + * } */ -export default class LoginPage extends React.Component { - constructor(props: any) { - super(props); +export default class LoginPage extends React.Component { + private usernameRef: any = undefined; + private passwordRef: any = undefined; - this.state = { - username: "", - password: "", - loading: false, - snack: "", - open: false, - }; + performLogin = () => { + this.props.setLoading(true); - this.update = this.update.bind(this); - this.performLogin = this.performLogin.bind(this); - } - - update(prop: string) { - return (event: any) => { - this.setState({ - [prop]: event.target.value, - }); - }; - } - - performLogin() { - const load = (loading: boolean) => { - this.setState({ - loading - }); - } - const showSnackbar = (msg: string) => { - this.setState({ - open: true, - snack: msg, - }); - }; - - load(true); - - const { username, password } = this.state; - console.log(this.state); + 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) => { - load(false); - showSnackbar("Failed to log in"); + this.props.setLoading(false); + this.props.setSnackbar(true, "Failed to log in"); }); } render() { - const snackbarClose = () => { - this.setState({ open: false }); - }; - return
{ + inputRef={node => this.usernameRef = node} /> + inputRef={node => this.passwordRef = node} /> { - this.state.loading ? ( + this.props.loading ? ( ) : undefined } @@ -126,12 +91,12 @@ export default class LoginPage extends React.Component { this.props.setSnackbar(false, "")} + message={this.props.snackMsg} autoHideDuration={6000} /> { - this.props.loggedIn ? ( + this.props.authenticated ? ( ) : undefined } diff --git a/src/reducers/index.ts b/src/reducers/index.ts index 310999e..6586a99 100644 --- a/src/reducers/index.ts +++ b/src/reducers/index.ts @@ -1,16 +1,47 @@ import * as Actions from "../actions"; -const initialState = { +import { ILearner } from "../models/learner"; +import { IUser } from "../models/user"; + +interface IState { + drawer: boolean; + drawerButton: boolean; + authenticated: boolean; + + // TODO: Rework this + user: IUser | {}, + + login: { + loading: boolean; + snackMsg: string; + snackOpen: boolean; + }; + + topTen: ILearner[]; +}; + +const initialState: IState = { // Show the drawer? drawer: false, // Should we show the button to open the drawer? drawerButton: true, // Is the user authenticated? // TODO: Set this to false - authenticated: true, + authenticated: false, + + user: {}, + + login: { + loading: false, + snackMsg: "", + snackOpen: false, + }, + + // The top ten + topTen: [], }; -export function LateinicusApp(state = initialState, action: any) { +export function LateinicusApp(state: IState = initialState, action: any) { switch (action.type) { case Actions.SET_DRAWER: return Object.assign({}, state, { @@ -20,6 +51,30 @@ export function LateinicusApp(state = initialState, action: any) { return Object.assign({}, state, { drawerButton: action.show, }); + case Actions.LOGIN_SET_SNACKBAR: + return Object.assign({}, state, { + login: { + loading: state.login.loading, + snackMsg: action.msg, + snackOpen: action.show, + }, + }); + case Actions.LOGIN_SET_LOADING: + return Object.assign({}, state, { + login: { + loading: action.show, + snackMsg: state.login.snackMsg, + snackOpen: state.login.snackOpen, + }, + }); + case Actions.SET_AUTHENTICATED: + return Object.assign({}, state, { + authenticated: action.status, + }); + case Actions.SET_USER: + return Object.assign({}, state, { + user: action.user, + }); default: return state; }