diff --git a/mobile/package-lock.json b/mobile/package-lock.json index 70e2beb..3c618c8 100644 --- a/mobile/package-lock.json +++ b/mobile/package-lock.json @@ -5104,9 +5104,9 @@ } }, "react-form-binder": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/react-form-binder/-/react-form-binder-1.2.0.tgz", - "integrity": "sha512-VFeiB5nCe01WU5aVJILMw7GLgOPsYJvdJEL9WRz7qecKDZx30sKA5bLDOWHsWQDZhediIr3KLpFkPxj0u89tDg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/react-form-binder/-/react-form-binder-2.0.0.tgz", + "integrity": "sha512-ihqbA3sp8eOOvjN2cSWOC7pfK+ukuRW5+dgpbrDJKnH/wgJ0LSMaJg2d/lX8bc0XO7+KxRJi7mBdizvCT1qhgQ==", "requires": { "eventemitter3": "^2.0.3", "prop-types": "^15.5.10", diff --git a/mobile/package.json b/mobile/package.json index 331ccf4..9b693df 100644 --- a/mobile/package.json +++ b/mobile/package.json @@ -23,7 +23,7 @@ "eventemitter3": "^3.1.0", "moment": "^2.22.1", "react": "^16.3.2", - "react-form-binder": "^1.2.0", + "react-form-binder": "^2.0.0", "react-native": "^0.55.4", "react-native-image-picker": "^0.26.7", "react-native-iphone-x-helper": "^1.0.3", diff --git a/mobile/src/Activity/Activity.js b/mobile/src/Activity/Activity.js index 035710f..b772053 100644 --- a/mobile/src/Activity/Activity.js +++ b/mobile/src/Activity/Activity.js @@ -22,7 +22,7 @@ import { BoundPhotoPanel, FormStaticInput, } from "../ui" -import { MessageModal, WaitModal } from "../Modal" +import { MessageModal, WaitModal, ProgressModal } from "../Modal" import autobind from "autobind-decorator" import KeyboardSpacer from "react-native-keyboard-spacer" import { isIphoneX } from "react-native-iphone-x-helper" @@ -88,6 +88,7 @@ export class Activity extends React.Component { binder: new FormBinder({}, Activity.bindings), waitModal: null, messageModal: null, + progressModal: null, } const { search } = this.props.location @@ -105,7 +106,7 @@ export class Activity extends React.Component { this.setState({ binder: new FormBinder( { - ...this.state.binder.getOriginalFieldValues(), + ...this.state.binder.originalObj, workItem: workItem._id, team: api.loggedInUser.team, }, @@ -225,12 +226,35 @@ export class Activity extends React.Component { @autobind handleUploadStarted() { - this.setState({ waitModal: { message: "Uploading Photo..." } }) + this.setState({ + progressModal: { message: "Uploading Photo..." }, + uploadPercent: 0, + }) } @autobind - handleUploadEnded() { - this.setState({ waitModal: null }) + handleUploadProgress(uploadData) { + console.log(uploadData) + if (this.state.progressModal) { + this.setState({ + uploadPercent: Math.round( + uploadData.uploadedChunks / uploadData.numberOfChunks * 100 + ), + }) + return true + } else { + return false + } + } + + @autobind + handleUploadEnded(successful, uploadData) { + this.setState({ progressModal: null }) + } + + @autobind + handleUploadCanceled() { + this.setState({ progressModal: null }) } render() { @@ -313,11 +337,18 @@ export class Activity extends React.Component { name="photos" binder={binder} onUploadStarted={this.handleUploadStarted} + onUploadProgress={this.handleUploadProgress} onUploadEnded={this.handleUploadEnded} /> {isIphoneX ? : null} + { - return doc.toClient(partial) - }) - - cursor.on("data", (doc) => { - Activities.push(doc) - }) - cursor.on("end", () => { - res.json({ - total: total, - offset: skip, - count: activities.length, - items: activities, - }) - }) - cursor.on("error", (err) => { - throw createError.InternalServerError(err.message) - }) - } - - async createActivity(req, res, next) { - const isAdmin = req.user.administrator - - if (!isAdmin) { - return new createError.Forbidden() - } - - // Create a new Activity template then assign it to a value in the req.body - const Activity = this.db.Activity - let activity = new Activity(req.body) - - // Save the activity (with promise) - If it doesnt, catch and throw error - const newActivity = await activity.save() - - res.json(newActivity.toClient()) - } - - async updateActivity(req, res, next) { - const isAdmin = req.user.administrator - - if (!isAdmin) { - return new createError.Forbidden() - } - - // Do this here because Mongoose will add it automatically otherwise - if (!req.body._id) { - throw createError.BadRequest("No _id given in body") - } - - let Activity = this.db.Activity - let activity = await Activity.findById(req.body._id) - - if (!activity) { - return next( - createError.NotFound(`Activity with _id ${req.body_id} was not found`) - ) - } - - let activityUpdates = new Activity(req.body) - - // Strip off all BSON types - activity.merge(activityUpdates) - const savedActivity = await activity.save() - - res.json(savedActivity.toClient()) - } - - async getActivity(req, res, next) { - const Activity = this.db.Activity - const _id = req.params._id - const activity = await Activity.findById(_id) - - if (!activity) { - throw createError.NotFound(`Activity with _id ${_id} not found`) - } - - res.json(activity.toClient()) - } - - async deleteActivity(req, res, next) { - const isAdmin = req.user.administrator - - if (!isAdmin) { - return new createError.Forbidden() - } - - const Activity = this.db.Activity - const _id = req.params._id - const activity = await Activity.remove({ _id }) - - if (!activity) { - throw createError.NotFound(`Activity with _id ${_id} not found`) - } - - res.json({}) - } - async deleteAllActivities(req, res, next) { const Activity = this.db.Activity const Team = this.db.Team diff --git a/server/src/api/routes/AssetRoutes.js b/server/src/api/routes/AssetRoutes.js index 1b30792..5bd271f 100644 --- a/server/src/api/routes/AssetRoutes.js +++ b/server/src/api/routes/AssetRoutes.js @@ -11,19 +11,19 @@ import B64 from "b64" import { PassThrough } from "stream" import { catchAll } from "." -function pipeToGridFS(readable, gfsWriteable, decoder) { +function pipeToGridFS(readable, writable, decoder) { const promise = new Promise((resolve, reject) => { readable.on("error", (error) => { reject(error) }) - gfsWriteable.on("error", (error) => { + writeable.on("error", (error) => { reject(error) }) - gfsWriteable.on("close", (file) => { + writeable.on("finish", (file) => { resolve(file) }) }) - readable.pipe(decoder).pipe(gfsWriteable) + readable.pipe(decoder).pipe(writeable) return promise } @@ -73,12 +73,13 @@ export class AssetRoutes { assetId = assetId.slice(0, extIndex) } - const file = await this.db.gridfs.findOneAsync({ _id: assetId }) + const cursor = await this.db.gridfs.findOne({ _id: assetId }) - if (!file) { + if (!cursor) { throw createError.NotFound(`Asset ${assetId} was not found`) } + const file = cursor.next() const ifNoneMatch = req.get("If-None-Match") if (ifNoneMatch && ifNoneMatch === file.md5) { @@ -98,13 +99,13 @@ export class AssetRoutes { ETag: file.md5, }) - this.db.gridfs.createReadStream({ _id: file._id }).pipe(res) + this.db.gridfs.openDownloadStream(file._id).pipe(res) } async deleteAsset(req, res, next) { const assetId = req.params._id - await this.db.gridfs.removeAsync({ _id: assetId }) + await this.db.gridfs.delete(assetId) res.json({}) } @@ -235,11 +236,11 @@ export class AssetRoutes { if (uploadedChunks >= uploadData.numberOfChunks) { let readable = redisReadStream(this.rs.client, uploadDataId) - let writeable = this.db.gridfs.createWriteStream({ - _id: uploadId, - filename: uploadData.fileName, - content_type: uploadData.contentType, - }) + let writeable = this.db.gridfs.openUploadStreamWithId( + uploadId, + uploadData.fileName, + { contentType: uploadData.contentType } + ) const decoder = uploadData.chunkContentType === "application/base64" diff --git a/server/src/api/routes/BaseRoutes.js b/server/src/api/routes/BaseRoutes.js new file mode 100644 index 0000000..c4779bf --- /dev/null +++ b/server/src/api/routes/BaseRoutes.js @@ -0,0 +1,147 @@ +import passport from "passport" +import createError from "http-errors" +import autobind from "autobind-decorator" +import { catchAll } from "." + +@autobind +export class BaseRoutes { + constructor(container, model) { + this.model = model + this.log = container.log + this.db = container.db + + const basePath = "/" + model.collection.collectionName + const app = container.app + + app + .route(basePath) + .get( + passport.authenticate("bearer", { session: false }), + catchAll(this.listItems) + ) + .post( + passport.authenticate("bearer", { session: false }), + catchAll(this.createItem) + ) + .put( + passport.authenticate("bearer", { session: false }), + catchAll(this.updateItem) + ) + + app + .route(basePath + "/:_id([a-f0-9]{24})") + .get( + passport.authenticate("bearer", { session: false }), + catchAll(this.getItem) + ) + .delete( + passport.authenticate("bearer", { session: false }), + catchAll(this.deleteItem) + ) + } + + async listItems(req, res, next) { + const ItemModel = this.model + const limit = req.query.limit || 20 + const skip = req.query.skip || 0 + const partial = !!req.query.partial + let query = {} + + const total = await ItemModel.count({}) + + let items = [] + let cursor = ItemModel.find(query) + .limit(limit) + .skip(skip) + .cursor() + .map((doc) => { + return doc.toClient(partial) + }) + + cursor.on("data", (doc) => { + items.push(doc) + }) + cursor.on("end", () => { + res.json({ + total: total, + offset: skip, + count: items.length, + items: items, + }) + }) + cursor.on("error", (err) => { + throw createError.InternalServerError(err.message) + }) + } + + async createItem(req, res, next) { + const isAdmin = req.user.administrator + + if (!isAdmin) { + return new createError.Forbidden() + } + + const ItemModel = this.model + let item = new ItemModel(req.body) + + const newItem = await item.save() + + res.json(newItem.toClient()) + } + + async updateItem(req, res, next) { + const isAdmin = req.user.administrator + + if (!isAdmin) { + return new createError.Forbidden() + } + + // Do this here because Mongoose will add it automatically otherwise + if (!req.body._id) { + throw createError.BadRequest("No _id given in body") + } + + let ItemModel = this.model + let item = await ItemModel.findById(req.body._id) + + if (!item) { + return next(createError.NotFound(`Item with _id ${_id} was not found`)) + } + + item.merge(new ItemModel(req.body)) + + const savedItem = await item.save() + + res.json(savedItem.toClient()) + } + + async getItem(req, res, next) { + const ItemModel = this.model + const _id = req.params._id + const item = await ItemModel.findById(_id) + + if (!item) { + throw createError.NotFound(`Item with _id ${_id} not found`) + } + + res.json(item.toClient()) + } + + async deleteItem(req, res, next) { + const isAdmin = req.user.administrator + + if (!isAdmin) { + return new createError.Forbidden() + } + + const ItemModel = this.model + const _id = req.params._id + const item = await ItemModel.remove({ _id }) + + if (!item) { + throw createError.NotFound(`Item with _id ${_id} not found`) + } + + res.json({}) + } +} diff --git a/server/src/api/routes/TeamRoutes.js b/server/src/api/routes/TeamRoutes.js index b8cd9a0..696cd5e 100644 --- a/server/src/api/routes/TeamRoutes.js +++ b/server/src/api/routes/TeamRoutes.js @@ -3,45 +3,14 @@ import createError from "http-errors" import autobind from "autobind-decorator" import zlib from "zlib" import { Readable } from "stream" -import { catchAll } from "." +import { catchAll, BaseRoutes } from "." @autobind -export class TeamRoutes { +export class TeamRoutes extends BaseRoutes { constructor(container) { - const app = container.app + super(container, container.db.Team) - this.log = container.log - this.db = container.db - this.mq = container.mq - this.ws = container.ws - - app - .route("/teams") - .get( - passport.authenticate("bearer", { session: false }), - catchAll(this.listTeams) - ) - .post( - passport.authenticate("bearer", { session: false }), - catchAll(this.createTeam) - ) - .put( - passport.authenticate("bearer", { session: false }), - catchAll(this.updateTeam) - ) - - app - .route("/teams/:_id([a-f0-9]{24})") - .get( - passport.authenticate("bearer", { session: false }), - catchAll(this.getTeam) - ) - .delete( - passport.authenticate("bearer", { session: false }), - catchAll(this.deleteTeam) - ) - - app + container.app .route("/teams/status") .get( passport.authenticate("bearer", { session: false }), @@ -49,109 +18,6 @@ export class TeamRoutes { ) } - async listTeams(req, res, next) { - const Team = this.db.Team - let limit = req.query.limit || 20 - let skip = req.query.skip || 0 - let partial = !!req.query.partial - let query = {} - - const total = await Team.count({}) - let teams = [] - let cursor = Team.find(query) - .limit(limit) - .skip(skip) - .cursor() - .map((doc) => { - return doc.toClient(partial) - }) - - cursor.on("data", (doc) => { - teams.push(doc) - }) - cursor.on("end", () => { - res.json({ - total: total, - offset: skip, - count: teams.length, - items: teams, - }) - }) - cursor.on("error", (err) => { - throw err - }) - } - - async createTeam(req, res, next) { - if (!req.user.administrator) { - throw createError.Forbidden() - } - - // Create a new Team template then assign it to a value in the req.body - const Team = this.db.Team - let team = new Team(req.body) - - const newTeam = await team.save() - - res.json(newTeam.toClient()) - } - - async updateTeam(req, res, next) { - if (!req.user.administrator) { - throw createError.Forbidden() - } - - // Do this here because Mongoose will add it automatically otherwise - if (!req.body._id) { - throw createError.BadRequest("No _id given in body") - } - - let Team = this.db.Team - let team = await Team.findById(req.body._id) - - if (!team) { - throw createError.NotFound(`Team with _id ${req.body_id} was not found`) - } - - let teamUpdates = new Team(req.body) - - team.merge(teamUpdates) - - const savedTeam = await team.save() - - res.json(savedTeam.toClient()) - } - - async getTeam(req, res, next) { - const Team = this.db.Team - const _id = req.params._id - - const team = await Team.findById(_id) - - if (!team) { - throw createError.NotFound(`Team with _id ${_id} not found`) - } - - res.json(team.toClient()) - } - - async deleteTeam(req, res, next) { - if (!req.user.administrator) { - throw createError.Forbidden() - } - - const Team = this.db.Team - const _id = req.params._id - - const removedTeam = await Team.remove({ _id }) - - if (!removedTeam) { - throw createError.NotFound(`Team with _id ${_id} not found`) - } - - res.json({}) - } - async getTeamStatus(req, res, next) { const Team = this.db.Team const Activity = this.db.Activity diff --git a/server/src/api/routes/WorkItemRoutes.js b/server/src/api/routes/WorkItemRoutes.js index a0eb910..d160cc1 100644 --- a/server/src/api/routes/WorkItemRoutes.js +++ b/server/src/api/routes/WorkItemRoutes.js @@ -2,33 +2,14 @@ import passport from "passport" import createError from "http-errors" import autobind from "autobind-decorator" import merge from "deepmerge" -import { catchAll } from "." +import { catchAll, BaseRoutes } from "." @autobind -export class WorkItemRoutes { +export class WorkItemRoutes extends BaseRoutes { constructor(container) { + super(container, container.db.WorkItem) const app = container.app - this.log = container.log - this.db = container.db - this.mq = container.mq - this.ws = container.ws - - app - .route("/workitems") - .get( - passport.authenticate("bearer", { session: false }), - catchAll(this.listWorkItems) - ) - .post( - passport.authenticate("bearer", { session: false }), - catchAll(this.createWorkItem) - ) - .put( - passport.authenticate("bearer", { session: false }), - catchAll(this.updateWorkItem) - ) - app .route("/workitems/activities") .get( @@ -36,17 +17,6 @@ export class WorkItemRoutes { catchAll(this.listWorkItemActivities) ) - app - .route("/workitems/:_id([a-f0-9]{24})") - .get( - passport.authenticate("bearer", { session: false }), - catchAll(this.getWorkItem) - ) - .delete( - passport.authenticate("bearer", { session: false }), - catchAll(this.deleteWorkItem) - ) - app .route("/workitems/all") .delete( @@ -55,40 +25,6 @@ export class WorkItemRoutes { ) } - async listWorkItems(req, res, next) { - const WorkItem = this.db.WorkItem - const limit = req.query.limit || 20 - const skip = req.query.skip || 0 - const partial = !!req.query.partial - let query = {} - - const total = await WorkItem.count({}) - - let workItems = [] - let cursor = WorkItem.find(query) - .limit(limit) - .skip(skip) - .cursor() - .map((doc) => { - return doc.toClient(partial) - }) - - cursor.on("data", (doc) => { - workItems.push(doc) - }) - cursor.on("end", () => { - res.json({ - total: total, - offset: skip, - count: workItems.length, - items: workItems, - }) - }) - cursor.on("error", (err) => { - throw createError.InternalServerError(err.message) - }) - } - async listWorkItemActivities(req, res, next) { const WorkItem = this.db.WorkItem const aggregate = WorkItem.aggregate() @@ -117,83 +53,6 @@ export class WorkItemRoutes { res.json({ items }) } - async createWorkItem(req, res, next) { - const isAdmin = req.user.administrator - - if (!isAdmin) { - return new createError.Forbidden() - } - - // Create a new WorkItem template then assign it to a value in the req.body - const WorkItem = this.db.WorkItem - let workItem = new WorkItem(req.body) - - // Save the workItem (with promise) - If it doesnt, catch and throw error - const newWorkItem = await workItem.save() - - res.json(newWorkItem.toClient()) - } - - async updateWorkItem(req, res, next) { - const isAdmin = req.user.administrator - - if (!isAdmin) { - return new createError.Forbidden() - } - - // Do this here because Mongoose will add it automatically otherwise - if (!req.body._id) { - throw createError.BadRequest("No _id given in body") - } - - let WorkItem = this.db.WorkItem - let workItem = await WorkItem.findById(req.body._id) - - if (!workItem) { - return next( - createError.NotFound(`WorkItem with _id ${req.body_id} was not found`) - ) - } - - const workItemUpdates = new WorkItem(req.body) - - workItem.merge(workItemUpdates) - - const savedWorkItem = await workItem.save() - - res.json(savedWorkItem.toClient()) - } - - async getWorkItem(req, res, next) { - const WorkItem = this.db.WorkItem - const _id = req.params._id - const workItem = await WorkItem.findById(_id) - - if (!workItem) { - throw createError.NotFound(`WorkItem with _id ${_id} not found`) - } - - res.json(workItem.toClient()) - } - - async deleteWorkItem(req, res, next) { - const isAdmin = req.user.administrator - - if (!isAdmin) { - return new createError.Forbidden() - } - - const WorkItem = this.db.WorkItem - const _id = req.params._id - const workItem = await WorkItem.remove({ _id }) - - if (!workItem) { - throw createError.NotFound(`WorkItem with _id ${_id} not found`) - } - - res.json({}) - } - async deleteAllWorkItems(req, res, next) { const Activity = this.db.Activity const WorkItem = this.db.WorkItem diff --git a/server/src/api/routes/index.js b/server/src/api/routes/index.js index 6ad37de..258054a 100644 --- a/server/src/api/routes/index.js +++ b/server/src/api/routes/index.js @@ -1,3 +1,4 @@ +export { BaseRoutes } from "./BaseRoutes" export { AuthRoutes } from "./AuthRoutes" export { AssetRoutes } from "./AssetRoutes" export { UserRoutes } from "./UserRoutes" diff --git a/server/src/database/DB.js b/server/src/database/DB.js index a77e8a8..6df3fdb 100644 --- a/server/src/database/DB.js +++ b/server/src/database/DB.js @@ -1,6 +1,6 @@ import mongoose from "mongoose" import mongodb from "mongodb" -import Grid from "gridfs-stream" +import { GridFSBucket } from "mongodb" import mergePlugin from "mongoose-doc-merge" import autobind from "autobind-decorator" import * as Schemas from "./schemas" @@ -18,9 +18,7 @@ export class DB { autoIndex: !isProduction, }) - this.gridfs = Grid(connection.db, mongoose.mongo) - this.gridfs.findOneAsync = util.promisify(this.gridfs.findOne) - this.gridfs.removeAsync = util.promisify(this.gridfs.remove) + this.gridfs = new GridFSBucket(connection.db) this.User = connection.model("User", Schemas.userSchema) this.WorkItem = connection.model("WorkItem", Schemas.workItemSchema) diff --git a/website/.eslintrc b/website/.eslintrc index 701599a..2ca54d4 100644 --- a/website/.eslintrc +++ b/website/.eslintrc @@ -24,6 +24,7 @@ "space-before-function-paren": ["error", "never"], "comma-dangle": ["error", "only-multiline"], "jsx-quotes": "off", - "quotes": "off" + "quotes": "off", + "indent": 0 } } diff --git a/website/package-lock.json b/website/package-lock.json index 52b2a35..b8e9ebb 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -14167,9 +14167,9 @@ } }, "react-form-binder": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/react-form-binder/-/react-form-binder-1.2.0.tgz", - "integrity": "sha512-VFeiB5nCe01WU5aVJILMw7GLgOPsYJvdJEL9WRz7qecKDZx30sKA5bLDOWHsWQDZhediIr3KLpFkPxj0u89tDg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/react-form-binder/-/react-form-binder-2.0.0.tgz", + "integrity": "sha512-ihqbA3sp8eOOvjN2cSWOC7pfK+ukuRW5+dgpbrDJKnH/wgJ0LSMaJg2d/lX8bc0XO7+KxRJi7mBdizvCT1qhgQ==", "requires": { "eventemitter3": "^2.0.3", "prop-types": "^15.5.10", diff --git a/website/package.json b/website/package.json index 32dc1f5..aeb119f 100644 --- a/website/package.json +++ b/website/package.json @@ -12,7 +12,7 @@ "radium": "^0.22.0", "react": "^16.2.0", "react-dom": "^16.2.0", - "react-form-binder": "^1.2.0", + "react-form-binder": "^2.0.0", "react-router-dom": "^4.1.1", "regexp-pattern": "^1.0.4", "socket.io-client": "^2.0.3" diff --git a/website/src/API.js b/website/src/API.js index cc8e03f..c34ba85 100644 --- a/website/src/API.js +++ b/website/src/API.js @@ -2,7 +2,7 @@ import EventEmitter from "eventemitter3" import io from "socket.io-client" import autobind from "autobind-decorator" -const authTokenName = "AuthToken" +const authTokenKeyName = "AuthToken" class NetworkError extends Error { constructor(message) { @@ -33,30 +33,31 @@ class APIError extends Error { class API extends EventEmitter { constructor() { super() - this.user = null let token = - localStorage.getItem(authTokenName) || - sessionStorage.getItem(authTokenName) + localStorage.getItem(authTokenKeyName) || + sessionStorage.getItem(authTokenKeyName) if (token) { - this.token = token - this.user = { pending: true } + this._token = token + this._user = { pending: true } this.who() .then((user) => { - this.user = user + this._user = user this.connectSocket() this.emit("login") }) .catch(() => { - localStorage.removeItem(authTokenName) - sessionStorage.removeItem(authTokenName) - this.token = null - this.user = null + localStorage.removeItem(authTokenKeyName) + sessionStorage.removeItem(authTokenKeyName) + this._token = null + this._user = {} this.socket = null this.emit("logout") }) + } else { + this._user = {} } } @@ -64,7 +65,7 @@ class API extends EventEmitter { this.socket = io(window.location.origin, { path: "/api/socketio", query: { - auth_token: this.token, + auth_token: this._token, }, }) this.socket.on("disconnect", (reason) => { @@ -77,10 +78,10 @@ class API extends EventEmitter { // Filter the few massages that affect our cached user data to avoid a server round trip switch (eventName) { case "newThumbnailImage": - this.user.thumbnailImageId = eventData.imageId + this._user.thumbnailImageId = eventData.imageId break case "newProfileImage": - this.user.imageId = eventData.imageId + this._user.imageId = eventData.imageId break default: // Nothing to see here... @@ -99,15 +100,15 @@ class API extends EventEmitter { } get loggedInUser() { - return this.user + return this._user } makeImageUrl(id, size) { if (id) { - return "/api/assets/" + id + "?access_token=" + this.token + return "/api/assets/" + id + "?access_token=" + this._token } else if (size && size.width && size.height) { return `/api/placeholders/${size.width}x${size.height}?access_token=${ - this.token + this._token }` } else { return null @@ -115,11 +116,11 @@ class API extends EventEmitter { } makeAssetUrl(id) { - return id ? "/api/assets/" + id + "?access_token=" + this.token : null + return id ? "/api/assets/" + id + "?access_token=" + this._token : null } makeTeamStatusUrl() { - return `/api/teams/status?access_token=${this.token}` + return `/api/teams/status?access_token=${this._token}` } static makeParams(params) { @@ -140,8 +141,8 @@ class API extends EventEmitter { cache: "no-store", } let headers = new Headers() - if (this.token) { - headers.set("Authorization", "Bearer " + this.token) + if (this._token) { + headers.set("Authorization", "Bearer " + this._token) } if (method === "POST" || method === "PUT") { if (requestOptions.binary) { @@ -207,12 +208,12 @@ class API extends EventEmitter { } if (remember) { - localStorage.setItem(authTokenName, token) + localStorage.setItem(authTokenKeyName, token) } else { - sessionStorage.setItem(authTokenName, token) + sessionStorage.setItem(authTokenKeyName, token) } - this.token = token - this.user = response.body + this._token = token + this._user = response.body this.connectSocket() this.emit("login") resolve(response.body) @@ -225,10 +226,10 @@ class API extends EventEmitter { logout() { let cb = () => { // Regardless of response, always logout in the client - localStorage.removeItem(authTokenName) - sessionStorage.removeItem(authTokenName) - this.token = null - this.user = null + localStorage.removeItem(authTokenKeyName) + sessionStorage.removeItem(authTokenKeyName) + this._token = null + this._user = {} this.disconnectSocket() this.emit("logout") } @@ -256,6 +257,8 @@ class API extends EventEmitter { return this.post("/auth/password/reset", passwords) } + // Users + getUser(id) { return this.get("/users/" + id) } @@ -271,16 +274,16 @@ class API extends EventEmitter { updateUser(user) { return new Promise((resolve, reject) => { this.put("/users", user) - .then((user) => { + .then((updatedUser) => { // If we just updated ourselves, update the internal cached copy - if (user._id === this.user._id) { - this.user = user + if (updatedUser._id === this._user._id) { + this._user = updatedUser this.emit("login") } - resolve(user) + resolve(updatedUser) }) - .catch((reason) => { - reject(reason) + .catch((error) => { + reject(error) }) }) } diff --git a/website/src/App.js b/website/src/App.js index 6989329..d36c784 100644 --- a/website/src/App.js +++ b/website/src/App.js @@ -1,4 +1,4 @@ -import React, { Component, Fragment } from "react" +import React, { Component } from "react" import { Login, Logout, @@ -13,160 +13,67 @@ import { Profile } from "./Profile" import { Users } from "./Users" import { Teams } from "./Teams" import { System } from "./System" -import { HeaderButton, HeaderText, Column, Row, Text, Box } from "ui" -import { BrowserRouter as Router, Route, Switch } from "react-router-dom" -import logoImage from "images/logo.png" -import { versionInfo } from "./version" -import { sizeInfo, colorInfo } from "ui/style" -import { api } from "src/API" +import { Header, Column, Footer } from "ui" +import { BrowserRouter, Route, Switch } from "react-router-dom" +import { sizeInfo } from "ui/style" import PropTypes from "prop-types" -import autobind from "autobind-decorator" +import { versionInfo } from "./version" export class App extends Component { static propTypes = { history: PropTypes.oneOfType([PropTypes.array, PropTypes.object]), } - constructor(props) { - super(props) - this.state = { - loggedInUser: api.loggedInUser, - } - } - - componentDidMount() { - api.addListener("login", this.handleUpdate) - api.addListener("logout", this.handleUpdate) - } - - componentWillUnmount() { - api.removeListener("login", this.handleUpdate) - api.removeListener("logout", this.handleUpdate) - } - - @autobind - handleUpdate() { - this.setState({ loggedInUser: api.loggedInUser }) - } - - handleLogout() { - // We have to use window here because App does not have history in it's props - window.location.replace("/logout") - } - - handleHome() { - window.location.replace("/") - } - - handleProfile() { - window.location.replace("/profile") - } - - @autobind - handleChangeTitle(title) { - this.setState({ title }) - } - render() { - const { loggedInUser } = this.state - let headerButtonsRight = null - let headerButtonsLeft = null - - if (loggedInUser) { - headerButtonsLeft = ( - - - - - ) - headerButtonsRight = ( - - - - - ) - } - return ( - + - - - - {headerButtonsLeft} - - {headerButtonsRight} - - - + ( + +
+ + )} + /> - ( - - )} - /> - ( - - )} - /> - ( - - )} - /> - ( - - )} - /> - ( - - )} - /> - + + + + + + - - - - {"v" + versionInfo.fullVersion} {versionInfo.copyright} - - - + ( + +