From 27ed934590cc779dae37ab2e831eb46ca3160ad0 Mon Sep 17 00:00:00 2001 From: Matthew Walowski Date: Sat, 16 Jan 2021 16:43:40 -0600 Subject: [PATCH] Member profile page (#23) * Create basic endpoint skeleton * get endpoint 500 error * Fix routing * Get member endpoint to work * Return more fields from endpoint * Return object id from endpoint * Add filterSensitiveInfo util * Give all non-required Member fields a default of null * Move passport-setup to utils * Remove console.log * Add members endpoints * Fix filterSensitiveInfo util * Use passport-setup.js in utils * Use auth endpoint * Move /api/auth/user to /api/members/current * Add omit fields to GET /:memberId * Change current user endpoint in frontend * Add more auth utils * Use isDirector from module in members routes * Change level default in model back to TBD * Add level priority explanation * Add detailed permission checking in members endpoint * Fix difference function * Fix typos in user-utils * Add _id as neverEditable field * Fix allFields not being checked correctly * Filter viewable fields when returning from PUT * Pull enum options from backend * Add status options to endpoint * Disable dropdowns * Add labels to fields * Create wrapper for getting member by ID * Add boolean selector * Add number fields * Add fields for basic string attributes * Retrieve schema type from DB * Move preprocessing to backend * Fix enum type detection * Remove duplicated code * Fix attribute change error * Run formatter * Remove warnings * Format client * Populate edit fields from DB * Add types to props * Run formatter * Remove alert * Run formatter * Get permissions from backend * Disable input boxes if missing permissions * Add enum dropdowns * Format client * Remove user ID override * Change to ES6 defaults * Remove unused package * Change var to let * Concisen member options endpoint * Delete env file * Unexport schema * Use concise property iteration * Make /options endpoint concise * Remove unneeded exports * Capitalize constant * Shorten attribute change callback * Run formatter * Turn off default-props in linter * Fix var name typo * Change member page routing * Run formatter * Use useParams hook * Change object syntax * Run formatter * Link "View Profile" to current logged-in user * Fix lint, ignore _id * Run format * Don't show mongo ID * Rename Member.js to Profile.js * Run formatter * Store fields in value instead of placeholder Co-authored-by: ishaansharma Co-authored-by: Jeffrey Tang --- api/config/dev.env | 5 - api/src/api/members.js | 49 + client/.eslintrc.json | 2 + client/package.json | 5 +- client/src/App.js | 7 +- .../EditableAttribute/BooleanAttribute.js | 35 + .../EditableAttribute/DateAttribute.js | 35 + .../EditableAttribute/EnumAttribute.js | 40 + .../EditableAttribute/StringAttribute.js | 37 + client/src/components/Profile/Profile.js | 14 +- client/src/components/navbar/Navbar.js | 10 +- client/src/index.js | 1 + client/src/pages/Member.js | 5 - client/src/pages/Profile.js | 159 +++ client/src/routes.js | 2 +- client/src/utils/apiWrapper.js | 60 ++ client/yarn.lock | 979 +++++++++++++++++- 17 files changed, 1397 insertions(+), 48 deletions(-) delete mode 100644 api/config/dev.env create mode 100644 client/src/components/EditableAttribute/BooleanAttribute.js create mode 100644 client/src/components/EditableAttribute/DateAttribute.js create mode 100644 client/src/components/EditableAttribute/EnumAttribute.js create mode 100644 client/src/components/EditableAttribute/StringAttribute.js delete mode 100644 client/src/pages/Member.js create mode 100644 client/src/pages/Profile.js diff --git a/api/config/dev.env b/api/config/dev.env deleted file mode 100644 index c74a452..0000000 --- a/api/config/dev.env +++ /dev/null @@ -1,5 +0,0 @@ -MONGO_URL= -GOOGLE_CLIENT_ID= -GOOGLE_CLIENT_SECRET= -SESSION_SECRET=keyboard cat -OAUTH_CALLBACK_URI=http://localhost:9000/api/auth/redirectURI \ No newline at end of file diff --git a/api/src/api/members.js b/api/src/api/members.js index 2620ff1..3c52bb6 100644 --- a/api/src/api/members.js +++ b/api/src/api/members.js @@ -72,6 +72,55 @@ router.post( }), ); +// Gives the enum options to the frontend to populate the dropdown. +// The key in the 'options' object must be the same as the DB attribute. +router.get( + '/options', + requireRegistered, + errorWrap(async (req, res) => { + const options = {}; + Member.schema.eachPath((pathname, schemaType) => { + const { enum: optionsEnum } = schemaType.options; + if (optionsEnum) { + options[pathname] = optionsEnum.map((option) => ({ + label: option, + value: option, + })); + } + }); + + res.json({ + success: true, + result: options, + }); + }), +); + +// Returns the types of all the schema properties for member. This allows the frontend +// to decide the best way to provide input for each attribute. +// Note: This will only work for non-nested fields +router.get( + '/schema', + requireRegistered, + errorWrap(async (req, res) => { + const schemaTypes = { Enum: [] }; + + Member.schema.eachPath((pathname, schemaType) => { + if (schemaTypes[schemaType.instance] == null) + schemaTypes[schemaType.instance] = []; + + if (schemaType.enumValues != null && schemaType.enumValues.length > 0) + schemaTypes['Enum'].push(pathname); + else schemaTypes[schemaType.instance].push(pathname); + }); + + res.json({ + success: true, + result: schemaTypes, + }); + }), +); + router.get( '/:memberId', requireRegistered, diff --git a/client/.eslintrc.json b/client/.eslintrc.json index 87277f6..26c3847 100644 --- a/client/.eslintrc.json +++ b/client/.eslintrc.json @@ -12,6 +12,8 @@ ], "plugins": ["flowtype", "import", "react", "react-hooks"], "rules": { + "react/require-default-props": "off", + "no-underscore-dangle": ["error", { "allow": ["_id"] }], "import/order": [ "warn", { diff --git a/client/package.json b/client/package.json index ff0d96b..9dcb5e7 100644 --- a/client/package.json +++ b/client/package.json @@ -4,15 +4,18 @@ "homepage": "https://pineapple.lol/", "license": "MIT", "dependencies": { + "@hack4impact-uiuc/bridge": "^1.1.0", "ag-grid-community": "^25.0.0", "ag-grid-enterprise": "^25.0.0", "ag-grid-react": "^25.0.0", "axios": "^0.21.1", "prop-types": "^15.7.2", "react": "^16.13.1", + "react-datepicker": "^3.4.1", "react-dom": "^16.13.1", "react-router-dom": "^5.2.0", - "react-scripts": "3.4.3" + "react-scripts": "3.4.3", + "react-select": "^3.2.0" }, "devDependencies": { "@babel/cli": "^7.6.2", diff --git a/client/src/App.js b/client/src/App.js index 3c37be7..adf7c3b 100644 --- a/client/src/App.js +++ b/client/src/App.js @@ -3,7 +3,7 @@ import { Route, Switch, Redirect, useLocation } from 'react-router-dom'; import * as Routes from './routes'; import Home from './pages/Home'; -import Member from './pages/Member'; +import Profile from './pages/Profile'; import Login from './pages/Login'; import Navbar from './components/navbar/Navbar'; import { getUserAuth } from './utils/apiWrapper'; @@ -31,8 +31,9 @@ function App() { {user ? : } - - {user ? : } + + {/* TODO: Replace this with the users ID once we get real data in the DB */} + {user ? : } diff --git a/client/src/components/EditableAttribute/BooleanAttribute.js b/client/src/components/EditableAttribute/BooleanAttribute.js new file mode 100644 index 0000000..d26ef4a --- /dev/null +++ b/client/src/components/EditableAttribute/BooleanAttribute.js @@ -0,0 +1,35 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +import EnumAttribute from './EnumAttribute'; + +const BooleanAttribute = ({ + value = '', + attributeLabel = '', + isDisabled = false, + onChange, +}) => { + const VALUE_OPTIONS = [ + { label: 'Yes', value: 'true' }, + { label: 'No', value: 'false' }, + ]; + + return ( + + ); +}; + +BooleanAttribute.propTypes = { + value: PropTypes.string, + attributeLabel: PropTypes.string, + isDisabled: PropTypes.bool, + onChange: PropTypes.func.isRequired, +}; + +export default BooleanAttribute; diff --git a/client/src/components/EditableAttribute/DateAttribute.js b/client/src/components/EditableAttribute/DateAttribute.js new file mode 100644 index 0000000..b3431c5 --- /dev/null +++ b/client/src/components/EditableAttribute/DateAttribute.js @@ -0,0 +1,35 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import DatePicker from 'react-datepicker'; +import 'react-datepicker/dist/react-datepicker.css'; + +const DateAttribute = ({ + value = '', + attributeLabel = '', + isDisabled = false, + onChange, +}) => { + const onValueChange = date => { + onChange(date, attributeLabel); + }; + + return ( +
+

{attributeLabel}

+ +
+ ); +}; + +DateAttribute.propTypes = { + value: PropTypes.string, + attributeLabel: PropTypes.string, + isDisabled: PropTypes.bool, + onChange: PropTypes.func.isRequired, +}; + +export default DateAttribute; diff --git a/client/src/components/EditableAttribute/EnumAttribute.js b/client/src/components/EditableAttribute/EnumAttribute.js new file mode 100644 index 0000000..4499fb3 --- /dev/null +++ b/client/src/components/EditableAttribute/EnumAttribute.js @@ -0,0 +1,40 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import Select from 'react-select'; + +const EnumAttribute = ({ + value = '', + valueOptions = [], + attributeLabel = '', + isDisabled = false, + onChange, +}) => { + const onValueChange = option => { + onChange(option, attributeLabel); + }; + + return ( +
+

{attributeLabel}

+