From 69fd85b63f05c318592f8994ba974eaa3cff1881 Mon Sep 17 00:00:00 2001 From: Nick Yaculak Date: Sun, 5 May 2019 18:21:01 -0400 Subject: [PATCH 1/3] Basic implementation for Admin user page --- app/package-lock.json | 23 ++- app/package.json | 2 + app/server/controllers/UserController.js | 12 +- app/server/routes/api/users.js | 6 +- client/src/components/UserPopover.js | 19 ++ client/src/components/UserStatistics.js | 19 ++ client/src/components/UsersSearch.js | 71 ++++++++ client/src/components/UsersTable.js | 110 +++++++++++- .../src/containers/Admin/AdminStatistics.js | 18 +- client/src/containers/Admin/AdminUsers.js | 168 +++++++++++++++++- client/src/containers/Admin/index.js | 2 +- client/src/layouts/AdminPage.js | 2 + client/src/layouts/Page/BasePage.js | 2 + client/src/layouts/UsersLayout.js | 31 ++++ client/src/services/UserService.js | 47 ++++- client/src/stores/AdminStore.js | 105 +++++++++-- 16 files changed, 600 insertions(+), 37 deletions(-) create mode 100644 client/src/components/UserPopover.js create mode 100644 client/src/components/UserStatistics.js create mode 100644 client/src/components/UsersSearch.js create mode 100644 client/src/layouts/UsersLayout.js diff --git a/app/package-lock.json b/app/package-lock.json index 2bde7e0..d9f5fb0 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -1048,8 +1048,7 @@ "decode-uri-component": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", - "dev": true + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" }, "deep-extend": { "version": "0.5.1", @@ -4873,6 +4872,16 @@ "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" }, + "query-string": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.5.0.tgz", + "integrity": "sha512-TYC4hDjZSvVxLMEucDMySkuAS9UIzSbAiYGyA9GWCjLKB8fQpviFbjd20fD7uejCDxZS+ftSdBKE6DS+xucJFg==", + "requires": { + "decode-uri-component": "^0.2.0", + "split-on-first": "^1.0.0", + "strict-uri-encode": "^2.0.0" + } + }, "range-parser": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", @@ -5540,6 +5549,11 @@ "integrity": "sha512-uBIcIl3Ih6Phe3XHK1NqboJLdGfwr1UN3k6wSD1dZpmPsIkb8AGNbZYJ1fOBk834+Gxy8rpfDxrS6XLEMZMY2g==", "dev": true }, + "split-on-first": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", + "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==" + }, "split-string": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", @@ -5650,6 +5664,11 @@ "integrity": "sha512-tNa3hzgkjEP7XbCkbRXe1jpg+ievoa0O4SCFlMOYEscGSS4JJsckGL8swUyAa/ApGU3Ae4t6Honor4HhL+tRyg==", "dev": true }, + "strict-uri-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", + "integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY=" + }, "string-width": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", diff --git a/app/package.json b/app/package.json index 63d9f99..75caa06 100644 --- a/app/package.json +++ b/app/package.json @@ -13,6 +13,7 @@ "express-async-errors": "^3.1.1", "handlebars": "^4.0.11", "jsonwebtoken": "5.0.4", + "lodash": "^4.17.11", "method-override": "^2.3.5", "moment": "^2.10.3", "mongoose": "^4.13.18", @@ -20,6 +21,7 @@ "nodemailer": "^1.4.0", "nodemailer-smtp-transport": "^1.0.3", "passport-local": "^1.0.0", + "query-string": "^6.5.0", "request": "^2.60.0", "underscore": "^1.8.3", "validator": "^3.40.1" diff --git a/app/server/controllers/UserController.js b/app/server/controllers/UserController.js index 791afed..d22326b 100644 --- a/app/server/controllers/UserController.js +++ b/app/server/controllers/UserController.js @@ -651,7 +651,17 @@ UserController.admitUser = function(id, user, callback){ }, { new: true }, - callback); + function (err, user) { + if (err) { + return callback(err); + } + + if (!user) { + return callback({ message: 'user cannot be admitted' }) + } + + return callback(null, user); + }); }); }; diff --git a/app/server/routes/api/users.js b/app/server/routes/api/users.js index db5e08e..d8a2540 100644 --- a/app/server/routes/api/users.js +++ b/app/server/routes/api/users.js @@ -125,7 +125,7 @@ router.post('/:id/unfavoriteFirebaseEvent/:firebaseId', isOwnerOrAdmin, async fu }); /** - * Admit a user. ADMIN ONLY, DUH + * Admit a user. Admin only. * * Also attaches the user who did the admitting, for liabaility. */ @@ -137,7 +137,7 @@ router.post('/:id/admit', isAdmin, function (req, res) { }); /** - * Check in a user. Admin or Organizer + * Check in a user. Admin or Organizer. */ router.post('/:id/checkin', isOrganizerOrAdmin, function (req, res) { const id = req.params.id; @@ -147,7 +147,7 @@ router.post('/:id/checkin', isOrganizerOrAdmin, function (req, res) { }); /** - * Check in a user. Admin or Organizer + * Check out a user. Admin or Organizer. */ router.post('/:id/checkout', isOrganizerOrAdmin, function (req, res) { const id = req.params.id; diff --git a/client/src/components/UserPopover.js b/client/src/components/UserPopover.js new file mode 100644 index 0000000..08ec94b --- /dev/null +++ b/client/src/components/UserPopover.js @@ -0,0 +1,19 @@ +import React from 'react'; +import { observer } from 'mobx-react'; +import { Modal } from 'semantic-ui-react'; + +const UserPopover = (props) => { + return ( + + {JSON.stringify(props.user, null, '\t')} + } + /> + ); +} + +export default observer(UserPopover); \ No newline at end of file diff --git a/client/src/components/UserStatistics.js b/client/src/components/UserStatistics.js new file mode 100644 index 0000000..7cc9aff --- /dev/null +++ b/client/src/components/UserStatistics.js @@ -0,0 +1,19 @@ +import * as React from 'react'; +import * as _ from 'lodash'; +import { toJS } from 'mobx'; + +class UserStatistics extends React.Component { + render() { + const { stats } = this.props; + const statsToDisplay = toJS(stats); + delete statsToDisplay.demo; + + return ( +
+        {JSON.stringify(statsToDisplay, null, 2)}
+      
+ ) + } +} + +export default UserStatistics; \ No newline at end of file diff --git a/client/src/components/UsersSearch.js b/client/src/components/UsersSearch.js new file mode 100644 index 0000000..a94c2d6 --- /dev/null +++ b/client/src/components/UsersSearch.js @@ -0,0 +1,71 @@ +import React from 'react'; +import { observer } from 'mobx-react'; +import * as _ from 'lodash'; +import { Button, Input, Label, Search } from 'semantic-ui-react'; + +@observer +class UsersSearch extends React.Component { + constructor(props) { + super(props); + + this.state = { + intermediatePageSize: props.pageSize, + }; + } + + componentDidUpdate(prevProps) { + if (prevProps.pageSize !== this.props.pageSize) { + this.setState({ intermediatePageSize: this.props.pageSize }); + } + } + + render() { + return ( +
+
+
+ + + + +
+ ); + } + + handleSearchChange = (e, { value }) => { + this.props.onChangeQuery(value); + } + + handlePageSizeChange = (e, { value }) => { + this.setState({ intermediatePageSize: value }); + } + + handlePageSizeSubmit = () => { + this.props.onChangePageSize(Number(this.state.intermediatePageSize)); + } +} + +export default UsersSearch; \ No newline at end of file diff --git a/client/src/components/UsersTable.js b/client/src/components/UsersTable.js index 4e94bcd..7d52686 100644 --- a/client/src/components/UsersTable.js +++ b/client/src/components/UsersTable.js @@ -1,19 +1,113 @@ import React from 'react'; import { observer } from 'mobx-react'; +import { Button, Icon, Table } from 'semantic-ui-react'; + +const checkIcon = +const xIcon = @observer -class UsersTable extends React.Component { +class UserRow extends React.Component { + render () { + const { user } = this.props; + + let rowStatus = {}; + switch (user.status.name) { + case 'confirmed': + rowStatus = { positive: true }; + break; + case 'admitted': + rowStatus = { warning: true }; + break; + case 'declined': + rowStatus = { error: true }; + break; + default: + break; + }; + + const name = user.status.completedProfile + ? `${user.profile.firstName} ${user.profile.lastName}` + : user.email; + const email = user.email; + const school = user.status.completedProfile ? user.profile.school.trim() : ""; + const year = user.status.completedProfile ? user.profile.schoolYear.trim() : ""; + + const verified = user.verified; + const submitted = user.status.completedProfile; + const admitted = user.status.admitted; + const confirmed = user.status.confirmed; + + const admitButton = ( +