Completed MainPage
This commit is contained in:
parent
3ae95cef00
commit
461603ff3c
26 changed files with 532 additions and 372 deletions
|
|
@ -32,7 +32,7 @@
|
|||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>React App</title>
|
||||
<title>KnowItAll</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
|
|
|
|||
|
|
@ -1,27 +1,25 @@
|
|||
import React from 'react';
|
||||
import { BrowserRouter, Routes, Route } from 'react-router-dom';
|
||||
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
|
||||
|
||||
import Header from './components/Header';
|
||||
import Navbar from './components/Navbar';
|
||||
import MainPage from './components/MainPage';
|
||||
|
||||
import Form from './components/Form';
|
||||
import Login from './components/Login';
|
||||
import Register from './components/Register';
|
||||
import EditUser from './components/EditUser';
|
||||
import Recover from './components/Recover';
|
||||
import Reset from './components/Reset';
|
||||
import Form from './components/utils/Form';
|
||||
import Login from './components/forms/Login';
|
||||
import Register from './components/forms/Register';
|
||||
import EditUser from './components/forms/EditUser';
|
||||
import Recover from './components/forms/Recover';
|
||||
import Reset from './components/forms/Reset';
|
||||
|
||||
import JoinedOrgs from './components/JoinedOrgs';
|
||||
import OrgDashboard from './components/OrgDashboard';
|
||||
|
||||
import NotFound from './components/NotFound';
|
||||
import JoinedOrgs from './components/orgs/JoinedOrgs';
|
||||
import OrgDashboard from './components/orgs/OrgDashboard';
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
<Route path='' element={<Header />}>
|
||||
<Route path='' element={<Navbar />}>
|
||||
<Route index element={<MainPage />} />
|
||||
|
||||
<Route path='login' element={<Login />} />
|
||||
|
|
@ -32,12 +30,13 @@ function App() {
|
|||
<Route path=':id' element={<Reset />} />
|
||||
</Route>
|
||||
|
||||
<Route path=''>
|
||||
<Route path='org' element={<JoinedOrgs />} />
|
||||
<Route path='joined'>
|
||||
<Route index element={<JoinedOrgs />} />
|
||||
<Route path=':id' element={<OrgDashboard />} />
|
||||
</Route>
|
||||
|
||||
<Route path='*' element={<NotFound />} />
|
||||
<Route path='*' element={<Navigate to='' />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
|
|
|
|||
|
|
@ -1,83 +0,0 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import axios from 'axios';
|
||||
import Cookies from 'js-cookie';
|
||||
|
||||
import { Paper, Typography, Box, Stack } from '@mui/material';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
|
||||
function OrgUnit({ org }) {
|
||||
const { orgID, orgName, memberCount, createdAt } = org;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Typography variant='h6'>
|
||||
<Link to={orgID}>Name: {orgName}</Link>
|
||||
</Typography>
|
||||
<Box>
|
||||
<Stack spacing={2} direction={{ xs: 'column', md: 'row' }}>
|
||||
<Typography variant='body1'>
|
||||
Members: {memberCount}
|
||||
</Typography>
|
||||
<Typography variant='body1'>
|
||||
Created On: {createdAt}
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Box>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
function JoinedOrgs() {
|
||||
const theme = useTheme();
|
||||
|
||||
const [orgs, setOrgs] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
async function getOrgs() {
|
||||
axios
|
||||
.get(
|
||||
`${process.env.DOMAIN_ROOT}/auth/orgs?token=${Cookies.get(
|
||||
'token'
|
||||
)}`
|
||||
)
|
||||
.then((response) => {
|
||||
setOrgs(() =>
|
||||
response.data.map((org) => {
|
||||
return {
|
||||
orgID: org.orgID,
|
||||
orgName: org.orgName,
|
||||
memberCount: org.memberCount,
|
||||
createdAt: org.createdAt,
|
||||
};
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
getOrgs();
|
||||
});
|
||||
|
||||
return (
|
||||
<Paper
|
||||
sx={{
|
||||
...theme.typography.body2,
|
||||
padding: '2vh',
|
||||
color: theme.palette.text.secondary,
|
||||
flexGrow: 1,
|
||||
height: '89vh',
|
||||
margin: '2vh',
|
||||
}}
|
||||
>
|
||||
<Typography variant='h6'>Organizations</Typography>
|
||||
<Box sx={{ overflowY: 'auto' }}>
|
||||
{orgs.map((org) => (
|
||||
<OrgUnit org={org} />
|
||||
))}
|
||||
</Box>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
||||
export default JoinedOrgs;
|
||||
|
|
@ -2,62 +2,184 @@ import React, { useState, useEffect } from 'react';
|
|||
import { Link } from 'react-router-dom';
|
||||
|
||||
import axios from 'axios';
|
||||
import Cookies from 'js-cookie';
|
||||
|
||||
import { Paper, Button, Typography, Stack, Box } from '@mui/material';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import {
|
||||
Button,
|
||||
Typography,
|
||||
Box,
|
||||
Grid,
|
||||
Stack,
|
||||
TextField,
|
||||
FormGroup,
|
||||
} from '@mui/material';
|
||||
|
||||
import OrgUnit from './utils/OrgUnit';
|
||||
import StackItem from './utils/StackItem';
|
||||
|
||||
function MainPage() {
|
||||
const theme = useTheme();
|
||||
|
||||
const [org, setOrg] = useState({});
|
||||
const [ownedOrgs, setOwnedOrgs] = useState([]);
|
||||
const [orgs, setOrgs] = useState([]);
|
||||
const [form, setForm] = useState({
|
||||
name: '',
|
||||
value: '',
|
||||
});
|
||||
|
||||
const linkStyle = { textDecoration: 'none', color: 'inherit' };
|
||||
|
||||
useEffect(() => {
|
||||
async function getOwnedOrgs() {
|
||||
await axios
|
||||
.get(
|
||||
`${process.env.DOMAIN_ROOT}/auth?token=${Cookies.get(
|
||||
'token'
|
||||
)}`
|
||||
)
|
||||
.then((response) => {
|
||||
setOwnedOrgs(() =>
|
||||
response.data.map((org) => {
|
||||
return {
|
||||
orgID: org.orgID,
|
||||
orgName: org.orgName,
|
||||
memberCount: org.memberCount,
|
||||
createdAt: org.createdAt,
|
||||
};
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
getOwnedOrgs();
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
async function getOrgs() {
|
||||
await axios
|
||||
.get(
|
||||
`${process.env.DOMAIN_ROOT}/auth/orgs?token=${Cookies.get(
|
||||
'token'
|
||||
)}`
|
||||
)
|
||||
.then((response) => {
|
||||
setOrgs(() =>
|
||||
response.data.map((org) => {
|
||||
return {
|
||||
orgID: org.orgID,
|
||||
orgName: org.orgName,
|
||||
memberCount: org.memberCount,
|
||||
createdAt: org.createdAt,
|
||||
};
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
getOrgs();
|
||||
});
|
||||
|
||||
function handleChange(e) {
|
||||
const name = e.target.name;
|
||||
const value = e.target.value;
|
||||
|
||||
setForm((form) => {
|
||||
return { ...form, [name]: value };
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Paper
|
||||
sx={{
|
||||
...theme.typography.body2,
|
||||
padding: '2vh',
|
||||
color: theme.palette.text.secondary,
|
||||
flexGrow: 1,
|
||||
height: '30vh',
|
||||
margin: '2vh',
|
||||
overflow: 'auto',
|
||||
}}
|
||||
>
|
||||
{org && (
|
||||
<React.Fragment>
|
||||
<Typography variant='h6'>
|
||||
<Link to={`orgs/${org.orgID}`} style={linkStyle}>
|
||||
Your Organization: {org.orgName}
|
||||
</Link>
|
||||
</Typography>
|
||||
<Box margin={2}>
|
||||
<Stack spacing={2}>
|
||||
<Typography variant='body1'>
|
||||
Members: {org.memberCount}
|
||||
<Box sx={{ flexGrow: 1 }}>
|
||||
<Grid
|
||||
container
|
||||
spacing={2}
|
||||
alignItems='center'
|
||||
style={{ height: '95vh' }}
|
||||
>
|
||||
<Grid item xs={1} sm={3} md={3.5} />
|
||||
<Grid item xs={10} sm={6} md={5}>
|
||||
<Stack spacing={2}>
|
||||
<StackItem text='Join an Organization'>
|
||||
<form>
|
||||
<FormGroup row>
|
||||
<TextField
|
||||
id='standard-name'
|
||||
label='Organization ID'
|
||||
name='orgID'
|
||||
value={form.orgID}
|
||||
onChange={handleChange}
|
||||
sx={{ width: '75%' }}
|
||||
/>
|
||||
<Button
|
||||
type='submit'
|
||||
variant='outlined'
|
||||
color='primary'
|
||||
disableElevation
|
||||
sx={{ width: '25%' }}
|
||||
>
|
||||
Join Organization
|
||||
</Button>
|
||||
</FormGroup>
|
||||
</form>
|
||||
</StackItem>
|
||||
<StackItem text='Create an Organization'>
|
||||
<form>
|
||||
<FormGroup row>
|
||||
<TextField
|
||||
id='standard-name'
|
||||
label='Organization Name'
|
||||
name='orgName'
|
||||
value={form.orgName}
|
||||
onChange={handleChange}
|
||||
sx={{ width: '70%' }}
|
||||
/>
|
||||
<Button
|
||||
type='submit'
|
||||
variant='outlined'
|
||||
color='primary'
|
||||
disableElevation
|
||||
sx={{ width: '30%' }}
|
||||
>
|
||||
Create Organization
|
||||
</Button>
|
||||
</FormGroup>
|
||||
</form>
|
||||
</StackItem>
|
||||
<StackItem>
|
||||
<Typography variant='h6'>
|
||||
<Link to='org' style={linkStyle}>
|
||||
Your Organizations
|
||||
</Link>
|
||||
</Typography>
|
||||
<Typography variant='body1'>
|
||||
Created On: {org.createdAt}
|
||||
{ownedOrgs.length === 0 && (
|
||||
<Typography variant='body2'>
|
||||
You do own any organizations
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
{ownedOrgs.map((org) => (
|
||||
<OrgUnit org={org} />
|
||||
))}
|
||||
</StackItem>
|
||||
<StackItem>
|
||||
<Typography variant='h6'>
|
||||
<Link to='joined' style={linkStyle}>
|
||||
Joined Organizations
|
||||
</Link>
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Box>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</Paper>
|
||||
<Paper
|
||||
sx={{
|
||||
...theme.typography.body2,
|
||||
padding: '2vh',
|
||||
textAlign: 'center',
|
||||
color: theme.palette.text.secondary,
|
||||
flexGrow: 1,
|
||||
height: '57vh',
|
||||
margin: '2vh',
|
||||
overflow: 'auto',
|
||||
}}
|
||||
></Paper>
|
||||
{orgs.length === 0 && (
|
||||
<Typography variant='body2'>
|
||||
You are not a member in any
|
||||
organizations
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
{orgs.map((org) => (
|
||||
<OrgUnit org={org} />
|
||||
))}
|
||||
</StackItem>
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid item xs={1} sm={3} md={3.5} />
|
||||
</Grid>
|
||||
</Box>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import {
|
|||
import MenuIcon from '@mui/icons-material/Menu';
|
||||
import { ThemeProvider, createTheme } from '@mui/material/styles';
|
||||
|
||||
function Header() {
|
||||
function Navbar() {
|
||||
const navigate = useNavigate();
|
||||
const [token, setToken] = useState('');
|
||||
|
||||
|
|
@ -28,7 +28,10 @@ function Header() {
|
|||
|
||||
useEffect(() => {
|
||||
setToken(() => Cookies.get('token'));
|
||||
}, [navigate]);
|
||||
if (!token) {
|
||||
navigate('login');
|
||||
}
|
||||
}, [navigate, token]);
|
||||
|
||||
async function handleLogout() {
|
||||
await axios.post(`${process.env.DOMAIN_ROOT}/auth/logout`, {
|
||||
|
|
@ -51,7 +54,9 @@ function Header() {
|
|||
aria-label='menu'
|
||||
sx={{ mr: 2 }}
|
||||
>
|
||||
<MenuIcon />
|
||||
<Link to='/' style={linkStyle}>
|
||||
<MenuIcon />
|
||||
</Link>
|
||||
</IconButton>
|
||||
<Typography
|
||||
variant='h6'
|
||||
|
|
@ -90,4 +95,4 @@ function Header() {
|
|||
);
|
||||
}
|
||||
|
||||
export default Header;
|
||||
export default Navbar;
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
import React from 'react';
|
||||
|
||||
import { Box } from '@mui/material';
|
||||
|
||||
function NotFound() {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Box sx={{ flexGrow: 1 }}>
|
||||
<p>404 Not Found</p>
|
||||
</Box>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
export default NotFound;
|
||||
|
|
@ -1,75 +0,0 @@
|
|||
import React, { useState } from 'react';
|
||||
import axios from 'axios';
|
||||
|
||||
import { Box, Typography, TextField, Button } from '@mui/material';
|
||||
|
||||
function Recover() {
|
||||
const [form, setForm] = useState({
|
||||
email: '',
|
||||
});
|
||||
const [error, setError] = useState('');
|
||||
const [success, setSuccess] = useState('');
|
||||
|
||||
function handleChange(e) {
|
||||
const name = e.target.name;
|
||||
const value = e.target.value;
|
||||
|
||||
setForm((form) => {
|
||||
return { ...form, [name]: value };
|
||||
});
|
||||
}
|
||||
|
||||
async function handleSubmit(e) {
|
||||
e.preventDefault();
|
||||
await axios
|
||||
.get(`${process.env.DOMAIN_ROOT}/auth/reset-password?${form.email}`)
|
||||
.then((response) => {
|
||||
setSuccess(() => response.data);
|
||||
})
|
||||
.catch((e) => setError(() => e.response.data));
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Typography variant='h4' sx={{ marginTop: '1vh 0vh' }}>
|
||||
Recover my Password
|
||||
</Typography>
|
||||
<TextField
|
||||
required
|
||||
fullWidth
|
||||
id='outline-required'
|
||||
label='Email'
|
||||
name='email'
|
||||
variant='outlined'
|
||||
onChange={handleChange}
|
||||
value={form.email}
|
||||
sx={{ margin: '1vh 0vh' }}
|
||||
/>
|
||||
{error && (
|
||||
<Box textAlign='right'>
|
||||
<Typography variant='body2' color='error'>
|
||||
{error}
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
{success && (
|
||||
<Box textAlign='right'>
|
||||
<Typography variant='body2' color='primary'>
|
||||
{success}
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
<Button
|
||||
variant='contained'
|
||||
color='primary'
|
||||
sx={{ margin: '1vh 0vh' }}
|
||||
type='submit'
|
||||
fullWidth
|
||||
>
|
||||
Recover my password
|
||||
</Button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
export default Recover;
|
||||
|
|
@ -1,89 +0,0 @@
|
|||
import React, { useState } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
|
||||
import axios from 'axios';
|
||||
|
||||
import { Box, Typography, TextField, Button } from '@mui/material';
|
||||
|
||||
function Reset() {
|
||||
const navigate = useNavigate();
|
||||
const { id } = useParams();
|
||||
|
||||
const [form, setForm] = useState({
|
||||
password: '',
|
||||
confirmPassword: '',
|
||||
});
|
||||
const [error, setError] = useState('');
|
||||
|
||||
function handleChange(e) {
|
||||
const name = e.target.name;
|
||||
const value = e.target.value;
|
||||
|
||||
setForm((form) => {
|
||||
return { ...form, [name]: value };
|
||||
});
|
||||
}
|
||||
|
||||
async function handleSubmit(e) {
|
||||
e.preventDefault();
|
||||
await axios
|
||||
.patch(`${process.env.DOMAIN_ROOT}/auth/reset-password/${id}`, {
|
||||
password: form.password,
|
||||
})
|
||||
.then(() => {
|
||||
navigate('/login');
|
||||
})
|
||||
.catch((e) => setError(() => e.response.data));
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Typography variant='h4' sx={{ marginTop: '1vh 0vh' }}>
|
||||
Reset my Password
|
||||
</Typography>
|
||||
<TextField
|
||||
required
|
||||
fullWidth
|
||||
id='outline-required'
|
||||
label='Password'
|
||||
name='password'
|
||||
variant='outlined'
|
||||
type='password'
|
||||
onChange={handleChange}
|
||||
value={form.password}
|
||||
sx={{ margin: '1vh 0vh' }}
|
||||
/>
|
||||
<TextField
|
||||
required
|
||||
fullWidth
|
||||
id='outline-required'
|
||||
label='Confirm Password'
|
||||
name='confirmPassword'
|
||||
variant='outlined'
|
||||
type='password'
|
||||
onChange={handleChange}
|
||||
value={form.confirmPassword}
|
||||
sx={{ margin: '1vh 0vh' }}
|
||||
/>
|
||||
{error && (
|
||||
<Box textAlign='right'>
|
||||
<Typography variant='body2' color='error'>
|
||||
{error}
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
<Button
|
||||
variant='contained'
|
||||
color='primary'
|
||||
sx={{ margin: '1vh 0vh' }}
|
||||
type='submit'
|
||||
fullWidth
|
||||
disabled={form.password !== form.confirmPassword}
|
||||
>
|
||||
Reset my Password
|
||||
</Button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
export default Reset;
|
||||
|
|
@ -6,7 +6,7 @@ import Cookies from 'js-cookie';
|
|||
|
||||
import { Box, Typography, TextField, Button } from '@mui/material';
|
||||
|
||||
import Form from './Form';
|
||||
import Form from '../utils/Form';
|
||||
|
||||
function EditUser() {
|
||||
const navigate = useNavigate();
|
||||
|
|
@ -39,9 +39,7 @@ function EditUser() {
|
|||
firstName: user.firstName,
|
||||
lastName: user.lastName,
|
||||
email: user.email,
|
||||
gender: user.gender,
|
||||
pronouns: user.pronouns,
|
||||
ethnicity: user.ethnicity,
|
||||
};
|
||||
});
|
||||
});
|
||||
|
|
@ -13,7 +13,7 @@ import {
|
|||
Checkbox,
|
||||
} from '@mui/material';
|
||||
|
||||
import Form from './Form';
|
||||
import Form from '../utils/Form';
|
||||
|
||||
function Login() {
|
||||
const navigate = useNavigate();
|
||||
79
client/src/components/forms/Recover.jsx
Normal file
79
client/src/components/forms/Recover.jsx
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
import React, { useState } from 'react';
|
||||
import axios from 'axios';
|
||||
|
||||
import { Box, Typography, TextField, Button } from '@mui/material';
|
||||
|
||||
import Form from '../utils/Form';
|
||||
|
||||
function Recover() {
|
||||
const [form, setForm] = useState({
|
||||
email: '',
|
||||
});
|
||||
const [error, setError] = useState('');
|
||||
const [success, setSuccess] = useState('');
|
||||
|
||||
function handleChange(e) {
|
||||
const name = e.target.name;
|
||||
const value = e.target.value;
|
||||
|
||||
setForm((form) => {
|
||||
return { ...form, [name]: value };
|
||||
});
|
||||
}
|
||||
|
||||
async function handleSubmit(e) {
|
||||
e.preventDefault();
|
||||
await axios
|
||||
.get(`${process.env.DOMAIN_ROOT}/auth/reset-password?${form.email}`)
|
||||
.then((response) => {
|
||||
setSuccess(() => response.data);
|
||||
})
|
||||
.catch((e) => setError(() => e.response.data));
|
||||
}
|
||||
|
||||
return (
|
||||
<Form>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Typography variant='h4' sx={{ marginTop: '1vh 0vh' }}>
|
||||
Recover my Password
|
||||
</Typography>
|
||||
<TextField
|
||||
required
|
||||
fullWidth
|
||||
id='outline-required'
|
||||
label='Email'
|
||||
name='email'
|
||||
variant='outlined'
|
||||
onChange={handleChange}
|
||||
value={form.email}
|
||||
sx={{ margin: '1vh 0vh' }}
|
||||
/>
|
||||
{error && (
|
||||
<Box textAlign='right'>
|
||||
<Typography variant='body2' color='error'>
|
||||
{error}
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
{success && (
|
||||
<Box textAlign='right'>
|
||||
<Typography variant='body2' color='primary'>
|
||||
{success}
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
<Button
|
||||
variant='contained'
|
||||
color='primary'
|
||||
sx={{ margin: '1vh 0vh' }}
|
||||
type='submit'
|
||||
fullWidth
|
||||
>
|
||||
Recover my password
|
||||
</Button>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
export default Recover;
|
||||
|
|
@ -5,7 +5,7 @@ import axios from 'axios';
|
|||
|
||||
import { Box, Typography, TextField, Button } from '@mui/material';
|
||||
|
||||
import Form from './Form';
|
||||
import Form from '../utils/Form';
|
||||
|
||||
function Register() {
|
||||
const navigate = useNavigate();
|
||||
|
|
@ -14,9 +14,7 @@ function Register() {
|
|||
firstName: '',
|
||||
lastName: '',
|
||||
email: '',
|
||||
gender: '',
|
||||
pronouns: '',
|
||||
ethnicity: '',
|
||||
password: '',
|
||||
confirmPassword: '',
|
||||
});
|
||||
|
|
@ -82,26 +80,6 @@ function Register() {
|
|||
value={form.email}
|
||||
sx={{ margin: '1vh 0vh' }}
|
||||
/>
|
||||
<TextField
|
||||
fullWidth
|
||||
id='outline-required'
|
||||
label='Ethnicity'
|
||||
name='ethnicity'
|
||||
variant='outlined'
|
||||
onChange={handleChange}
|
||||
value={form.ethnicity}
|
||||
sx={{ margin: '1vh 0vh' }}
|
||||
/>
|
||||
<TextField
|
||||
fullWidth
|
||||
id='outline-required'
|
||||
label='Gender'
|
||||
name='gender'
|
||||
variant='outlined'
|
||||
onChange={handleChange}
|
||||
value={form.gender}
|
||||
sx={{ margin: '1vh 0vh' }}
|
||||
/>
|
||||
<TextField
|
||||
fullWidth
|
||||
id='outline-required'
|
||||
93
client/src/components/forms/Reset.jsx
Normal file
93
client/src/components/forms/Reset.jsx
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
import React, { useState } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
|
||||
import axios from 'axios';
|
||||
|
||||
import { Box, Typography, TextField, Button } from '@mui/material';
|
||||
|
||||
import Form from '../utils/Form';
|
||||
|
||||
function Reset() {
|
||||
const navigate = useNavigate();
|
||||
const { id } = useParams();
|
||||
|
||||
const [form, setForm] = useState({
|
||||
password: '',
|
||||
confirmPassword: '',
|
||||
});
|
||||
const [error, setError] = useState('');
|
||||
|
||||
function handleChange(e) {
|
||||
const name = e.target.name;
|
||||
const value = e.target.value;
|
||||
|
||||
setForm((form) => {
|
||||
return { ...form, [name]: value };
|
||||
});
|
||||
}
|
||||
|
||||
async function handleSubmit(e) {
|
||||
e.preventDefault();
|
||||
await axios
|
||||
.patch(`${process.env.DOMAIN_ROOT}/auth/reset-password/${id}`, {
|
||||
password: form.password,
|
||||
})
|
||||
.then(() => {
|
||||
navigate('/login');
|
||||
})
|
||||
.catch((e) => setError(() => e.response.data));
|
||||
}
|
||||
|
||||
return (
|
||||
<Form>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Typography variant='h4' sx={{ marginTop: '1vh 0vh' }}>
|
||||
Reset my Password
|
||||
</Typography>
|
||||
<TextField
|
||||
required
|
||||
fullWidth
|
||||
id='outline-required'
|
||||
label='Password'
|
||||
name='password'
|
||||
variant='outlined'
|
||||
type='password'
|
||||
onChange={handleChange}
|
||||
value={form.password}
|
||||
sx={{ margin: '1vh 0vh' }}
|
||||
/>
|
||||
<TextField
|
||||
required
|
||||
fullWidth
|
||||
id='outline-required'
|
||||
label='Confirm Password'
|
||||
name='confirmPassword'
|
||||
variant='outlined'
|
||||
type='password'
|
||||
onChange={handleChange}
|
||||
value={form.confirmPassword}
|
||||
sx={{ margin: '1vh 0vh' }}
|
||||
/>
|
||||
{error && (
|
||||
<Box textAlign='right'>
|
||||
<Typography variant='body2' color='error'>
|
||||
{error}
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
<Button
|
||||
variant='contained'
|
||||
color='primary'
|
||||
sx={{ margin: '1vh 0vh' }}
|
||||
type='submit'
|
||||
fullWidth
|
||||
disabled={form.password !== form.confirmPassword}
|
||||
>
|
||||
Reset my Password
|
||||
</Button>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
export default Reset;
|
||||
31
client/src/components/orgs/JoinedOrgs.jsx
Normal file
31
client/src/components/orgs/JoinedOrgs.jsx
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import React from 'react';
|
||||
|
||||
import axios from 'axios';
|
||||
import Cookies from 'js-cookie';
|
||||
|
||||
import Organizations from '../utils/Organizations';
|
||||
|
||||
function JoinedOrgs() {
|
||||
async function getOrgs(setOrgs) {
|
||||
axios
|
||||
.get(
|
||||
`${process.env.DOMAIN_ROOT}/auth?token=${Cookies.get('token')}`
|
||||
)
|
||||
.then((response) => {
|
||||
setOrgs(() =>
|
||||
response.data.map((org) => {
|
||||
return {
|
||||
orgID: org.orgID,
|
||||
orgName: org.orgName,
|
||||
memberCount: org.memberCount,
|
||||
createdAt: org.createdAt,
|
||||
};
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return <Organizations retrieveOrgs={getOrgs} />;
|
||||
}
|
||||
|
||||
export default JoinedOrgs;
|
||||
33
client/src/components/orgs/MemberOrgs.jsx
Normal file
33
client/src/components/orgs/MemberOrgs.jsx
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import React from 'react';
|
||||
|
||||
import axios from 'axios';
|
||||
import Cookies from 'js-cookie';
|
||||
|
||||
import Organizations from '../utils/Organizations';
|
||||
|
||||
function JoinedOrgs() {
|
||||
async function getOrgs(setOrgs) {
|
||||
axios
|
||||
.get(
|
||||
`${process.env.DOMAIN_ROOT}/auth/orgs?token=${Cookies.get(
|
||||
'token'
|
||||
)}`
|
||||
)
|
||||
.then((response) => {
|
||||
setOrgs(() =>
|
||||
response.data.map((org) => {
|
||||
return {
|
||||
orgID: org.orgID,
|
||||
orgName: org.orgName,
|
||||
memberCount: org.memberCount,
|
||||
createdAt: org.createdAt,
|
||||
};
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return <Organizations retrieveOrgs={getOrgs} />;
|
||||
}
|
||||
|
||||
export default JoinedOrgs;
|
||||
|
|
@ -13,7 +13,7 @@ import {
|
|||
Button,
|
||||
} from '@mui/material';
|
||||
|
||||
import Dashboard from './Dashboard';
|
||||
import Dashboard from '../utils/Dashboard';
|
||||
|
||||
function OrgDashboard() {
|
||||
const { orgID } = useParams();
|
||||
|
|
@ -30,6 +30,7 @@ function OrgDashboard() {
|
|||
orgMembers: [],
|
||||
createdAt: '',
|
||||
});
|
||||
|
||||
const [rows, setRows] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
28
client/src/components/utils/OrgUnit.jsx
Normal file
28
client/src/components/utils/OrgUnit.jsx
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { Stack, Box, Typography } from '@mui/material';
|
||||
|
||||
function OrgUnit({ org }) {
|
||||
const { orgID, orgName, memberCount, createdAt } = org;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Typography variant='h6'>
|
||||
<Link to={orgID}>Name: {orgName}</Link>
|
||||
</Typography>
|
||||
<Box>
|
||||
<Stack spacing={2} direction={{ xs: 'column', md: 'row' }}>
|
||||
<Typography variant='body1'>
|
||||
Members: {memberCount}
|
||||
</Typography>
|
||||
<Typography variant='body1'>
|
||||
Created On: {createdAt}
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Box>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
export default OrgUnit;
|
||||
38
client/src/components/utils/Organizations.jsx
Normal file
38
client/src/components/utils/Organizations.jsx
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import { Paper, Typography, Box } from '@mui/material';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
|
||||
import OrgUnit from './OrgUnit';
|
||||
|
||||
function Organizations({ retrieveOrgs }) {
|
||||
const theme = useTheme();
|
||||
|
||||
const [orgs, setOrgs] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
retrieveOrgs(setOrgs);
|
||||
}, [retrieveOrgs]);
|
||||
|
||||
return (
|
||||
<Paper
|
||||
sx={{
|
||||
...theme.typography.body2,
|
||||
padding: '2vh',
|
||||
color: theme.palette.text.secondary,
|
||||
flexGrow: 1,
|
||||
height: '89vh',
|
||||
margin: '2vh',
|
||||
}}
|
||||
>
|
||||
<Typography variant='h6'>Organizations</Typography>
|
||||
<Box sx={{ overflowY: 'auto' }}>
|
||||
{orgs.map((org) => (
|
||||
<OrgUnit org={org} />
|
||||
))}
|
||||
</Box>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
||||
export default Organizations;
|
||||
22
client/src/components/utils/StackItem.jsx
Normal file
22
client/src/components/utils/StackItem.jsx
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import React from 'react';
|
||||
|
||||
import { Paper, Typography } from '@mui/material';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
|
||||
function StackItem({ text, children }) {
|
||||
const theme = useTheme();
|
||||
|
||||
const paperStyle = {
|
||||
...theme.typography.body2,
|
||||
padding: '2vh',
|
||||
color: theme.palette.text.secondary,
|
||||
};
|
||||
return (
|
||||
<Paper sx={paperStyle}>
|
||||
<Typography variant='h6'>{text}</Typography>
|
||||
{children}
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
||||
export default StackItem;
|
||||
|
|
@ -11,8 +11,8 @@ module.exports = (sequelize, DataTypes) => {
|
|||
foreignKey: 'userID',
|
||||
onDelete: 'CASCADE',
|
||||
});
|
||||
User.hasOne(models.Organization, {
|
||||
as: 'ownedOrg',
|
||||
User.hasMany(models.Organization, {
|
||||
as: 'ownedOrgs',
|
||||
foreignKey: 'ownerID',
|
||||
onDelete: 'CASCADE',
|
||||
});
|
||||
|
|
@ -52,12 +52,6 @@ module.exports = (sequelize, DataTypes) => {
|
|||
pronouns: {
|
||||
type: DataTypes.STRING,
|
||||
},
|
||||
gender: {
|
||||
type: DataTypes.STRING,
|
||||
},
|
||||
ethnicity: {
|
||||
type: DataTypes.STRING,
|
||||
},
|
||||
},
|
||||
{ sequelize }
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
async function GetKnownOrgs(req, res, next) {
|
||||
async function GetMemberOrgs(req, res, next) {
|
||||
const user = req.user;
|
||||
|
||||
const result = await user.getMemberOrgs();
|
||||
return res.send(result);
|
||||
}
|
||||
|
||||
module.exports = GetKnownOrgs;
|
||||
module.exports = GetMemberOrgs;
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
async function GetOwnedGroups(req, res, next) {
|
||||
const user = req.user;
|
||||
|
||||
const result = await user.getOwnedGroups();
|
||||
const result = await user.getOwnedOrgs();
|
||||
return res.send(result);
|
||||
}
|
||||
|
||||
|
|
@ -13,7 +13,7 @@ const RequestReset = require('./auth/RequestReset');
|
|||
const ResetPassword = require('./auth/ResetPassword');
|
||||
const GetUserDetails = require('./auth/GetUserDetails');
|
||||
const GetKnownOrgs = require('./auth/GetKnownOrgs');
|
||||
const GetOwnedGroups = require('./auth/GetOwnedGroups');
|
||||
const GetOwnedORgs = require('./auth/GetOwnedOrgs');
|
||||
|
||||
router.post('/register', CreateUser);
|
||||
router.patch('/update', tokenAuth, UpdateUserDetails);
|
||||
|
|
@ -23,7 +23,8 @@ router.get('/reset-password', RequestReset);
|
|||
router.patch('/reset-password/:id', ResetPassword);
|
||||
|
||||
router.get('/:userID', tokenAuth, checkKnownUser, GetUserDetails);
|
||||
|
||||
router.get('/', tokenAuth, GetOwnedOrgs);
|
||||
router.get('/orgs', tokenAuth, GetKnownOrgs);
|
||||
router.get('/groups', tokenAuth, GetOwnedGroups);
|
||||
|
||||
module.exports = router;
|
||||
|
|
|
|||
|
|
@ -6,12 +6,12 @@ async function CreateOrg(req, res, next) {
|
|||
return res.status(400).send('Form missing necessary fields');
|
||||
}
|
||||
|
||||
const existing = await user.getOwnedOrg();
|
||||
if (existing) {
|
||||
const existing = await user.getOwnedOrgs();
|
||||
if (existing.map((org) => org.orgName).includes(orgName)) {
|
||||
return res
|
||||
.status(500)
|
||||
.send(
|
||||
'This user already owns an organization, delete it to create another.'
|
||||
'This user already has an organization with this name, please pick another.'
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
Reference in a new issue