diff --git a/smart-hut/package.json b/smart-hut/package.json index 7a37c28..236574a 100644 --- a/smart-hut/package.json +++ b/smart-hut/package.json @@ -10,6 +10,7 @@ "@testing-library/user-event": "^7.1.2", "axios": "^0.19.2", "classnames": "^2.2.6", + "immutability-helper": "^3.0.2", "material-ui-image": "^3.2.3", "react": "^16.12.0", "react-axios": "^2.0.3", diff --git a/smart-hut/src/App.js b/smart-hut/src/App.js index c39324d..820e9ea 100644 --- a/smart-hut/src/App.js +++ b/smart-hut/src/App.js @@ -12,44 +12,17 @@ import ConfirmRegistration from "./views/ConfirmRegistration"; import ConfirmResetPassword from "./views/ConfirmResetPassword"; import Instruction from "./views/Instruction"; import queryString from "query-string"; - -import { call } from "./client_server"; - -/*let userJsonString = JSON.parse(localStorage.getItem("token")); - let date = new Date().getTime().toString(); - let delta = date - userJsonString.timestamp; - if (delta < 5*60*60*1000) { - loggedIn = true; - }*/ +import { RemoteService } from "./remote"; +import { connect } from "react-redux"; class App extends Component { - constructor(props) { - super(props); - - let loggedIn = false; - let token = undefined; - - try { - let userJsonString = localStorage.getItem("token"); - let exp = localStorage.getItem("exp"); - let date = new Date().getTime(); - if (userJsonString && exp && date < exp) { - loggedIn = true; - token = userJsonString; - } else { - localStorage.removeItem("token"); - localStorage.removeItem("exp"); - } - } catch (exception) {} + constructor(props, context) { + super(props, context); this.state = { - loggedIn: loggedIn, - token: token, + query: "", info: "", }; - - this.auth = this.auth.bind(this); - this.logout = this.logout.bind(this); } componentDidMount() { @@ -58,70 +31,21 @@ class App extends Component { this.setState({ query: values, }); - } else { - this.setState({ - query: "ciao", - }); } } - auth(data) { - return call - .login(data.params) - .then((res) => { - if (res.data && res.status === 200) { - let expire = new Date().getTime() + 60 * 60 * 5 * 1000; - localStorage.setItem("token", res.data.jwttoken); - localStorage.setItem("exp", expire); - call.setToken(res.data.jwttoken); - this.setState({ - user: data.params.user, - token: res.data.jwttoken, - loggedIn: true, - }); - this.getInfo(); - return res; - //this.props.history.push("/dashboard"); - } else { - this.setState({ - error: res.data.message, - }); - return res.status; - } - }) - .catch((err) => { - return err; - }); - } - - logout() { - this.setState({ - loggedIn: false, - }); - - localStorage.removeItem("token"); - localStorage.removeItem("exp"); - } - render() { + console.log("rendering root", this.props.loggedIn, this.state.query); return ( - {this.state.loggedIn && this.state.token ? ( - - ) : ( - - )} + {this.props.loggedIn ? : } - {this.state.loggedIn ? ( - - ) : ( - - )} + {this.props.loggedIn ? : } @@ -149,4 +73,9 @@ class App extends Component { } } -export default App; +const mapStateToProps = (state, _) => { + console.log("malusae react", state); + return { loggedIn: !!(state.login && state.login.loggedIn) }; +}; +const AppContainer = connect(mapStateToProps, RemoteService)(App); +export default AppContainer; diff --git a/smart-hut/src/components/HeaderController.js b/smart-hut/src/components/HeaderController.js index d5a5e19..e0adbf4 100644 --- a/smart-hut/src/components/HeaderController.js +++ b/smart-hut/src/components/HeaderController.js @@ -1,7 +1,9 @@ import React from "react"; import { Grid, Divider, Button, Label, Responsive } from "semantic-ui-react"; import { Segment, Image } from "semantic-ui-react"; -import { call } from "../client_server"; +import { RemoteService } from "../remote"; +import { withRouter } from "react-router-dom"; +import { connect } from "react-redux"; const IconHomeImage = () => ( ( const TitleImage = () => ; -export default class MyHeader extends React.Component { +export class MyHeader extends React.Component { constructor(props) { super(props); this.state = { @@ -24,15 +26,13 @@ export default class MyHeader extends React.Component { this.getInfo(); } + getInfo() { - call.getUserInfo(this.state.token).then((res) => { - if (res.status === 200) { - this.setState({ - username: res.data.username, - }); - } - }); + this.props + .fetchUserInfo() + .catch((err) => console.error("MyHeader fetch user info error", err)); } + render() { return (
@@ -86,3 +86,13 @@ export default class MyHeader extends React.Component { ); } } + +const mapStateToProps = (state, _) => ({ + username: + state.userInfo && state.userInfo.username ? state.userInfo.username : "", +}); +const LoginContainer = connect( + mapStateToProps, + RemoteService +)(withRouter(MyHeader)); +export default LoginContainer; diff --git a/smart-hut/src/remote.js b/smart-hut/src/remote.js index e170bd5..e070415 100644 --- a/smart-hut/src/remote.js +++ b/smart-hut/src/remote.js @@ -2,30 +2,43 @@ import smartHutStore from "./store"; import actions from "./storeActions"; import axios from "axios"; -class Endpoint { - socket = null; - axiosInstance = axios.create({ - baseURL: this.URL, - validateStatus: (status) => status >= 200 && status < 300, - }); +/** + * An object returned by promise rejections in remoteservice + * @typedef {Error} RemoteError + * @property {String[]} messages a list of user-friendly error messages to show; + */ +class RemoteError extends Error { + messages; - /** - * Returns the endpoint URL (SmartHut backend URL) - * @returns {String} endpoint URL - */ - static get URL() { - return window.BACKEND_URL !== "__BACKEND_URL__" - ? window.BACKEND_URL - : "http://localhost:8080"; + constructor(messages) { + super("remote error"); + this.messages = messages; } +} + +/** + * Returns the endpoint URL (SmartHut backend URL) + * @returns {String} endpoint URL + */ +function endpointURL() { + return window.BACKEND_URL !== "__BACKEND_URL__" + ? window.BACKEND_URL + : "http://localhost:8080"; +} + +const Endpoint = { + axiosInstance: axios.create({ + baseURL: endpointURL(), + validateStatus: (status) => status >= 200 && status < 300, + }), /** * Returns token for current session, null if logged out * @returns {String|null} the token */ - static get token() { + get token() { return smartHutStore.getState().login.token; - } + }, /** * Performs an authenticated request @@ -34,99 +47,106 @@ class Endpoint { * @param {[String]String} query query ('?') parameters (no params by default) * @param {any} body the JSON request body */ - static send(method, route, query = {}, body = null) { - if (!this.token) { + send: (method, route, query = {}, body = null) => { + if (!Endpoint.token) { throw new Error("No token while performing authenticated request"); } - if (method !== "get") - return this.axiosInstance(route, { - method: method, - params: query, - data: ["put", "post"].indexOf(method) !== -1 ? body : null, - headers: { - Authorization: `Bearer ${this.token}`, - }, - }).then((res) => { - if (!res.data) { - console.error("Response body is empty"); - return null; - } else { - return res; - } - }); - } + return Endpoint.axiosInstance(route, { + method: method, + params: query, + data: ["put", "post"].indexOf(method) !== -1 ? body : null, + headers: { + Authorization: `Bearer ${Endpoint.token}`, + }, + }).then((res) => { + if (!res.data) { + console.error("Response body is empty"); + return null; + } else { + return res; + } + }); + }, + /** * Performs login * @param {String} usernameOrEmail * @param {String} password - * @returns {Promise} promise that resolves to void and rejects - * with user-fiendly errors as a String array + * @returns {Promise} promise that resolves to the token string + * and rejects to the axios error. */ - static login(dispatch, usernameOrEmail, password) { + login: (usernameOrEmail, password) => { return Endpoint.axiosInstance - .post(`${Endpoint.URL}/auth/login`, { + .post(`/auth/login`, { usernameOrEmail, password, }) .then((res) => { - localStorage.setItem("token", res.token); + localStorage.setItem("token", res.data.jwttoken); localStorage.setItem("exp", new Date().getTime() + 5 * 60 * 60 * 1000); - this.socket = new ServiceSocket(res.data.token); - return res.data.token; + Endpoint.socket = new ServiceSocket(res.data.jwttoken); + return res.data.jwttoken; }); - } + }, - static logout() { - this.socket.close(); - this.socket = null; - } + /** + * Returns an immediately resolved promise for the socket logouts + * @return {Promise} An always-resolved promise + */ + logout: () => { + return Promise.resolve(void 0); + }, /** * Performs an authenticated GET request * @param {String} route the desired route (e.g. "/rooms/1/devices") * @param {[String]String} query query ('?') parameters (no params by default) + * @returns {Promise<*, *>} The Axios-generated promise */ - static get(route, query) { + get(route, query = {}) { return this.send("get", route, query); - } + }, /** * Performs an authenticated POST request * @param {String} route the desired route (e.g. "/rooms/1/devices") * @param {[String]String} query query ('?') parameters (no params by default) * @param {any} body the JSON request body + * @returns {Promise<*, *>} The Axios-generated promise */ - static post(route, query, body) { + post(route, query, body) { return this.send("post", route, query, body); - } + }, /** * Performs an authenticated PUT request * @param {String} route the desired route (e.g. "/rooms/1/devices") * @param {[String]String} query query ('?') parameters (no params by default) * @param {any} body the JSON request body + * @returns {Promise<*, *>} The Axios-generated promise */ - static put(route, query, body) { + put(route, query = {}, body = {}) { return this.send("put", route, query, body); - } + }, /** * Performs an authenticated DELETE request * @param {get|post|put|delete} the desired method * @param {String} route the desired route (e.g. "/rooms/1/devices") * @param {[String]String} query query ('?') parameters (no params by default) + * @returns {Promise<*, *>} The Axios-generated promise */ - static delete(route, query) { + delete(route, query = {}) { return this.send("delete", route, query); - } -} + }, +}; /** * Given an error response, returns an array of user * friendly messages to display to the user * @param {*} err the Axios error reponse object - * @returns {String[]} user friendly error messages + * @returns {RemoteError} user friendly error messages */ function parseValidationErrors(err) { if ( @@ -135,96 +155,97 @@ function parseValidationErrors(err) { err.response.data && Array.isArray(err.response.data.errors) ) { - return [...new Set(err.response.data.errors.map((e) => e.defaultMessage))]; + throw new RemoteError([ + ...new Set(err.response.data.errors.map((e) => e.defaultMessage)), + ]); } else { console.warn("Non validation error", err); - return ["Network error"]; + throw new RemoteError(["Network error"]); } } -export class RemoteService { +export const RemoteService = { /** * Performs login * @param {String} usernameOrEmail * @param {String} password - * @returns {Promise} promise that resolves to void and rejects - * with user-fiendly errors as a String array + * @returns {Promise} promise that resolves to void and rejects + * with user-fiendly errors as a RemoteError */ - static login(usernameOrEmail, password) { + login: (usernameOrEmail, password) => { return (dispatch) => { - return Endpoint.login(dispatch, usernameOrEmail, password) + return Endpoint.login(usernameOrEmail, password) .then((token) => dispatch(actions.loginSuccess(token))) .catch((err) => { console.warn("login error", err); - return [ - err.response.status === 401 + throw new RemoteError([ + err.response && err.response.status === 401 ? "Wrong credentials" : "An error occurred while logging in", - ]; + ]); }); }; - } + }, /** * Performs logout - * @param {String} usernameOrEmail - * @param {String} password */ - static logout() { - return (dispatch) => Endpoint.logout.then(void dispatch(actions.logout())); - } + logout: () => { + return (dispatch) => + Endpoint.logout().then(void dispatch(actions.logout())); + }, /** * Fetches user information via REST calls, if it is logged in - * @returns {Promise} promise that resolves to void and rejects - * with user-fiendly errors as a String array + * @returns {Promise} promise that resolves to void and rejects + * with user-fiendly errors as a RemoteError */ - static fetchUserInfo() { + fetchUserInfo: () => { return (dispatch) => { return Endpoint.get("/auth/profile") .then((res) => void dispatch(actions.userInfoUpdate(res.data))) .catch((err) => { console.warn("Fetch user info error", err); - return ["Network error"]; + throw new RemoteError(["Network error"]); }); }; - } + }, /** * Fetches all rooms that belong to this user. This call does not * populate the devices attribute in rooms. - * @returns {Promise} promise that resolves to void and rejects - * with user-fiendly errors as a String array + * @returns {Promise} promise that resolves to void and rejects + * with user-fiendly errors as a RemoteError */ - static fetchAllRooms() { + fetchAllRooms: () => { return (dispatch) => { return Endpoint.get("/room") .then((res) => void dispatch(actions.roomsUpdate(res.data))) .catch((err) => { console.error("Fetch all rooms error", err); - return ["Network error"]; + throw new RemoteError(["Network error"]); }); }; - } + }, /** * Fetches all devices in a particular room, or fetches all devices. * This also updates the devices attribute on values in the map rooms. * @param {Number|null} roomId the room to which fetch devices * from, null to fetch from all rooms - * @returns {Promise} promise that resolves to void and rejects - * with user-fiendly errors as a String array + * @returns {Promise} promise that resolves to void and rejects + * with user-fiendly errors as a RemoteError */ - static fetchDevices(roomId = null) { + fetchDevices: (roomId = null) => { return (dispatch) => { return Endpoint.get(roomId ? `/room/${roomId}/device` : "/device") .then((res) => void dispatch(actions.devicesUpdate(roomId, res.data))) .catch((err) => { console.error(`Fetch devices roomId=${roomId} error`, err); - return ["Network error"]; + throw new RemoteError(["Network error"]); }); }; - } + }, /** * Creates/Updates a room with the given data @@ -232,10 +253,10 @@ export class RemoteService { * @param {String} data.icon the room's icon name in SemanticUI icons * @param {String} data.image ths room's image, as base64 * @param {Number|null} roomId the room's id if update, null for creation - * @returns {Promise} promise that resolves to void and rejects - * with user-fiendly errors as a String array + * @returns {Promise} promise that resolves to void and rejects + * with user-fiendly errors as a RemoteError */ - static saveRoom(data, roomId = null) { + saveRoom: (data, roomId = null) => { return (dispatch) => { data = { name: data.name, @@ -250,7 +271,7 @@ export class RemoteService { .then((res) => void dispatch(actions.roomSave(res.data))) .catch(parseValidationErrors); }; - } + }, /** * Creates/Updates a device with the given data. If @@ -261,10 +282,10 @@ export class RemoteService { * is used for updates and the POST "/" * endpoints are used for creation. * @param {Device} data the device to update. - * @returns {Promise} promise that resolves to void and rejects - * with user-fiendly errors as a String array + * @returns {Promise} promise that resolves to void and rejects + * with user-fiendly errors as a RemoteError */ - static updateDevice(data) { + updateDevice: (data) => { return (dispatch) => { let url = "/device"; if ((data.id && data.flowType === "OUTPUT") || !data.id) { @@ -275,12 +296,12 @@ export class RemoteService { .then((res) => void dispatch(actions.deviceUpdate(res.data))) .catch((err) => { console.warn("Update device: ", data, "error: ", err); - return ["Network error"]; + throw new RemoteError(["Network error"]); }); }; - } + }, - static _operateInput(url, getUrl, action) { + _operateInput: (url, getUrl, action) => { return (dispatch) => { return Endpoint.put(url, {}, action) .then(async (res) => { @@ -290,10 +311,10 @@ export class RemoteService { }) .catch((err) => { console.warn(`${url} error`, err); - return ["Network error"]; + throw new RemoteError(["Network error"]); }); }; - } + }, /** * Changes the state of a switch, by turning it on, off or toggling it. @@ -302,30 +323,30 @@ export class RemoteService { * * @param {Number} switchId the switch device id * @param {SwitchOperation} type the operation to perform on the switch - * @returns {Promise} promise that resolves to void and rejects - * with user-fiendly errors as a String array + * @returns {Promise} promise that resolves to void and rejects + * with user-fiendly errors as a RemoteError */ - static switchOperate(switchId, type) { + switchOperate: (switchId, type) => { return this._operateInput("/switch/operate", `/switch/${switchId}`, { type: type.toUpperCase(), id: switchId, }); - } + }, /** * Turns a knob dimmer to a specific amount * * @param {Number} dimmerId the knob dimmer id * @param {number} intensity the absolute intensity to dim to. Must be >=0 and <= 100. - * @returns {Promise} promise that resolves to void and rejects - * with user-fiendly errors as a String array + * @returns {Promise} promise that resolves to void and rejects + * with user-fiendly errors as a RemoteError */ - static knobDimmerDimTo(dimmerId, intensity) { + knobDimmerDimTo: (dimmerId, intensity) => { return this._operateInput("/knobDimmer/dimTo", `/knobDimmer/${dimmerId}`, { intensity, id: dimmerId, }); - } + }, /** * Turns a button dimmer up or down @@ -334,10 +355,10 @@ export class RemoteService { * * @param {Number} dimmerId the button dimmer id * @param {ButtonDimmerDimType} dimType the type of dim to perform - * @returns {Promise} promise that resolves to void and rejects - * with user-fiendly errors as a String array + * @returns {Promise} promise that resolves to void and rejects + * with user-fiendly errors as a RemoteError */ - static buttonDimmerDim(dimmerId, dimType) { + buttonDimmerDim: (dimmerId, dimType) => { return this._operateInput( "/buttonDimmer/dim", `/buttonDimmer/${dimmerId}`, @@ -346,59 +367,63 @@ export class RemoteService { id: dimmerId, } ); - } + }, /** * Resets the meter on a smart plug * * @param {Number} smartPlugId the smart plug to reset - * @returns {Promise} promise that resolves to void and rejects - * with user-fiendly errors as a String array + * @returns {Promise} promise that resolves to void and rejects + * with user-fiendly errors as a RemoteError */ - static smartPlugReset(smartPlugId) { + smartPlugReset(smartPlugId) { return (dispatch) => { return Endpoint.delete(`/smartPlug/${smartPlugId}/meter`) .then((res) => dispatch(actions.deviceOperationUpdate([res.data]))) .catch((err) => { console.warn(`Smartplug reset error`, err); - return ["Network error"]; + throw new RemoteError(["Network error"]); }); }; - } + }, /** * Deletes a room * @param {Number} roomId the id of the room to delete - * @returns {Promise} promise that resolves to void and rejects - * with user-fiendly errors as a String array + * @returns {Promise} promise that resolves to void and rejects + * with user-fiendly errors as a RemoteError */ - static deleteRoom(roomId) { + deleteRoom: (roomId) => { return (dispatch) => { Endpoint.delete(`/room/${roomId}`) .then((_) => dispatch(actions.roomDelete(roomId))) .catch((err) => { console.warn("Room deletion error", err); - return ["Network error"]; + throw new RemoteError(["Network error"]); }); }; - } + }, /** * Deletes a device * @param {Number} deviceId the id of the device to delete - * @returns {Promise} promise that resolves to void and rejects - * with user-fiendly errors as a String array + * @returns {Promise} promise that resolves to void and rejects + * with user-fiendly errors as a RemoteError */ - static deleteDevice(deviceId) { + deleteDevice: (deviceId) => { return (dispatch) => { Endpoint.delete(`/device/${deviceId}`) .then((_) => dispatch(actions.deviceDelete(deviceId))) .catch((err) => { console.warn("Device deletion error", err); - return ["Network error"]; + throw new RemoteError(["Network error"]); }); }; - } + }, +}; + +for (const key in RemoteService) { + RemoteService[key] = RemoteService[key].bind(RemoteService); } /** Class to handle connection with the sensor socket */ @@ -409,7 +434,7 @@ class ServiceSocket { connection; static get URL() { - const httpURL = new URL(Endpoint.URL); + const httpURL = new URL(endpointURL()); const isSecure = httpURL.protocol === "https:"; const protocol = isSecure ? "wss:" : "ws:"; const port = httpURL.port || (isSecure ? 443 : 80); @@ -512,7 +537,7 @@ export class Forms { .then((_) => void 0) .catch((err) => { console.warn("Init reset password failed", err); - return ["Network error"]; + throw new RemoteError(["Network error"]); }); } diff --git a/smart-hut/src/store.js b/smart-hut/src/store.js index 06290c0..354cfc3 100644 --- a/smart-hut/src/store.js +++ b/smart-hut/src/store.js @@ -1,68 +1,100 @@ import { createStore, applyMiddleware } from "redux"; import thunk from "redux-thunk"; import actions from "./storeActions"; +import update from "immutability-helper"; function reducer(previousState, action) { - let newState = Object.assign({}, previousState); + let newState; const createOrUpdateRoom = (room) => { if (!(room.id in newState.rooms)) { - newState.rooms[room.id] = room; - newState.rooms[room.id].devices = new Set(); + newState = update(newState, { + rooms: { [room.id]: { devices: { $set: new Set() } } }, + }); } else { - newState.rooms[room.id].name = room.name; - newState.rooms[room.id].image = room.image; - newState.rooms[room.id].icon = room.icon; + newState = update(newState, { + rooms: { + [room.id]: { + name: { $set: room.name }, + image: { $set: room.image }, + icon: { $set: room.icon }, + }, + }, + }); } }; + let change; switch (action.type) { case "LOGIN_UPDATE": - newState.login = action.login; - delete newState.errors.login; + newState = update(previousState, { login: { $set: action.login } }); break; case "USER_INFO_UPDATE": - newState.user = action.user; - delete newState.errors.userInfo; + newState = update(previousState, { userInfo: { $set: action.user } }); break; case "ROOMS_UPDATE": + newState = previousState; for (const room of action.rooms) { createOrUpdateRoom(room); } - delete newState.errors.rooms; break; case "DEVICES_UPDATE": + change = null; + // if room is given, delete all devices in that room // and remove any join between that room and deleted // devices if (action.roomId) { + change = { + rooms: { [action.roomId]: { devices: { $set: new Set() } } }, + devices: { $unset: [] }, + }; + const room = newState.rooms[action.roomId]; for (const deviceId of room.devices) { - delete newState.devices[deviceId]; + change.devices.$unset.push(deviceId); } - room.devices = []; } else if (action.partial) { // if the update is partial and caused by an operation on an input // device (like /switch/operate), iteratively remove deleted // devices and their join with their corresponding room. + change = { + devices: { $unset: [] }, + rooms: {}, + }; + for (const device of action.devices) { - const room = newState.rooms[newState.devices[device.id].roomId]; - room.devices.delete(device.id); - delete newState.devices[device.id]; + const roomId = previousState.devices[device.id].roomId; + change.rooms[roomId] = change.rooms[roomId] || { + devices: { $remove: [] }, + }; + change.rooms[roomId].devices.$remove.push(device.id); + change.devices.$unset.push(device.id); } } else { // otherwise, just delete all devices and all joins // between rooms and devices - newState.devices = {}; + change = { + devices: { $set: {} }, + rooms: {}, + }; + for (const room of newState.rooms) { - room.devices = []; + change.rooms[room.id].devices = { $set: new Set() }; } } + newState = update(previousState, change); + change = { + devices: {}, + rooms: {}, + }; for (const device of action.devices) { - newState.devices[device.id] = device; + change.devices[device.id] = { $set: device }; if (device.roomId in newState.rooms) { - newState.rooms[device.roomId].devices.add(device.id); + const devices = change.rooms[device.roomId].devices; + devices.$add = devices.$add || []; + devices.$add.push(device.id); } else { console.warn( "Cannot join device", @@ -72,14 +104,13 @@ function reducer(previousState, action) { ); } } - - delete newState.errors.devices; + newState = update(newState, change); break; case "ROOM_SAVE": createOrUpdateRoom(action.room); break; case "ROOM_DELETE": - if (!(actions.roomId in newState.rooms)) { + if (!(actions.roomId in previousState.rooms)) { console.warn(`Room to delete ${actions.roomId} does not exist`); break; } @@ -87,31 +118,41 @@ function reducer(previousState, action) { // This update does not ensure the consistent update of switchId/dimmerId properties // on output devices connected to an input device in this room. Please manually request // all devices again if consistent update is desired - for (const id of newState.rooms[action.roomId].devices) { - delete newState.devices[id]; + change = { devices: { $unset: [] } }; + + for (const id of previousState.rooms[action.roomId].devices) { + change.devices.$unset.push(id); } - delete newState.rooms[action.roomId]; + change.rooms = { $unset: actions.roomId }; + newState = update(previousState, change); break; case "DEVICE_DELETE": - if (!(actions.deviceId in newState.devices)) { + if (!(actions.deviceId in previousState.devices)) { console.warn(`Device to delete ${actions.deviceId} does not exist`); break; } - newState.rooms[newState.devices[actions.deviceId].roomId].devices.delete( - actions.deviceId - ); - delete newState.devices[actions.deviceId]; + newState = update(previousState, { + devices: { $unset: actions.deviceId }, + rooms: { + [previousState.devices[actions.deviceId].roomId]: { + devices: { $remove: actions.deviceId }, + }, + }, + }); break; case "LOGOUT": - newState.login = { token: null, loggedIn: false }; - newState.rooms = []; - newState.devices = []; - delete newState.errors.login; + newState = { + login: { loggedIn: false, token: null }, + rooms: [], + devices: [], + userInfo: null, + }; break; default: console.warn(`Action type ${action.type} unknown`, action); + return previousState; } console.log("new state: ", newState); @@ -123,6 +164,7 @@ function createSmartHutStore() { const exp = localStorage.getItem("exp"); const initialState = { + errors: {}, login: { token: token, }, @@ -133,7 +175,7 @@ function createSmartHutStore() { devices: {}, }; - initialState.login.loggedIn = token && exp > new Date().getTime(); + initialState.login.loggedIn = !!(token && exp > new Date().getTime()); if (!initialState.login.loggedIn) { localStorage.removeItem("token"); localStorage.removeItem("exp"); diff --git a/smart-hut/src/views/Login.js b/smart-hut/src/views/Login.js index ec15625..4d80aad 100644 --- a/smart-hut/src/views/Login.js +++ b/smart-hut/src/views/Login.js @@ -9,8 +9,11 @@ import { Icon, Input, } from "semantic-ui-react"; +import { RemoteService } from "../remote"; +import { withRouter } from "react-router-dom"; +import { connect } from "react-redux"; -export default class Login extends Component { +class Login extends Component { constructor(props) { super(props); this.state = { @@ -23,31 +26,15 @@ export default class Login extends Component { handleLogin = (e) => { e.preventDefault(); - const params = { - usernameOrEmail: this.state.user, - password: this.state.password, - }; this.props - .auth({ - user: this.state.user, - params: params, - }) - .then((res) => { - if (res.response.status === 200) { - } else if (res.response.status === 401) { - this.setState({ - error: { state: true, message: "Wrong credentials" }, - }); - } else { - console.log(res); - this.setState({ - error: { state: true, message: "An error occurred while logging" }, - }); - } - }) + .login(this.state.user, this.state.password) + .then(() => this.props.history.push("/dashboard")) .catch((err) => { console.log(err); + this.setState({ + error: { state: true, message: err.messages.join(" - ") }, + }); }); }; @@ -108,7 +95,7 @@ export default class Login extends Component { color="blue" fluid size="large" - onClick={this.handleLogin} + onClick={this.handleLogin.bind(this)} > Login @@ -127,3 +114,9 @@ export default class Login extends Component { ); } } + +const mapStateToProps = (state, _) => ({ loggedIn: state.login.loggedIn }); +const LoginContainer = withRouter(connect(mapStateToProps, RemoteService))( + Login +); +export default LoginContainer; diff --git a/smart-hut/yarn.lock b/smart-hut/yarn.lock index d625164..7501bf0 100644 --- a/smart-hut/yarn.lock +++ b/smart-hut/yarn.lock @@ -5228,6 +5228,13 @@ immer@1.10.0: resolved "https://registry.yarnpkg.com/immer/-/immer-1.10.0.tgz#bad67605ba9c810275d91e1c2a47d4582e98286d" integrity sha512-O3sR1/opvCDGLEVcvrGTMtLac8GJ5IwZC4puPrLuRj3l7ICKvkmA0vGuU9OW8mV9WIBRnaxp5GJh9IEAaNOoYg== +immutability-helper@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/immutability-helper/-/immutability-helper-3.0.2.tgz#e9187158b47c93368a92e84c31714c4b3dff30b0" + integrity sha512-fcrJ26wpvUcuGRpoGY4hyQ/JOeR1HAunMmE3C0XYXSe6plAGtgTlB2S4BzueBANCPrDJ7AByL1yrIRLIlVfwpA== + dependencies: + invariant "^2.2.4" + import-cwd@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/import-cwd/-/import-cwd-2.1.0.tgz#aa6cf36e722761285cb371ec6519f53e2435b0a9"