Fixed major bug with frontend not hissint correct routes

This commit is contained in:
Ceferino Patino 2022-03-13 14:13:22 -05:00
commit a44f8a6503
22 changed files with 318 additions and 149 deletions

View file

@ -17,7 +17,6 @@
"@testing-library/react": "^12.1.3",
"@testing-library/user-event": "^13.5.0",
"axios": "^0.26.0",
"perfect-scrollbar": "^1.5.5",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-router-dom": "^6.2.2",
@ -10996,10 +10995,6 @@
"node": ">=8"
}
},
"node_modules/perfect-scrollbar": {
"version": "1.5.5",
"license": "MIT"
},
"node_modules/performance-now": {
"version": "2.1.0",
"license": "MIT"
@ -21663,9 +21658,6 @@
"path-type": {
"version": "4.0.0"
},
"perfect-scrollbar": {
"version": "1.5.5"
},
"performance-now": {
"version": "2.1.0"
},

View file

@ -1,46 +1,45 @@
{
"name": "client",
"version": "0.1.0",
"private": true,
"dependencies": {
"@emotion/react": "^11.8.1",
"@emotion/styled": "^11.8.1",
"@mui/icons-material": "^5.4.4",
"@mui/material": "^5.4.4",
"@mui/x-data-grid": "^5.6.0",
"@testing-library/jest-dom": "^5.16.2",
"@testing-library/react": "^12.1.3",
"@testing-library/user-event": "^13.5.0",
"axios": "^0.26.0",
"perfect-scrollbar": "^1.5.5",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-router-dom": "^6.2.2",
"react-scripts": "5.0.0",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
"name": "client",
"version": "0.1.0",
"private": true,
"dependencies": {
"@emotion/react": "^11.8.1",
"@emotion/styled": "^11.8.1",
"@mui/icons-material": "^5.4.4",
"@mui/material": "^5.4.4",
"@mui/x-data-grid": "^5.6.0",
"@testing-library/jest-dom": "^5.16.2",
"@testing-library/react": "^12.1.3",
"@testing-library/user-event": "^13.5.0",
"axios": "^0.26.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-router-dom": "^6.2.2",
"react-scripts": "5.0.0",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

View file

@ -28,9 +28,6 @@
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<script>
const ps = new PerfectScrollbar('root');
</script>
<div id="root"></div>
<!--
This HTML file is a template.

View file

@ -11,8 +11,10 @@ import EditUser from './components/forms/EditUser';
import Recover from './components/forms/Recover';
import Reset from './components/forms/Reset';
import JoinedOrgs from './components/orgs/JoinedOrgs';
import OwnedOrgs from './components/orgs/OwnedOrgs';
import MemberOrgs from './components/orgs/MemberOrgs';
import OrgDashboard from './components/orgs/OrgDashboard';
import UpdateOrganization from './components/forms/UpdateOrganization';
function App() {
return (
@ -30,10 +32,16 @@ function App() {
<Route path=':id' element={<Reset />} />
</Route>
<Route path='org' element={<JoinedOrgs />} />
<Route path='org' element={<OwnedOrgs />} />
<Route path='joined'>
<Route index element={<JoinedOrgs />} />
<Route path=':id' element={<OrgDashboard />} />
<Route index element={<MemberOrgs />} />
<Route path=':id'>
<Route index element={<OrgDashboard />} />
<Route
path='update'
element={<UpdateOrganization />}
/>
</Route>
</Route>
<Route path='*' element={<Navigate to='' />} />

View file

@ -1,5 +1,5 @@
import React, { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import { Link, useNavigate } from 'react-router-dom';
import axios from 'axios';
import Cookies from 'js-cookie';
@ -18,22 +18,30 @@ import OrgUnit from './utils/OrgUnit';
import StackItem from './utils/StackItem';
function MainPage() {
const navigate = useNavigate();
const [ownedOrgs, setOwnedOrgs] = useState([]);
const [orgs, setOrgs] = useState([]);
const [form, setForm] = useState({
name: '',
value: '',
id: '',
});
const linkStyle = { textDecoration: 'none', color: 'inherit' };
useEffect(() => {
if (!Cookies.get('token')) {
navigate('/login');
}
});
useEffect(() => {
async function getOwnedOrgs() {
await axios
.get(
`${process.env.DOMAIN_ROOT}/auth?token=${Cookies.get(
'token'
)}`
`${
process.env.REACT_APP_DOMAIN_ROOT
}/auth?token=${Cookies.get('token')}`
)
.then((response) => {
setOwnedOrgs(() =>
@ -55,9 +63,9 @@ function MainPage() {
async function getOrgs() {
await axios
.get(
`${process.env.DOMAIN_ROOT}/auth/orgs?token=${Cookies.get(
'token'
)}`
`${
process.env.REACT_APP_DOMAIN_ROOT
}/auth/orgs?token=${Cookies.get('token')}`
)
.then((response) => {
setOrgs(() =>
@ -84,6 +92,26 @@ function MainPage() {
});
}
async function joinOrg(e) {
e.preventDefault();
await axios.post(`${process.env.REACT_APP_DOMAIN_ROOT}/org/create`, {
token: Cookies.get('token'),
orgName: form.name,
});
}
async function createOrg(e) {
e.preventDefault();
await axios.post(
`${process.env.REACT_APP_DOMAIN_ROOT}/org/${form.id}`,
{
token: Cookies.get('token'),
}
);
}
return (
<React.Fragment>
<Box sx={{ flexGrow: 1 }}>
@ -97,13 +125,13 @@ function MainPage() {
<Grid item xs={10} sm={6} md={5}>
<Stack spacing={2}>
<StackItem text='Join an Organization'>
<form>
<form onSubmit={joinOrg}>
<FormGroup row>
<TextField
id='standard-name'
label='Organization ID'
name='orgID'
value={form.orgID}
name='id'
value={form.id}
onChange={handleChange}
sx={{ width: '75%' }}
/>
@ -120,7 +148,7 @@ function MainPage() {
</form>
</StackItem>
<StackItem text='Create an Organization'>
<form>
<form onSubmit={createOrg}>
<FormGroup row>
<TextField
id='standard-name'

View file

@ -1,5 +1,5 @@
import React, { useEffect, useState } from 'react';
import { Link, Outlet, useNavigate } from 'react-router-dom';
import { Link, Outlet } from 'react-router-dom';
import axios from 'axios';
import Cookies from 'js-cookie';
@ -16,7 +16,6 @@ import MenuIcon from '@mui/icons-material/Menu';
import { ThemeProvider, createTheme } from '@mui/material/styles';
function Navbar() {
const navigate = useNavigate();
const [token, setToken] = useState('');
const linkStyle = { textDecoration: 'none', color: 'inherit' };
@ -28,17 +27,15 @@ function Navbar() {
useEffect(() => {
setToken(() => Cookies.get('token'));
if (!token) {
navigate('login');
}
}, [navigate, token]);
}, []);
async function handleLogout() {
await axios.post(`${process.env.DOMAIN_ROOT}/auth/logout`, {
await axios.post(`${process.env.REACT_APP_DOMAIN_ROOT}/auth/logout`, {
token: Cookies.get('token'),
});
Cookies.remove('token');
Cookies.remove('userID');
setToken(() => '');
}
return (

View file

@ -25,7 +25,7 @@ function EditUser() {
async function getInfo() {
axios
.get(
`${process.env.DOMAIN_ROOT}/auth/${Cookies.get(
`${process.env.REACT_APP_DOMAIN_ROOT}/auth/${Cookies.get(
'userID'
)}?token=${Cookies.get('token')}`
)
@ -59,7 +59,10 @@ function EditUser() {
const { confirmPassword, ...filteredForm } = form;
await axios
.post(`${process.env.DOMAIN_ROOT}/auth/register`, filteredForm)
.post(
`${process.env.REACT_APP_DOMAIN_ROOT}/auth/register`,
filteredForm
)
.then(() => {
navigate('/login');
})

View file

@ -29,7 +29,7 @@ function Login() {
if (Cookies.get('token')) {
async function logout() {
await axios
.post(`${process.env.DOMAIN_ROOT}/auth/logout`, {
.post(`${process.env.REACT_APP_DOMAIN_ROOT}/auth/logout`, {
token: Cookies.get('token'),
})
.catch((e) => console.log(e));
@ -38,7 +38,7 @@ function Login() {
Cookies.remove('token');
Cookies.remove('userID');
}
});
}, []);
function handleChange(e) {
const name = e.target.name;
@ -61,7 +61,7 @@ function Login() {
async function handleSubmit(e) {
e.preventDefault();
await axios
.post(`${process.env.DOMAIN_ROOT}/auth/login`, form)
.post(`${process.env.REACT_APP_DOMAIN_ROOT}/auth/login`, form)
.then((response) => {
Cookies.set('token', response.data.token, {
expires: form.remember ? 3650 : 1,

View file

@ -24,7 +24,9 @@ function Recover() {
async function handleSubmit(e) {
e.preventDefault();
await axios
.get(`${process.env.DOMAIN_ROOT}/auth/reset-password?${form.email}`)
.get(
`${process.env.REACT_APP_DOMAIN_ROOT}/auth/reset-password?${form.email}`
)
.then((response) => {
setSuccess(() => response.data);
})

View file

@ -34,7 +34,10 @@ function Register() {
const { confirmPassword, ...filteredForm } = form;
await axios
.post(`${process.env.DOMAIN_ROOT}/auth/register`, filteredForm)
.post(
`${process.env.REACT_APP_DOMAIN_ROOT}/auth/register`,
filteredForm
)
.then(() => {
navigate('/login');
})

View file

@ -29,9 +29,12 @@ function Reset() {
async function handleSubmit(e) {
e.preventDefault();
await axios
.patch(`${process.env.DOMAIN_ROOT}/auth/reset-password/${id}`, {
password: form.password,
})
.patch(
`${process.env.REACT_APP_DOMAIN_ROOT}/auth/reset-password/${id}`,
{
password: form.password,
}
)
.then(() => {
navigate('/login');
})

View file

@ -0,0 +1,60 @@
import React, { useState } from 'react';
import { useParams } from 'react-router-dom';
import axios from 'axios';
import { Typography, TextField, Button } from '@mui/material';
import Form from '../utils/Form';
function UpdateOrganization() {
const { orgID } = useParams();
const [name, setName] = useState('');
function handleChange(e) {
const value = e.target.value;
setName(() => value);
}
async function handleSubmit(e) {
e.preventDefault();
await axios.patch(`${process.env.REACT_APP_DOMAIN_ROOT}/org/update`, {
orgName: name,
orgID,
});
}
return (
<Form>
<form onSubmit={handleSubmit}>
<Typography variant='h4' sx={{ marginTop: '1vh 0vh' }}>
Rename Organization
</Typography>
<TextField
required
fullWidth
id='outline-required'
label='name'
name='name'
variant='outlined'
onChange={handleChange}
value={name}
sx={{ margin: '1vh 0vh' }}
/>
<Button
variant='contained'
color='primary'
sx={{ margin: '1vh 0vh' }}
type='submit'
fullWidth
>
Rename
</Button>
</form>
</Form>
);
}
export default UpdateOrganization;

View file

@ -9,9 +9,9 @@ function JoinedOrgs() {
async function getOrgs(setOrgs) {
axios
.get(
`${process.env.DOMAIN_ROOT}/auth/orgs?token=${Cookies.get(
'token'
)}`
`${
process.env.REACT_APP_DOMAIN_ROOT
}/auth/orgs?token=${Cookies.get('token')}`
)
.then((response) => {
setOrgs(() =>

View file

@ -1,5 +1,5 @@
import React, { useState, useEffect } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { useNavigate, useParams, Link } from 'react-router-dom';
import axios from 'axios';
import Cookies from 'js-cookie';
@ -19,26 +19,19 @@ function OrgDashboard() {
const { orgID } = useParams();
const navigate = useNavigate();
const [org, setOrg] = useState({
orgID: '',
orgName: '',
orgOwner: {
firstName: '',
lastName: '',
email: '',
},
orgMembers: [],
createdAt: '',
});
const [org, setOrg] = useState({});
const [rows, setRows] = useState([]);
const [status, setStatus] = useState('');
const [selection, setSelection] = useState();
const linkStyle = { textDecoration: 'none', color: 'inherit' };
useEffect(() => {
async function getData() {
await axios
.get(
`${
process.env.DOMAIN_ROOT
process.env.REACT_APP_DOMAIN_ROOT
}/org/${orgID}?token=${Cookies.get('token')}`
)
.then((response) => {
@ -59,18 +52,15 @@ function OrgDashboard() {
const orgMembers = res.orgMembers.map((member, index) => {
return {
id: index,
firstName: member.firstName,
lastName: member.lastName,
nickname: member.nickname,
namePronounciation: member.pronounciation,
pronouns: member.pronouns,
email: member.email,
id: member.userID,
...member,
};
});
setRows(() => orgMembers);
setStatus(() => res.status);
})
.catch((error) => {
.catch(() => {
navigate('/');
});
}
@ -78,8 +68,46 @@ function OrgDashboard() {
getData();
});
async function handleDelete() {
await axios
.delete(`${process.env.REACT_APP_DOMAIN_ROOT}/org/delete`, {
params: {
token: Cookies.get('token'),
orgID,
},
})
.then(navigate(''));
}
function copyID() {
navigator.clipboard.writeText(orgID);
}
async function removeSelected() {
const doomedUserIDs = selection.map((row) => row.id);
await axios.post(
`${process.env.REACT_APP_DOMAIN_ROOT}/org/${orgID}/delete`,
{
token: Cookies.get('token'),
orgID,
doomedUserIDs,
}
);
}
async function leaveOrg() {
await axios.post(
`${process.env.REACT_APP_DOMAIN_ROOT}/org/${orgID}/delete`,
{
token: Cookies.get('token'),
orgID,
}
);
}
return (
<Dashboard rows={rows}>
<Dashboard rows={rows} setSelection={setSelection}>
<Typography variant='h6'>Organizations/{org.orgName}</Typography>
<Box sx={{ marginTop: '1vh' }}>
<Grid container columns={{ xs: 3, md: 12 }}>
@ -116,25 +144,48 @@ function OrgDashboard() {
xs
>
<Stack spacing={2} alignItems='flex-end'>
<ButtonGroup
variant='outlined'
aria-label='outlined button group'
>
<Button color='warning'>
Edit {org.orgName}
{status && (
<React.Fragment>
<ButtonGroup
variant='outlined'
aria-label='outlined button group'
>
<Button color='warning'>
<Link to='update' style={linkStyle}>
Edit {org.orgName}
</Link>
</Button>
<Button
color='error'
onClick={handleDelete}
>
Delete {org.orgName}
</Button>
</ButtonGroup>
<ButtonGroup
variant='outlined'
aria-label='outlined button group'
>
<Button
color='success'
onClick={copyID}
>
Copy Org ID
</Button>
<Button
color='error'
onClick={removeSelected}
>
Remove People
</Button>
</ButtonGroup>
</React.Fragment>
)}
{!status && (
<Button color='error' onClick={leaveOrg}>
Leave Org
</Button>
<Button color='error'>
Delete {org.orgName}
</Button>
</ButtonGroup>
<ButtonGroup
variant='outlined'
aria-label='outlined button group'
>
<Button color='primary'>Refresh</Button>
<Button color='success'>Add People</Button>
<Button color='error'>Remove People</Button>
</ButtonGroup>
)}
</Stack>
</Grid>
</Grid>

View file

@ -9,7 +9,9 @@ function JoinedOrgs() {
async function getOrgs(setOrgs) {
axios
.get(
`${process.env.DOMAIN_ROOT}/auth?token=${Cookies.get('token')}`
`${process.env.REACT_APP_DOMAIN_ROOT}/auth?token=${Cookies.get(
'token'
)}`
)
.then((response) => {
setOrgs(() =>

View file

@ -6,7 +6,7 @@ import { useTheme } from '@mui/material/styles';
import { DataGrid } from '@mui/x-data-grid';
import PlayArrowIcon from '@mui/icons-material/PlayArrow';
function Dashboard({ children, rows }) {
function Dashboard({ children, rows, setSelection }) {
const theme = useTheme();
const columns = [
@ -69,6 +69,9 @@ function Dashboard({ children, rows }) {
columns={columns}
pageSize={5}
rowsPerPageOptions={[5]}
onSelectionModelChange={(newSelection) => {
setSelection(newSelection.rows);
}}
checkboxSelection
/>
</Box>

View file

@ -12,12 +12,12 @@ module.exports = (sequelize, DataTypes) => {
onDelete: 'CASCADE',
});
User.hasMany(models.Organization, {
as: 'ownedOrgs',
as: 'ownedOrg',
foreignKey: 'ownerID',
onDelete: 'CASCADE',
});
User.belongsToMany(models.Organization, {
as: 'memberOrgs',
as: 'memberOrg',
through: 'orgUsers',
foreignKey: 'userID',
onDelete: 'CASCADE',

View file

@ -6,8 +6,11 @@ async function CreateOrg(req, res, next) {
return res.status(400).send('Form missing necessary fields');
}
const existing = await user.getOwnedOrgs();
if (existing.map((org) => org.orgName).includes(orgName)) {
console.log(user);
const existing = await user.countOwnedOrg({
where: { orgName: orgName },
});
if (existing > 0) {
return res
.status(500)
.send(

View file

@ -1,8 +1,8 @@
async function DeleteOrg(req, res, next) {
const user = req.user;
const orgID = req.query.orgID;
const result = await user.getOwnedOrg();
await result.destroy();
await user.removeOwnedOrg({ where: { orgID: orgID } });
return res.send('Organization deleted');
}

View file

@ -9,11 +9,15 @@ async function GetOrg(req, res, next) {
include: [
{
association: 'orgOwner',
attributes: { exclude: ['password', 'createdAt', 'updatedAt'] },
attributes: {
exclude: ['userID', 'password', 'createdAt', 'updatedAt'],
},
},
{
association: 'orgMembers',
attributes: { exclude: ['password', 'createdAt', 'updatedAt'] },
attributes: {
exclude: ['password', 'createdAt', 'updatedAt'],
},
required: false,
},
],

View file

@ -1,18 +1,31 @@
const { Organization } = require('../../models/index');
async function AddOrgMember(req, res, next) {
async function RemoveOrgMember(req, res, next) {
const user = req.user;
const orgID = req.params.orgID;
const doomedUserIDs = req.body.doomedUsers;
const result = await Organization.findOne({ where: { orgID: orgID } });
const result = await Organization.findByPk(orgID, {
include: { association: 'orgOwner' },
});
if (!result) {
return res.status(500).send('No organization exists with that id.');
}
await result.removeOrgMember(user);
if (!doomedUserIDs) {
await result.removeOrgMember({ where: { userID: user.userID } });
} else {
if (user.userID == result.orgOwner.userID) {
await result.removeOrgMembers({
where: { userID: { [Op.in]: doomedUserIDs } },
});
} else {
res.status(403).send('You do not have permission to do that.');
}
}
return res.send('User removed from organization.');
return res.send('User(s) removed from organization.');
}
module.exports = AddOrgMember;
module.exports = RemoveOrgMember;

View file

@ -1,13 +1,14 @@
async function UpdateOrgDetails(req, res, next) {
const user = req.user;
const orgName = req.body.orgName;
const orgID = req.body.orgID;
if (!orgName) {
return res.status(400).send('Form missing required information.');
}
const result = await user.getOwnedOrg();
result.update({ orgName: orgName });
const result = await user.getOwnedOrgs({ where: { orgID: orgID } });
result[0].update({ orgName: orgName });
return res.send(result);
}