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}
-
-
-
+ (
+
+
+
+ )}
+ />
-
+
)
}
}
diff --git a/website/src/Auth/DefaultRoute.js b/website/src/Auth/DefaultRoute.js
index 6886e36..b72eec5 100644
--- a/website/src/Auth/DefaultRoute.js
+++ b/website/src/Auth/DefaultRoute.js
@@ -1,47 +1,14 @@
-import React, { Fragment, Component } from 'react'
-import { api } from 'src/API'
-import { Route, Redirect } from 'react-router-dom'
-import { Column } from 'ui'
-import autobind from 'autobind-decorator'
+import React, { Component } from "react"
+import { Route, Redirect } from "react-router-dom"
+import PropTypes from "prop-types"
export class DefaultRoute extends Component {
- @autobind
- updateComponent() {
- this.forceUpdate()
- }
-
- componentDidMount() {
- api.addListener('login', this.updateComponent)
- }
-
- componentWillUnmount() {
- api.removeListener('login', this.updateComponent)
+ static propTypes = {
+ redirect: PropTypes.string,
}
render() {
- const user = api.loggedInUser
- let path = null
-
- if (user) {
- if (!user.pending) {
- path = user.administrator ? '/home' : '/profile'
- }
- } else {
- path = '/login'
- }
-
- return (
- {
- return (
-
-
- {path ? : null}
-
- )
- }}
- />
- )
+ // NOTE: When working on the site, Redirect to the page you are working on
+ return } />
}
}
diff --git a/website/src/Auth/ForgotPassword.js b/website/src/Auth/ForgotPassword.js
index 28d2909..22eb005 100644
--- a/website/src/Auth/ForgotPassword.js
+++ b/website/src/Auth/ForgotPassword.js
@@ -1,28 +1,28 @@
-import React, { Component, Fragment } from 'react'
-import PropTypes from 'prop-types'
-import { regExpPattern } from 'regexp-pattern'
-import { Image, Text, Column, Row, BoundInput, BoundButton, Box } from 'ui'
-import { MessageModal, WaitModal } from '../Modal'
-import { api } from 'src/API'
-import { FormBinder } from 'react-form-binder'
-import headerLogo from 'images/deighton.png'
-import { sizeInfo, colorInfo } from 'ui/style'
-import autobind from 'autobind-decorator'
+import React, { Component, Fragment } from "react"
+import PropTypes from "prop-types"
+import { regExpPattern } from "regexp-pattern"
+import { Image, Text, Column, Row, BoundInput, BoundButton, Box } from "ui"
+import { MessageModal, WaitModal } from "../Modal"
+import { api } from "src/API"
+import { FormBinder } from "react-form-binder"
+import headerLogo from "images/badge.png"
+import { sizeInfo, colorInfo } from "ui/style"
+import autobind from "autobind-decorator"
export class ForgotPassword extends Component {
static propTypes = {
- history: PropTypes.oneOfType([PropTypes.array, PropTypes.object])
+ history: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
}
static bindings = {
email: {
alwaysGet: true,
- isValid: (r, v) => (regExpPattern.email.test(v))
+ isValid: (r, v) => regExpPattern.email.test(v),
},
submit: {
noValue: true,
- isDisabled: (r) => (!r.anyModified || !r.allValid)
- }
+ isDisabled: (r) => !r.anyModified || !r.allValid,
+ },
}
constructor(props) {
@@ -30,7 +30,7 @@ export class ForgotPassword extends Component {
this.state = {
binder: new FormBinder({}, ForgotPassword.bindings),
messageModal: null,
- waitModal: null
+ waitModal: null,
}
}
@@ -45,16 +45,18 @@ export class ForgotPassword extends Component {
const obj = this.state.binder.getModifiedFieldValues()
- this.setState({ waitModal: { message: 'Requesting Reset Email' } })
+ this.setState({ waitModal: { message: "Requesting Reset Email" } })
const cb = (res) => {
this.setState({
waitModal: null,
messageModal: {
- icon: 'thumb',
- title: 'Password Reset Requested',
- message: `If everything checks out, an email will be sent to '${obj.email}' with a reset link. Please click on it to finish resetting the password.`
- }
+ icon: "thumb",
+ title: "Password Reset Requested",
+ message: `If everything checks out, an email will be sent to '${
+ obj.email
+ }' with a reset link. Please click on it to finish resetting the password.`,
+ },
})
}
@@ -63,7 +65,7 @@ export class ForgotPassword extends Component {
@autobind
handleMessageModalDismiss() {
- this.props.history.replace('/')
+ this.props.history.replace("/")
}
render() {
@@ -77,8 +79,13 @@ export class ForgotPassword extends Component {
-