Refactored all components into controllers and dummies
This commit is contained in:
parent
18f2e25628
commit
b62d177654
37 changed files with 1425 additions and 1347 deletions
|
|
@ -2,19 +2,19 @@ import React, { useState } from 'react';
|
|||
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
|
||||
|
||||
import Navbar from './components/Navbar';
|
||||
import MainPage from './components/MainPage';
|
||||
import MainPage from './controllers/MainPage';
|
||||
|
||||
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 Form from './components/Form';
|
||||
import Login from './controllers/Login';
|
||||
import Register from './controllers/Register';
|
||||
import EditUser from './controllers/EditUser';
|
||||
import Recover from './controllers/Recover';
|
||||
import Reset from './controllers/Reset';
|
||||
|
||||
import OwnedOrgs from './components/orgs/OwnedOrgs';
|
||||
import MemberOrgs from './components/orgs/MemberOrgs';
|
||||
import OrgDashboard from './components/orgs/OrgDashboard';
|
||||
import UpdateOrganization from './components/forms/UpdateOrganization';
|
||||
import OwnedOrgs from './controllers/OwnedOrgs';
|
||||
import MemberOrgs from './controllers/MemberOrgs';
|
||||
import OrgDashboard from './controllers/OrgDashboard';
|
||||
import UpdateOrganization from './controllers/UpdateOrganization';
|
||||
|
||||
function App() {
|
||||
const [token, setToken] = useState(true);
|
||||
|
|
|
|||
221
client/src/components/Dashboard.jsx
Normal file
221
client/src/components/Dashboard.jsx
Normal file
|
|
@ -0,0 +1,221 @@
|
|||
import React, { useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import {
|
||||
Accordion,
|
||||
AccordionDetails,
|
||||
AccordionSummary,
|
||||
Box,
|
||||
Button,
|
||||
ButtonGroup,
|
||||
Collapse,
|
||||
Grid,
|
||||
IconButton,
|
||||
Stack,
|
||||
Typography,
|
||||
useMediaQuery,
|
||||
} from '@mui/material';
|
||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||
import PlayArrowIcon from '@mui/icons-material/PlayArrow';
|
||||
import { DataGrid } from '@mui/x-data-grid';
|
||||
|
||||
import MemberUnit from './MemberUnit';
|
||||
import { DynamicPaper, DynamicStack } from './StyledElements';
|
||||
|
||||
function Dashboard({
|
||||
rows,
|
||||
setSelection,
|
||||
onClick,
|
||||
org,
|
||||
status,
|
||||
handleDelete,
|
||||
copyID,
|
||||
copyJoinLink,
|
||||
removeSelected,
|
||||
leaveOrg,
|
||||
}) {
|
||||
const xs = useMediaQuery((theme) => theme.breakpoints.only('xs'));
|
||||
const sm = useMediaQuery((theme) => theme.breakpoints.only('sm'));
|
||||
const md = useMediaQuery((theme) => theme.breakpoints.only('md'));
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const linkStyle = { textDecoration: 'none', color: 'inherit' };
|
||||
const columns = [
|
||||
{ field: 'id', headerName: 'ID', flex: 1 },
|
||||
{ field: 'firstName', headerName: 'First Name', flex: 3 },
|
||||
{ field: 'lastName', headerName: 'Last Name', flex: 3 },
|
||||
{ field: 'nickname', headerName: 'Nickname', flex: 2 },
|
||||
{
|
||||
field: 'namePronounciation',
|
||||
headerName: 'Pronounciation',
|
||||
flex: 2,
|
||||
sortable: false,
|
||||
renderCell: (params) => (
|
||||
<IconButton
|
||||
size='large'
|
||||
edge='start'
|
||||
color='inherit'
|
||||
aria-label='menu'
|
||||
sx={{ mr: 2 }}
|
||||
onClick={() => onClick(params.id)}
|
||||
>
|
||||
<PlayArrowIcon />
|
||||
</IconButton>
|
||||
),
|
||||
},
|
||||
{ field: 'pronouns', headerName: 'Pronouns', flex: 2 },
|
||||
{ field: 'email', headerName: 'Email', flex: 10, sortable: false },
|
||||
];
|
||||
|
||||
function handleOpen() {
|
||||
setOpen((initial) => (initial ? false : true));
|
||||
}
|
||||
|
||||
const buttonGroupOrientation = () => {
|
||||
if (xs) return 'vertical';
|
||||
};
|
||||
|
||||
function calculateCollapsedSize() {
|
||||
if (xs) return '37vh';
|
||||
if (sm) return '63vh';
|
||||
if (md) return '68vh';
|
||||
}
|
||||
|
||||
function calculateTableHeight() {
|
||||
return open ? '64vh' : '100%';
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Box sx={{ padding: '2vh' }}>
|
||||
<Accordion expanded={open} onChange={handleOpen}>
|
||||
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
||||
<Typography variant='h6'>
|
||||
Organizations/{org.name}
|
||||
</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<Box sx={{ marginTop: '1vh' }} fullWidth>
|
||||
<Grid
|
||||
container
|
||||
columns={{ xs: 3, sm: 12, md: 12 }}
|
||||
spacing={1}
|
||||
>
|
||||
<Grid item xs={3}>
|
||||
<Stack>
|
||||
<Typography variant='body1'>
|
||||
Organization ID: {org.orgID}
|
||||
</Typography>
|
||||
<Typography variant='body1'>
|
||||
Members: {org.memberCount}
|
||||
</Typography>
|
||||
<Typography variant='body1'>
|
||||
Created On: {org.createdAt}
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid item xs={3}>
|
||||
<Stack>
|
||||
<Typography variant='body1'>
|
||||
Owner:{' '}
|
||||
{`${org.owner.firstName} ${org.owner.lastName}`}
|
||||
</Typography>
|
||||
<Typography variant='body1'>
|
||||
Email: {org.owner.email}
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid item xs={3} sm={6} md={6}>
|
||||
<DynamicStack spacing={1}>
|
||||
{status ? (
|
||||
<React.Fragment>
|
||||
<ButtonGroup
|
||||
variant='outlined'
|
||||
orientation={buttonGroupOrientation()}
|
||||
>
|
||||
<Button color='warning'>
|
||||
<Link
|
||||
to='update'
|
||||
style={linkStyle}
|
||||
>
|
||||
Edit {org.name}
|
||||
</Link>
|
||||
</Button>
|
||||
<Button
|
||||
color='error'
|
||||
onClick={handleDelete}
|
||||
>
|
||||
Delete {org.name}
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
<ButtonGroup
|
||||
variant='outlined'
|
||||
orientation={buttonGroupOrientation()}
|
||||
>
|
||||
<Button
|
||||
color='success'
|
||||
onClick={copyID}
|
||||
>
|
||||
Copy Org ID
|
||||
</Button>
|
||||
<Button
|
||||
color='success'
|
||||
onClick={copyJoinLink}
|
||||
>
|
||||
Copy Org Join Link
|
||||
</Button>
|
||||
<Button
|
||||
color='error'
|
||||
onClick={removeSelected}
|
||||
>
|
||||
Remove People
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<Button
|
||||
color='error'
|
||||
onClick={leaveOrg}
|
||||
>
|
||||
Leave Org
|
||||
</Button>
|
||||
)}
|
||||
</DynamicStack>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
</Box>
|
||||
<Collapse
|
||||
collapsedSize={calculateCollapsedSize()}
|
||||
in={open ? false : true}
|
||||
>
|
||||
<DynamicPaper>
|
||||
{(xs || sm) &&
|
||||
rows.map((member) => {
|
||||
return (
|
||||
<MemberUnit member={member} onClick={onClick} />
|
||||
);
|
||||
})}
|
||||
{md && (
|
||||
<Box height={calculateTableHeight()}>
|
||||
<DataGrid
|
||||
rows={rows}
|
||||
columns={columns}
|
||||
pageSize={5}
|
||||
rowsPerPageOptions={[5]}
|
||||
onSelectionModelChange={(newSelection) => {
|
||||
setSelection(() => newSelection.rows);
|
||||
}}
|
||||
checkboxSelection
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</DynamicPaper>
|
||||
</Collapse>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
export default Dashboard;
|
||||
64
client/src/components/EditUserForm.jsx
Normal file
64
client/src/components/EditUserForm.jsx
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Box, Typography } from '@mui/material';
|
||||
|
||||
import Form from './Form';
|
||||
import RecordMp3 from './RecordMp3';
|
||||
import { FormSubmit, FormTextField } from './StyledElements';
|
||||
|
||||
function EditUserForm({ form, error, handleSubmit, handleChange }) {
|
||||
const linkStyle = { textDecoration: 'none', color: 'inherit' };
|
||||
|
||||
return (
|
||||
<Form text='Update Account Details'>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<FormTextField
|
||||
required
|
||||
label='First Name'
|
||||
name='firstName'
|
||||
onChange={handleChange}
|
||||
value={form.firstName}
|
||||
/>
|
||||
<FormTextField
|
||||
required
|
||||
label='Last Name'
|
||||
name='lastName'
|
||||
onChange={handleChange}
|
||||
value={form.lastName}
|
||||
/>
|
||||
<FormTextField
|
||||
required
|
||||
label='Email'
|
||||
name='email'
|
||||
onChange={handleChange}
|
||||
value={form.email}
|
||||
/>
|
||||
<FormTextField
|
||||
required
|
||||
label='Pronouns'
|
||||
name='pronouns'
|
||||
onChange={handleChange}
|
||||
value={form.pronouns}
|
||||
/>
|
||||
{error && (
|
||||
<Box textAlign='right'>
|
||||
<Typography variant='body2' color='secondary'>
|
||||
{error}
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
<FormSubmit color='primary' type='submit'>
|
||||
Update Details
|
||||
</FormSubmit>
|
||||
<FormSubmit color='error' type='button'>
|
||||
<Link to='/recover' style={linkStyle}>
|
||||
Reset Your Password
|
||||
</Link>
|
||||
</FormSubmit>
|
||||
</form>
|
||||
<RecordMp3 />
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
export default EditUserForm;
|
||||
45
client/src/components/Form.jsx
Normal file
45
client/src/components/Form.jsx
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
import React from 'react';
|
||||
import { Outlet } from 'react-router-dom';
|
||||
import { Box, Grid, Paper, Typography } from '@mui/material';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
|
||||
function Form({ children, text }) {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Box sx={{ flexGrow: 1 }}>
|
||||
<Grid
|
||||
container
|
||||
spacing={2}
|
||||
alignItems='center'
|
||||
style={{ height: '95vh' }}
|
||||
>
|
||||
<Grid item xs={0.5} sm={2} md={4} />
|
||||
<Grid item xs={11} sm={8} md={4}>
|
||||
<Paper
|
||||
sx={{
|
||||
...theme.typography.body2,
|
||||
padding: '2vh',
|
||||
textAlign: 'center',
|
||||
color: theme.palette.text.secondary,
|
||||
}}
|
||||
>
|
||||
{text && (
|
||||
<Typography
|
||||
variant='h4'
|
||||
sx={{ marginTop: '1vh 0vh' }}
|
||||
>
|
||||
{text}
|
||||
</Typography>
|
||||
)}
|
||||
{children}
|
||||
<Outlet />
|
||||
</Paper>
|
||||
</Grid>
|
||||
<Grid item xs={0.5} sm={2} md={4} />
|
||||
</Grid>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default Form;
|
||||
73
client/src/components/LoginForm.jsx
Normal file
73
client/src/components/LoginForm.jsx
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Box, Checkbox, FormControlLabel, Typography } from '@mui/material';
|
||||
|
||||
import Form from '../Form';
|
||||
import { FormSubmit, FormTextField } from '../StyledElements';
|
||||
|
||||
function Login({
|
||||
form,
|
||||
error,
|
||||
handleSubmit,
|
||||
handleChange,
|
||||
handleCheckboxChange,
|
||||
}) {
|
||||
return (
|
||||
<Form>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Typography variant='h4' sx={{ marginTop: '1vh 0vh' }}>
|
||||
Login
|
||||
</Typography>
|
||||
<FormTextField
|
||||
required
|
||||
label='Email'
|
||||
name='email'
|
||||
onChange={handleChange}
|
||||
value={form.email}
|
||||
/>
|
||||
<FormTextField
|
||||
required
|
||||
label='Password'
|
||||
name='password'
|
||||
onChange={handleChange}
|
||||
value={form.password}
|
||||
other={{ type: 'password' }}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<FormControlLabel
|
||||
control={<Checkbox checked={form.remember} />}
|
||||
labelPlacement='start'
|
||||
label='Remember me'
|
||||
name='remember'
|
||||
onChange={handleCheckboxChange}
|
||||
/>
|
||||
</div>
|
||||
{error && (
|
||||
<Box textAlign='right'>
|
||||
<Typography variant='body2' color='error'>
|
||||
{error}
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
<FormSubmit color='primary' type='submit'>
|
||||
Login
|
||||
</FormSubmit>
|
||||
<Typography variant='body1'>
|
||||
Dont have an account? <Link to='/register'>Register</Link>
|
||||
</Typography>
|
||||
<Typography variant='body1'>
|
||||
Forgot your password?{' '}
|
||||
<Link to='/recover'>Recover Password</Link>
|
||||
</Typography>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
export default Login;
|
||||
|
|
@ -1,214 +0,0 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
|
||||
import axios from 'axios';
|
||||
import Cookies from 'js-cookie';
|
||||
|
||||
import {
|
||||
Button,
|
||||
Typography,
|
||||
Box,
|
||||
Grid,
|
||||
Stack,
|
||||
TextField,
|
||||
FormGroup,
|
||||
} from '@mui/material';
|
||||
import { styled } from '@mui/material/styles';
|
||||
|
||||
import OrgUnit from './utils/OrgUnit';
|
||||
import StackItem from './utils/StackItem';
|
||||
|
||||
const Form = styled(FormGroup)(({ theme }) => ({
|
||||
[theme.breakpoints.only('xs')]: {
|
||||
column: true,
|
||||
},
|
||||
}));
|
||||
const FormField = styled(TextField)(({ theme }) => ({
|
||||
[theme.breakpoints.only('xs')]: {
|
||||
column: true,
|
||||
width: '75%',
|
||||
},
|
||||
}));
|
||||
const FormButton = styled(Button)(({ theme }) => ({
|
||||
disableElevation: true,
|
||||
color: theme.palette.primary.main,
|
||||
[theme.breakpoints.only('xs')]: {
|
||||
column: true,
|
||||
width: '25%',
|
||||
},
|
||||
}));
|
||||
|
||||
function MainPage() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [ownedOrgs, setOwnedOrgs] = useState([]);
|
||||
const [orgs, setOrgs] = useState([]);
|
||||
const [form, setForm] = useState({
|
||||
name: '',
|
||||
id: '',
|
||||
});
|
||||
|
||||
const linkStyle = { textDecoration: 'none', color: 'inherit' };
|
||||
|
||||
useEffect(() => {
|
||||
if (Cookies.get('token')) {
|
||||
getMemberOrgs();
|
||||
getOwnedOrgs();
|
||||
} else {
|
||||
navigate('/login');
|
||||
}
|
||||
}, [navigate]);
|
||||
|
||||
function handleChange(e) {
|
||||
const name = e.target.name;
|
||||
const value = e.target.value;
|
||||
|
||||
setForm((form) => {
|
||||
return { ...form, [name]: value };
|
||||
});
|
||||
}
|
||||
|
||||
async function getOwnedOrgs() {
|
||||
await axios
|
||||
.get(
|
||||
`${process.env.REACT_APP_API_ROOT}/auth?token=${Cookies.get(
|
||||
'token'
|
||||
)}`
|
||||
)
|
||||
.then((response) => {
|
||||
setOwnedOrgs(() => response.data);
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e);
|
||||
});
|
||||
}
|
||||
|
||||
async function getMemberOrgs() {
|
||||
await axios
|
||||
.get(
|
||||
`${
|
||||
process.env.REACT_APP_API_ROOT
|
||||
}/auth/orgs?token=${Cookies.get('token')}`
|
||||
)
|
||||
.then((response) => {
|
||||
setOrgs(() => response.data);
|
||||
});
|
||||
}
|
||||
|
||||
async function createOrg(e) {
|
||||
e.preventDefault();
|
||||
console.log('create');
|
||||
|
||||
await axios
|
||||
.post(`${process.env.REACT_APP_API_ROOT}/org/create`, {
|
||||
token: Cookies.get('token'),
|
||||
orgName: form.name,
|
||||
})
|
||||
.then(() => {
|
||||
getOwnedOrgs();
|
||||
});
|
||||
}
|
||||
|
||||
async function joinOrg(e) {
|
||||
e.preventDefault();
|
||||
|
||||
await axios
|
||||
.post(`${process.env.REACT_APP_API_ROOT}/org/${form.id}/add`, {
|
||||
token: Cookies.get('token'),
|
||||
})
|
||||
.then(() => {
|
||||
getMemberOrgs();
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Box sx={{ flexGrow: 1 }}>
|
||||
<Grid
|
||||
container
|
||||
spacing={2}
|
||||
alignItems='center'
|
||||
style={{ height: '95vh' }}
|
||||
>
|
||||
<Grid item xs={0.5} sm={2} md={4} />
|
||||
<Grid item xs={11} sm={8} md={4}>
|
||||
<Stack spacing={2}>
|
||||
<StackItem text='Join an Organization'>
|
||||
<form onSubmit={joinOrg}>
|
||||
<Form>
|
||||
<FormField
|
||||
label='Organization ID'
|
||||
name='id'
|
||||
value={form.id}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<FormButton
|
||||
type='submit'
|
||||
variant='outlined'
|
||||
>
|
||||
Join Organization
|
||||
</FormButton>
|
||||
</Form>
|
||||
</form>
|
||||
</StackItem>
|
||||
<StackItem text='Create an Organization'>
|
||||
<form onSubmit={createOrg}>
|
||||
<Form>
|
||||
<FormField
|
||||
label='Organization Name'
|
||||
name='name'
|
||||
value={form.name}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<FormButton
|
||||
type='submit'
|
||||
variant='outlined'
|
||||
>
|
||||
Create Organization
|
||||
</FormButton>
|
||||
</Form>
|
||||
</form>
|
||||
</StackItem>
|
||||
<StackItem>
|
||||
<Typography variant='h6'>
|
||||
<Link to='org' style={linkStyle}>
|
||||
Your Organizations
|
||||
</Link>
|
||||
</Typography>
|
||||
{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='org/joined' style={linkStyle}>
|
||||
Joined Organizations
|
||||
</Link>
|
||||
</Typography>
|
||||
{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={0.5} sm={2} md={4} />
|
||||
</Grid>
|
||||
</Box>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
export default MainPage;
|
||||
106
client/src/components/MainPageBase.jsx
Normal file
106
client/src/components/MainPageBase.jsx
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Box, Grid, Stack, Typography } from '@mui/material';
|
||||
|
||||
import OrgUnit from './OrgUnit';
|
||||
import StackItem from './StackItem';
|
||||
import { Form, FormButton, FormField } from './StyledElements';
|
||||
|
||||
function MainPageBase({
|
||||
form,
|
||||
orgs,
|
||||
ownedOrgs,
|
||||
joinOrg,
|
||||
createOrg,
|
||||
handleChange,
|
||||
}) {
|
||||
const linkStyle = { textDecoration: 'none', color: 'inherit' };
|
||||
|
||||
return (
|
||||
<Box sx={{ flexGrow: 1 }}>
|
||||
<Grid
|
||||
container
|
||||
spacing={2}
|
||||
alignItems='center'
|
||||
style={{ height: '95vh' }}
|
||||
>
|
||||
<Grid item xs={0.5} sm={2} md={4} />
|
||||
<Grid item xs={11} sm={8} md={4}>
|
||||
<Stack spacing={2}>
|
||||
<StackItem text='Join an Organization'>
|
||||
<form onSubmit={joinOrg}>
|
||||
<Form>
|
||||
<FormField
|
||||
label='Organization ID'
|
||||
name='id'
|
||||
value={form.id}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<FormButton
|
||||
type='submit'
|
||||
variant='outlined'
|
||||
>
|
||||
Join Organization
|
||||
</FormButton>
|
||||
</Form>
|
||||
</form>
|
||||
</StackItem>
|
||||
<StackItem text='Create an Organization'>
|
||||
<form onSubmit={createOrg}>
|
||||
<Form>
|
||||
<FormField
|
||||
label='Organization Name'
|
||||
name='name'
|
||||
value={form.name}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<FormButton
|
||||
type='submit'
|
||||
variant='outlined'
|
||||
>
|
||||
Create Organization
|
||||
</FormButton>
|
||||
</Form>
|
||||
</form>
|
||||
</StackItem>
|
||||
<StackItem>
|
||||
<Typography variant='h6'>
|
||||
<Link to='org' style={linkStyle}>
|
||||
Your Organizations
|
||||
</Link>
|
||||
</Typography>
|
||||
{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='org/joined' style={linkStyle}>
|
||||
Joined Organizations
|
||||
</Link>
|
||||
</Typography>
|
||||
{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={0.5} sm={2} md={4} />
|
||||
</Grid>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default MainPageBase;
|
||||
|
|
@ -1,12 +1,11 @@
|
|||
import React from 'react';
|
||||
|
||||
import {
|
||||
Accordion,
|
||||
AccordionSummary,
|
||||
AccordionDetails,
|
||||
Typography,
|
||||
IconButton,
|
||||
AccordionSummary,
|
||||
Grid,
|
||||
IconButton,
|
||||
Typography,
|
||||
} from '@mui/material';
|
||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||
import PlayArrowIcon from '@mui/icons-material/PlayArrow';
|
||||
|
|
@ -1,17 +1,16 @@
|
|||
import React from 'react';
|
||||
import { Link, Outlet } from 'react-router-dom';
|
||||
|
||||
import {
|
||||
AppBar,
|
||||
Button,
|
||||
CssBaseline,
|
||||
IconButton,
|
||||
Toolbar,
|
||||
Typography,
|
||||
IconButton,
|
||||
CssBaseline,
|
||||
useMediaQuery,
|
||||
} from '@mui/material';
|
||||
import { createTheme, ThemeProvider } from '@mui/material/styles';
|
||||
import MenuIcon from '@mui/icons-material/Menu';
|
||||
import { ThemeProvider, createTheme } from '@mui/material/styles';
|
||||
|
||||
function Navbar({ token }) {
|
||||
const linkStyle = { textDecoration: 'none', color: 'inherit' };
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import {
|
||||
Typography,
|
||||
Accordion,
|
||||
AccordionSummary,
|
||||
AccordionDetails,
|
||||
AccordionSummary,
|
||||
Typography,
|
||||
} from '@mui/material';
|
||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||
|
||||
|
|
@ -1,19 +1,12 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import { Paper, Typography, Box } from '@mui/material';
|
||||
import React from 'react';
|
||||
import { Box, Paper, Typography } from '@mui/material';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
|
||||
import OrgUnit from './OrgUnit';
|
||||
|
||||
function Organizations({ retrieveOrgs }) {
|
||||
function Organizations({ orgs }) {
|
||||
const theme = useTheme();
|
||||
|
||||
const [orgs, setOrgs] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
retrieveOrgs(setOrgs);
|
||||
}, [retrieveOrgs]);
|
||||
|
||||
return (
|
||||
<Paper
|
||||
sx={{
|
||||
|
|
@ -1,14 +1,12 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
|
||||
import MicRecorder from 'mic-recorder-to-mp3';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import axios from 'axios';
|
||||
import Cookies from 'js-cookie';
|
||||
|
||||
import { IconButton } from '@mui/material';
|
||||
import PlayArrow from '@mui/icons-material/PlayArrow';
|
||||
import MicRecorder from 'mic-recorder-to-mp3';
|
||||
import Mic from '@mui/icons-material/Mic';
|
||||
import Upload from '@mui/icons-material/Upload';
|
||||
import PlayArrow from '@mui/icons-material/PlayArrow';
|
||||
import SaveAs from '@mui/icons-material/SaveAs';
|
||||
import Upload from '@mui/icons-material/Upload';
|
||||
import { IconButton } from '@mui/material';
|
||||
|
||||
function RecordMp3() {
|
||||
const [recorder] = useState(
|
||||
40
client/src/components/RecoverForm.jsx
Normal file
40
client/src/components/RecoverForm.jsx
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
import React from 'react';
|
||||
import { Box, Typography } from '@mui/material';
|
||||
|
||||
import { FormSubmit, FormTextField } from '../StyledElements';
|
||||
|
||||
function RecoverForm({ form, error, success, handleSubmit, handleChange }) {
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Typography variant='h4' sx={{ marginTop: '1vh 0vh' }}>
|
||||
Recover my Password
|
||||
</Typography>
|
||||
<FormTextField
|
||||
required
|
||||
label='Email'
|
||||
name='email'
|
||||
onChange={handleChange}
|
||||
value={form.email}
|
||||
/>
|
||||
{error && (
|
||||
<Box textAlign='right'>
|
||||
<Typography variant='body2' color='error'>
|
||||
{error}
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
{success && (
|
||||
<Box textAlign='right'>
|
||||
<Typography variant='body2' color='primary'>
|
||||
{success}
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
<FormSubmit color='primary' type='submit'>
|
||||
Recover my password
|
||||
</FormSubmit>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
export default RecoverForm;
|
||||
78
client/src/components/RegisterForm.jsx
Normal file
78
client/src/components/RegisterForm.jsx
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Box, TextField, Typography } from '@mui/material';
|
||||
|
||||
import Form from '../Form';
|
||||
import { FormSubmit, FormTextField } from '../StyledElements';
|
||||
|
||||
function RegisterForm({ form, handleSubmit, handleChange, error }) {
|
||||
return (
|
||||
<Form text='Create an Account'>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<FormTextField
|
||||
required
|
||||
label='First Name'
|
||||
name='firstName'
|
||||
onChange={handleChange}
|
||||
value={form.firstName}
|
||||
/>
|
||||
<FormTextField
|
||||
required
|
||||
label='Last Name'
|
||||
name='lastName'
|
||||
onChange={handleChange}
|
||||
value={form.lastName}
|
||||
/>
|
||||
<FormTextField
|
||||
required
|
||||
label='Email'
|
||||
name='email'
|
||||
onChange={handleChange}
|
||||
value={form.email}
|
||||
/>
|
||||
<FormTextField
|
||||
fullWidth
|
||||
label='Pronouns'
|
||||
name='pronouns'
|
||||
onChange={handleChange}
|
||||
value={form.pronouns}
|
||||
/>
|
||||
<TextField
|
||||
required
|
||||
label='Password'
|
||||
name='password'
|
||||
onChange={handleChange}
|
||||
value={form.password}
|
||||
other={{ type: 'password' }}
|
||||
/>
|
||||
<TextField
|
||||
required
|
||||
label='Confirm Password'
|
||||
name='confirmPassword'
|
||||
onChange={handleChange}
|
||||
value={form.confirmPassword}
|
||||
other={{ type: 'password' }}
|
||||
/>
|
||||
{error && (
|
||||
<Box textAlign='right'>
|
||||
<Typography variant='body2' color='secondary'>
|
||||
{error}
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
<FormSubmit
|
||||
color='primary'
|
||||
type='submit'
|
||||
other={{ disabled: form.password !== form.confirmPassword }}
|
||||
>
|
||||
Create an Account
|
||||
</FormSubmit>
|
||||
<Typography variant='body1'>
|
||||
Already have an account? <Link to='/login'>Login</Link>
|
||||
</Typography>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
export default RegisterForm;
|
||||
46
client/src/components/ResetForm.jsx
Normal file
46
client/src/components/ResetForm.jsx
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import React from 'react';
|
||||
import { Box, Typography } from '@mui/material';
|
||||
|
||||
import { FormSubmit, FormTextField } from '../StyledElements';
|
||||
|
||||
function ResetForm({ form, handleSubmit, handleChange, error }) {
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Typography variant='h4' sx={{ marginTop: '1vh 0vh' }}>
|
||||
Reset my Password
|
||||
</Typography>
|
||||
<FormTextField
|
||||
required
|
||||
label='Password'
|
||||
name='password'
|
||||
onChange={handleChange}
|
||||
value={form.password}
|
||||
other={{ type: 'password' }}
|
||||
/>
|
||||
<FormTextField
|
||||
required
|
||||
label='Confirm Password'
|
||||
name='confirmPassword'
|
||||
onChange={handleChange}
|
||||
value={form.confirmPassword}
|
||||
other={{ type: 'password' }}
|
||||
/>
|
||||
{error && (
|
||||
<Box textAlign='right'>
|
||||
<Typography variant='body2' color='error'>
|
||||
{error}
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
<FormSubmit
|
||||
color='primary'
|
||||
type='submit'
|
||||
other={{ disabled: form.password !== form.confirmPassword }}
|
||||
>
|
||||
Reset my Password
|
||||
</FormSubmit>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
export default ResetForm;
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
import React from 'react';
|
||||
|
||||
import { Paper, Typography } from '@mui/material';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
|
||||
88
client/src/components/StyledElements.jsx
Normal file
88
client/src/components/StyledElements.jsx
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
import { Button, FormGroup, Paper, Stack, TextField } from '@mui/material';
|
||||
import { styled } from '@mui/material/styles';
|
||||
|
||||
const Form = styled(FormGroup)(({ theme }) => ({
|
||||
[theme.breakpoints.only('xs')]: {
|
||||
column: true,
|
||||
},
|
||||
}));
|
||||
const FormField = styled(TextField)(({ theme }) => ({
|
||||
[theme.breakpoints.only('xs')]: {
|
||||
column: true,
|
||||
width: '75%',
|
||||
},
|
||||
}));
|
||||
const FormButton = styled(Button)(({ theme }) => ({
|
||||
disableElevation: true,
|
||||
color: theme.palette.primary.main,
|
||||
[theme.breakpoints.only('xs')]: {
|
||||
column: true,
|
||||
width: '25%',
|
||||
},
|
||||
}));
|
||||
|
||||
const DynamicStack = styled(Stack)(({ theme }) => ({
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
spacing: 2,
|
||||
alignItems: 'flex-end',
|
||||
},
|
||||
}));
|
||||
|
||||
const DynamicPaper = styled(Paper)(({ theme }) => ({
|
||||
...theme.typography.body2,
|
||||
padding: '2vh',
|
||||
textAlign: 'center',
|
||||
color: theme.palette.text.secondary,
|
||||
flexGrow: 1,
|
||||
margin: '0 2vh',
|
||||
[theme.breakpoints.only('xs')]: {
|
||||
height: '80vh',
|
||||
},
|
||||
[theme.breakpoints.only('sm')]: {
|
||||
height: '80vh',
|
||||
},
|
||||
[theme.breakpoints.only('md')]: {
|
||||
height: '82vh',
|
||||
},
|
||||
}));
|
||||
|
||||
const FormTextField = ({ label, name, value, onChange, required, other }) => {
|
||||
return (
|
||||
<TextField
|
||||
required={required}
|
||||
fullWidth
|
||||
label={label}
|
||||
name={name}
|
||||
variant='outlined'
|
||||
onChange={onChange}
|
||||
value={value}
|
||||
sx={{ margin: '1vh 0vh' }}
|
||||
{...other}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const FormSubmit = ({ color, children, type, other }) => {
|
||||
return (
|
||||
<Button
|
||||
variant='contained'
|
||||
color={color}
|
||||
sx={{ margin: '1vh 0vh' }}
|
||||
type={type}
|
||||
fullWidth
|
||||
{...other}
|
||||
>
|
||||
{children}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
export {
|
||||
Form,
|
||||
FormField,
|
||||
FormButton,
|
||||
DynamicStack,
|
||||
DynamicPaper,
|
||||
FormSubmit,
|
||||
FormTextField,
|
||||
};
|
||||
29
client/src/components/UpdateOrganizationForm.jsx
Normal file
29
client/src/components/UpdateOrganizationForm.jsx
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import React from 'react';
|
||||
import { Typography } from '@mui/material';
|
||||
|
||||
import Form from '../Form';
|
||||
import { FormSubmit, FormTextField } from '../StyledElements';
|
||||
|
||||
function UpdateOrganizationForm({ name, handleSubmit, handleChange }) {
|
||||
return (
|
||||
<Form text='Rename Organization'>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Typography variant='h4' sx={{ marginTop: '1vh 0vh' }}>
|
||||
Rename Organization
|
||||
</Typography>
|
||||
<FormTextField
|
||||
required
|
||||
label='Name'
|
||||
name='name'
|
||||
onChange={handleChange}
|
||||
value={name}
|
||||
/>
|
||||
<FormSubmit color='primary' type='submit'>
|
||||
Rename
|
||||
</FormSubmit>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
export default UpdateOrganizationForm;
|
||||
|
|
@ -1,148 +0,0 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
|
||||
import axios from 'axios';
|
||||
import Cookies from 'js-cookie';
|
||||
|
||||
import { Box, Typography, TextField, Button } from '@mui/material';
|
||||
|
||||
import Form from '../utils/Form';
|
||||
import RecordMp3 from '../utils/RecordMp3';
|
||||
|
||||
function EditUser() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [form, setForm] = useState({
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
email: '',
|
||||
pronouns: '',
|
||||
});
|
||||
const [error, setError] = useState('');
|
||||
|
||||
const linkStyle = { textDecoration: 'none', color: 'inherit' };
|
||||
|
||||
useEffect(() => {
|
||||
async function getInfo() {
|
||||
axios
|
||||
.get(
|
||||
`${process.env.REACT_APP_API_ROOT}/auth/${Cookies.get(
|
||||
'userID'
|
||||
)}?token=${Cookies.get('token')}`
|
||||
)
|
||||
.then((response) => {
|
||||
const user = response.data;
|
||||
|
||||
setForm(() => {
|
||||
return {
|
||||
firstName: user.firstName,
|
||||
lastName: user.lastName,
|
||||
email: user.email,
|
||||
pronouns: user.pronouns,
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
getInfo();
|
||||
}, []);
|
||||
|
||||
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.REACT_APP_API_ROOT}/auth/update`, {
|
||||
...form,
|
||||
token: Cookies.get('token'),
|
||||
})
|
||||
.then(() => {
|
||||
navigate('/');
|
||||
})
|
||||
.catch((e) => setError(() => e.response.data));
|
||||
}
|
||||
|
||||
return (
|
||||
<Form>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Typography variant='h4' sx={{ marginTop: '1vh 0vh' }}>
|
||||
Update Account Details
|
||||
</Typography>
|
||||
<TextField
|
||||
required
|
||||
fullWidth
|
||||
label='First Name'
|
||||
name='firstName'
|
||||
variant='outlined'
|
||||
onChange={handleChange}
|
||||
value={form.firstName}
|
||||
sx={{ margin: '1vh 0vh' }}
|
||||
/>
|
||||
<TextField
|
||||
required
|
||||
fullWidth
|
||||
label='Last Name'
|
||||
name='lastName'
|
||||
variant='outlined'
|
||||
onChange={handleChange}
|
||||
value={form.lastName}
|
||||
sx={{ margin: '1vh 0vh' }}
|
||||
/>
|
||||
<TextField
|
||||
required
|
||||
fullWidth
|
||||
label='Email'
|
||||
name='email'
|
||||
variant='outlined'
|
||||
onChange={handleChange}
|
||||
value={form.email}
|
||||
sx={{ margin: '1vh 0vh' }}
|
||||
/>
|
||||
<TextField
|
||||
fullWidth
|
||||
label='Pronouns'
|
||||
name='pronouns'
|
||||
variant='outlined'
|
||||
onChange={handleChange}
|
||||
value={form.pronouns}
|
||||
sx={{ margin: '1vh 0vh' }}
|
||||
/>
|
||||
{error && (
|
||||
<Box textAlign='right'>
|
||||
<Typography variant='body2' color='secondary'>
|
||||
{error}
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
<Button
|
||||
variant='contained'
|
||||
color='primary'
|
||||
sx={{ margin: '1vh 0vh' }}
|
||||
type='submit'
|
||||
fullWidth
|
||||
>
|
||||
Update Details
|
||||
</Button>
|
||||
<Button
|
||||
variant='contained'
|
||||
color='error'
|
||||
sx={{ margin: '1vh 0vh' }}
|
||||
fullWidth
|
||||
>
|
||||
<Link to='/recover' style={linkStyle}>
|
||||
Reset Your Password
|
||||
</Link>
|
||||
</Button>
|
||||
</form>
|
||||
<RecordMp3 />
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
export default EditUser;
|
||||
|
|
@ -1,154 +0,0 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
|
||||
import axios from 'axios';
|
||||
import Cookies from 'js-cookie';
|
||||
|
||||
import {
|
||||
Box,
|
||||
Typography,
|
||||
TextField,
|
||||
Button,
|
||||
FormControlLabel,
|
||||
Checkbox,
|
||||
} from '@mui/material';
|
||||
|
||||
import Form from '../utils/Form';
|
||||
|
||||
function Login({ setToken }) {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [form, setForm] = useState({
|
||||
email: '',
|
||||
password: '',
|
||||
remember: false,
|
||||
});
|
||||
const [error, setError] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
async function logout() {
|
||||
await axios
|
||||
.post(`${process.env.REACT_APP_API_ROOT}/auth/logout`, {
|
||||
token: Cookies.get('token'),
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e);
|
||||
});
|
||||
|
||||
Cookies.remove('token');
|
||||
Cookies.remove('userID');
|
||||
|
||||
setToken(() => false);
|
||||
}
|
||||
|
||||
if (Cookies.get('token')) {
|
||||
logout();
|
||||
}
|
||||
}, [setToken]);
|
||||
|
||||
function handleChange(e) {
|
||||
const name = e.target.name;
|
||||
const value = e.target.value;
|
||||
|
||||
setForm((form) => {
|
||||
return { ...form, [name]: value };
|
||||
});
|
||||
}
|
||||
|
||||
function handleCheckboxChange(e) {
|
||||
const name = e.target.name;
|
||||
const checked = e.target.checked;
|
||||
|
||||
setForm(() => {
|
||||
return { ...form, [name]: checked };
|
||||
});
|
||||
}
|
||||
|
||||
async function handleSubmit(e) {
|
||||
e.preventDefault();
|
||||
await axios
|
||||
.post(`${process.env.REACT_APP_API_ROOT}/auth/login`, form)
|
||||
.then((response) => {
|
||||
Cookies.set('token', response.data.token, {
|
||||
expires: form.remember ? 3650 : 1,
|
||||
});
|
||||
Cookies.set('userID', response.data.userID, {
|
||||
expires: form.remember ? 3650 : 1,
|
||||
});
|
||||
setToken(true);
|
||||
navigate('/');
|
||||
})
|
||||
.catch((e) => setError(() => e.response.data));
|
||||
}
|
||||
|
||||
return (
|
||||
<Form>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Typography variant='h4' sx={{ marginTop: '1vh 0vh' }}>
|
||||
Login
|
||||
</Typography>
|
||||
<TextField
|
||||
required
|
||||
fullWidth
|
||||
label='Email'
|
||||
name='email'
|
||||
variant='outlined'
|
||||
onChange={handleChange}
|
||||
value={form.email}
|
||||
sx={{ margin: '1vh 0vh' }}
|
||||
/>
|
||||
<TextField
|
||||
required
|
||||
fullWidth
|
||||
label='Password'
|
||||
name='password'
|
||||
type='password'
|
||||
variant='outlined'
|
||||
onChange={handleChange}
|
||||
value={form.password}
|
||||
sx={{ margin: '1vh 0vh' }}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<FormControlLabel
|
||||
control={<Checkbox checked={form.remember} />}
|
||||
labelPlacement='start'
|
||||
label='Remember me'
|
||||
name='remember'
|
||||
onChange={handleCheckboxChange}
|
||||
/>
|
||||
</div>
|
||||
{error && (
|
||||
<Box textAlign='right'>
|
||||
<Typography variant='body2' color='error'>
|
||||
{error}
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
<Button
|
||||
variant='contained'
|
||||
color='primary'
|
||||
sx={{ margin: '1vh 0vh' }}
|
||||
type='submit'
|
||||
fullWidth
|
||||
>
|
||||
Login
|
||||
</Button>
|
||||
<Typography variant='body1'>
|
||||
Dont have an account? <Link to='/register'>Register</Link>
|
||||
</Typography>
|
||||
<Typography variant='body1'>
|
||||
Forgot your password?{' '}
|
||||
<Link to='/recover'>Recover Password</Link>
|
||||
</Typography>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
export default Login;
|
||||
|
|
@ -1,76 +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.REACT_APP_API_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
|
||||
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,140 +0,0 @@
|
|||
import React, { useState } from 'react';
|
||||
import { Link, useNavigate, useSearchParams } from 'react-router-dom';
|
||||
|
||||
import axios from 'axios';
|
||||
|
||||
import { Box, Typography, TextField, Button } from '@mui/material';
|
||||
|
||||
import Form from '../utils/Form';
|
||||
|
||||
function Register() {
|
||||
const navigate = useNavigate();
|
||||
const [params] = useSearchParams();
|
||||
|
||||
const [form, setForm] = useState({
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
email: '',
|
||||
pronouns: '',
|
||||
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();
|
||||
|
||||
const { confirmPassword, ...filteredForm } = form;
|
||||
await axios
|
||||
.post(`${process.env.REACT_APP_API_ROOT}/auth/register`, {
|
||||
...filteredForm,
|
||||
orgID: params.orgID,
|
||||
})
|
||||
.then(() => {
|
||||
navigate('/login');
|
||||
})
|
||||
.catch((e) => setError(() => e.response.data));
|
||||
}
|
||||
|
||||
return (
|
||||
<Form>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Typography variant='h4' sx={{ marginTop: '1vh 0vh' }}>
|
||||
Create an Account
|
||||
</Typography>
|
||||
<TextField
|
||||
required
|
||||
fullWidth
|
||||
label='First Name'
|
||||
name='firstName'
|
||||
variant='outlined'
|
||||
onChange={handleChange}
|
||||
value={form.firstName}
|
||||
sx={{ margin: '1vh 0vh' }}
|
||||
/>
|
||||
<TextField
|
||||
required
|
||||
fullWidth
|
||||
label='Last Name'
|
||||
name='lastName'
|
||||
variant='outlined'
|
||||
onChange={handleChange}
|
||||
value={form.lastName}
|
||||
sx={{ margin: '1vh 0vh' }}
|
||||
/>
|
||||
<TextField
|
||||
required
|
||||
fullWidth
|
||||
label='Email'
|
||||
name='email'
|
||||
variant='outlined'
|
||||
onChange={handleChange}
|
||||
value={form.email}
|
||||
sx={{ margin: '1vh 0vh' }}
|
||||
/>
|
||||
<TextField
|
||||
fullWidth
|
||||
label='Pronouns'
|
||||
name='pronouns'
|
||||
variant='outlined'
|
||||
onChange={handleChange}
|
||||
value={form.pronouns}
|
||||
sx={{ margin: '1vh 0vh' }}
|
||||
/>
|
||||
<TextField
|
||||
required
|
||||
fullWidth
|
||||
label='Password'
|
||||
name='password'
|
||||
type='password'
|
||||
variant='outlined'
|
||||
onChange={handleChange}
|
||||
value={form.password}
|
||||
sx={{ margin: '1vh 0vh' }}
|
||||
/>
|
||||
<TextField
|
||||
required
|
||||
fullWidth
|
||||
label='Confirm Password'
|
||||
name='confirmPassword'
|
||||
type='password'
|
||||
variant='outlined'
|
||||
onChange={handleChange}
|
||||
value={form.confirmPassword}
|
||||
sx={{ margin: '1vh 0vh' }}
|
||||
/>
|
||||
{error && (
|
||||
<Box textAlign='right'>
|
||||
<Typography variant='body2' color='secondary'>
|
||||
{error}
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
<Button
|
||||
variant='contained'
|
||||
color='primary'
|
||||
sx={{ margin: '1vh 0vh' }}
|
||||
type='submit'
|
||||
fullWidth
|
||||
disabled={form.password !== form.confirmPassword}
|
||||
>
|
||||
Create an Account
|
||||
</Button>
|
||||
<Typography variant='body1'>
|
||||
Already have an account? <Link to='/login'>Login</Link>
|
||||
</Typography>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
export default Register;
|
||||
|
|
@ -1,90 +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.REACT_APP_API_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
|
||||
label='Password'
|
||||
name='password'
|
||||
variant='outlined'
|
||||
type='password'
|
||||
onChange={handleChange}
|
||||
value={form.password}
|
||||
sx={{ margin: '1vh 0vh' }}
|
||||
/>
|
||||
<TextField
|
||||
required
|
||||
fullWidth
|
||||
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;
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
import React, { useState } from 'react';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
|
||||
import axios from 'axios';
|
||||
import Cookies from 'js-cookie';
|
||||
|
||||
import { Typography, TextField, Button } from '@mui/material';
|
||||
|
||||
import Form from '../utils/Form';
|
||||
|
||||
function UpdateOrganization() {
|
||||
const { orgID } = useParams();
|
||||
const navigate = useNavigate();
|
||||
|
||||
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_API_ROOT}/org/update`, {
|
||||
token: Cookies.get('token'),
|
||||
orgName: name,
|
||||
orgID,
|
||||
})
|
||||
.then(() => {
|
||||
navigate(`/org/${orgID}`);
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Form>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Typography variant='h4' sx={{ marginTop: '1vh 0vh' }}>
|
||||
Rename Organization
|
||||
</Typography>
|
||||
<TextField
|
||||
required
|
||||
fullWidth
|
||||
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;
|
||||
|
|
@ -1,263 +0,0 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { useNavigate, useParams, Link } from 'react-router-dom';
|
||||
|
||||
import axios from 'axios';
|
||||
import Cookies from 'js-cookie';
|
||||
|
||||
import {
|
||||
Typography,
|
||||
Grid,
|
||||
Stack,
|
||||
Box,
|
||||
ButtonGroup,
|
||||
Button,
|
||||
Accordion,
|
||||
AccordionSummary,
|
||||
AccordionDetails,
|
||||
useMediaQuery,
|
||||
} from '@mui/material';
|
||||
import { styled } from '@mui/material/styles';
|
||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||
|
||||
import Dashboard from '../utils/Dashboard';
|
||||
|
||||
const DynamicStack = styled(Stack)(({ theme }) => ({
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
spacing: 2,
|
||||
alignItems: 'flex-end',
|
||||
},
|
||||
}));
|
||||
|
||||
function OrgDashboard() {
|
||||
const xs = useMediaQuery((theme) => theme.breakpoints.only('xs'));
|
||||
const { orgID } = useParams();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [org, setOrg] = useState({
|
||||
id: orgID,
|
||||
name: '',
|
||||
owner: {
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
email: '',
|
||||
},
|
||||
member: [],
|
||||
createdAt: '',
|
||||
memberCount: 0,
|
||||
});
|
||||
const [rows, setRows] = useState([]);
|
||||
const [status, setStatus] = useState(true);
|
||||
const [selection, setSelection] = useState(null);
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const linkStyle = { textDecoration: 'none', color: 'inherit' };
|
||||
const buttonGroupOrientation = () => {
|
||||
if (xs) return 'vertical';
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
async function getData() {
|
||||
await axios
|
||||
.get(
|
||||
`${
|
||||
process.env.REACT_APP_API_ROOT
|
||||
}/org/${orgID}?token=${Cookies.get('token')}`
|
||||
)
|
||||
.then((response) => {
|
||||
const res = response.data;
|
||||
|
||||
setOrg(() => {
|
||||
return { ...res.org, memberCount: res.memberCount };
|
||||
});
|
||||
|
||||
setRows(() => res.org.member);
|
||||
setStatus(() => res.status);
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e);
|
||||
navigate('/');
|
||||
});
|
||||
}
|
||||
|
||||
getData();
|
||||
}, [navigate, orgID]);
|
||||
|
||||
async function handleDelete() {
|
||||
await axios
|
||||
.delete(`${process.env.REACT_APP_API_ROOT}/org/delete`, {
|
||||
params: {
|
||||
token: Cookies.get('token'),
|
||||
orgID,
|
||||
},
|
||||
})
|
||||
.then(navigate('/'));
|
||||
}
|
||||
|
||||
function copyID() {
|
||||
navigator.clipboard.writeText(orgID);
|
||||
}
|
||||
|
||||
function copyJoinLink() {
|
||||
navigator.clipboard.writeText(
|
||||
`${process.env.REACT_APP_DOMAIN_ROOT}/register?orgID=${orgID}`
|
||||
);
|
||||
}
|
||||
|
||||
async function removeSelected() {
|
||||
const doomedUserIDs = selection.map((row) => row.id);
|
||||
|
||||
await axios.post(
|
||||
`${process.env.REACT_APP_API_ROOT}/org/${orgID}/delete`,
|
||||
{
|
||||
token: Cookies.get('token'),
|
||||
orgID,
|
||||
doomedUserIDs,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async function leaveOrg() {
|
||||
await axios
|
||||
.post(`${process.env.REACT_APP_API_ROOT}/org/${orgID}/delete`, {
|
||||
token: Cookies.get('token'),
|
||||
orgID,
|
||||
})
|
||||
.then(() => navigate('/'))
|
||||
.catch((e) => {
|
||||
console.log(e);
|
||||
navigate('/');
|
||||
});
|
||||
}
|
||||
|
||||
function handleOpen() {
|
||||
setOpen((initial) => (initial ? false : true));
|
||||
}
|
||||
|
||||
async function play(id) {
|
||||
axios
|
||||
.get(
|
||||
`${process.env.REACT_APP_DOMAIN_ROOT}/public/audio/${id}.mp3`,
|
||||
{
|
||||
responseType: 'blob',
|
||||
}
|
||||
)
|
||||
.then((res) => {
|
||||
const uploadedFile = new File([res.data], 'userAudio.mp3', {
|
||||
type: res.data.type,
|
||||
lastModified: Date.now(),
|
||||
});
|
||||
const audioFile = new Audio(URL.createObjectURL(uploadedFile));
|
||||
audioFile.play();
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Dashboard
|
||||
rows={rows}
|
||||
setSelection={setSelection}
|
||||
onClick={play}
|
||||
open={open}
|
||||
>
|
||||
<Accordion expanded={open} onChange={handleOpen}>
|
||||
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
||||
<Typography variant='h6'>
|
||||
Organizations/{org.name}
|
||||
</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<Box sx={{ marginTop: '1vh' }} fullWidth>
|
||||
<Grid
|
||||
container
|
||||
columns={{ xs: 3, sm: 12, md: 12 }}
|
||||
spacing={1}
|
||||
>
|
||||
<Grid item xs={3}>
|
||||
<Stack>
|
||||
<Typography variant='body1'>
|
||||
Organization ID: {orgID}
|
||||
</Typography>
|
||||
<Typography variant='body1'>
|
||||
Members: {org.memberCount}
|
||||
</Typography>
|
||||
<Typography variant='body1'>
|
||||
Created On: {org.createdAt}
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid item xs={3}>
|
||||
<Stack>
|
||||
<Typography variant='body1'>
|
||||
Owner:{' '}
|
||||
{`${org.owner.firstName} ${org.owner.lastName}`}
|
||||
</Typography>
|
||||
<Typography variant='body1'>
|
||||
Email: {org.owner.email}
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid item xs={3} sm={6} md={6}>
|
||||
<DynamicStack spacing={1}>
|
||||
{status ? (
|
||||
<React.Fragment>
|
||||
<ButtonGroup
|
||||
variant='outlined'
|
||||
orientation={buttonGroupOrientation()}
|
||||
>
|
||||
<Button color='warning'>
|
||||
<Link
|
||||
to='update'
|
||||
style={linkStyle}
|
||||
>
|
||||
Edit {org.name}
|
||||
</Link>
|
||||
</Button>
|
||||
<Button
|
||||
color='error'
|
||||
onClick={handleDelete}
|
||||
>
|
||||
Delete {org.name}
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
<ButtonGroup
|
||||
variant='outlined'
|
||||
orientation={buttonGroupOrientation()}
|
||||
>
|
||||
<Button
|
||||
color='success'
|
||||
onClick={copyID}
|
||||
>
|
||||
Copy Org ID
|
||||
</Button>
|
||||
<Button
|
||||
color='success'
|
||||
onClick={copyJoinLink}
|
||||
>
|
||||
Copy Org Join Link
|
||||
</Button>
|
||||
<Button
|
||||
color='error'
|
||||
onClick={removeSelected}
|
||||
>
|
||||
Remove People
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<Button
|
||||
color='error'
|
||||
onClick={leaveOrg}
|
||||
>
|
||||
Leave Org
|
||||
</Button>
|
||||
)}
|
||||
</DynamicStack>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
</Dashboard>
|
||||
);
|
||||
}
|
||||
|
||||
export default OrgDashboard;
|
||||
|
|
@ -1,104 +0,0 @@
|
|||
import React from 'react';
|
||||
|
||||
import { Paper, Box, IconButton, useMediaQuery, Collapse } from '@mui/material';
|
||||
import { styled } from '@mui/material/styles';
|
||||
import { DataGrid } from '@mui/x-data-grid';
|
||||
import PlayArrowIcon from '@mui/icons-material/PlayArrow';
|
||||
|
||||
import MemberUnit from '../utils/MemberUnit';
|
||||
|
||||
const DynamicPaper = styled(Paper)(({ theme }) => ({
|
||||
...theme.typography.body2,
|
||||
padding: '2vh',
|
||||
textAlign: 'center',
|
||||
color: theme.palette.text.secondary,
|
||||
flexGrow: 1,
|
||||
margin: '0 2vh',
|
||||
[theme.breakpoints.only('xs')]: {
|
||||
height: '80vh',
|
||||
},
|
||||
[theme.breakpoints.only('sm')]: {
|
||||
height: '80vh',
|
||||
},
|
||||
[theme.breakpoints.only('md')]: {
|
||||
height: '82vh',
|
||||
},
|
||||
}));
|
||||
|
||||
function Dashboard({ children, rows, setSelection, onClick, open }) {
|
||||
const xs = useMediaQuery((theme) => theme.breakpoints.only('xs'));
|
||||
const sm = useMediaQuery((theme) => theme.breakpoints.only('sm'));
|
||||
const md = useMediaQuery((theme) => theme.breakpoints.only('md'));
|
||||
|
||||
const columns = [
|
||||
{ field: 'id', headerName: 'ID', flex: 1 },
|
||||
{ field: 'firstName', headerName: 'First Name', flex: 3 },
|
||||
{ field: 'lastName', headerName: 'Last Name', flex: 3 },
|
||||
{ field: 'nickname', headerName: 'Nickname', flex: 2 },
|
||||
{
|
||||
field: 'namePronounciation',
|
||||
headerName: 'Pronounciation',
|
||||
flex: 2,
|
||||
sortable: false,
|
||||
renderCell: (params) => (
|
||||
<IconButton
|
||||
size='large'
|
||||
edge='start'
|
||||
color='inherit'
|
||||
aria-label='menu'
|
||||
sx={{ mr: 2 }}
|
||||
onClick={() => onClick(params.id)}
|
||||
>
|
||||
<PlayArrowIcon />
|
||||
</IconButton>
|
||||
),
|
||||
},
|
||||
{ field: 'pronouns', headerName: 'Pronouns', flex: 2 },
|
||||
{ field: 'email', headerName: 'Email', flex: 10, sortable: false },
|
||||
];
|
||||
|
||||
function calculateCollapsedSize() {
|
||||
if (xs) return '37vh';
|
||||
if (sm) return '63vh';
|
||||
if (md) return '68vh';
|
||||
}
|
||||
|
||||
function calculateTableHeight() {
|
||||
return open ? '64vh' : '100%';
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Box sx={{ padding: '2vh' }}>{children}</Box>
|
||||
<Collapse
|
||||
collapsedSize={calculateCollapsedSize()}
|
||||
in={open ? false : true}
|
||||
>
|
||||
<DynamicPaper>
|
||||
{(xs || sm) &&
|
||||
rows.map((member) => {
|
||||
return (
|
||||
<MemberUnit member={member} onClick={onClick} />
|
||||
);
|
||||
})}
|
||||
{md && (
|
||||
<Box height={calculateTableHeight()}>
|
||||
<DataGrid
|
||||
rows={rows}
|
||||
columns={columns}
|
||||
pageSize={5}
|
||||
rowsPerPageOptions={[5]}
|
||||
onSelectionModelChange={(newSelection) => {
|
||||
setSelection(() => newSelection.rows);
|
||||
}}
|
||||
checkboxSelection
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</DynamicPaper>
|
||||
</Collapse>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
export default Dashboard;
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
import React from 'react';
|
||||
import { Outlet } from 'react-router-dom';
|
||||
|
||||
import { Box, Grid, Paper } from '@mui/material';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
|
||||
function Form({ children }) {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Box sx={{ flexGrow: 1 }}>
|
||||
<Grid
|
||||
container
|
||||
spacing={2}
|
||||
alignItems='center'
|
||||
style={{ height: '95vh' }}
|
||||
>
|
||||
<Grid item xs={0.5} sm={2} md={4} />
|
||||
<Grid item xs={11} sm={8} md={4}>
|
||||
<Paper
|
||||
sx={{
|
||||
...theme.typography.body2,
|
||||
padding: '2vh',
|
||||
textAlign: 'center',
|
||||
color: theme.palette.text.secondary,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
<Outlet />
|
||||
</Paper>
|
||||
</Grid>
|
||||
<Grid item xs={0.5} sm={2} md={4} />
|
||||
</Grid>
|
||||
</Box>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
export default Form;
|
||||
75
client/src/controllers/EditUser.jsx
Normal file
75
client/src/controllers/EditUser.jsx
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import axios from 'axios';
|
||||
import Cookies from 'js-cookie';
|
||||
|
||||
import EditUserForm from '../components/EditUserForm';
|
||||
|
||||
function EditUser() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [form, setForm] = useState({
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
email: '',
|
||||
pronouns: '',
|
||||
});
|
||||
const [error, setError] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
async function getInfo() {
|
||||
axios
|
||||
.get(
|
||||
`${process.env.REACT_APP_API_ROOT}/auth/${Cookies.get(
|
||||
'userID'
|
||||
)}?token=${Cookies.get('token')}`
|
||||
)
|
||||
.then((response) => {
|
||||
const user = response.data;
|
||||
|
||||
setForm(() => {
|
||||
return {
|
||||
firstName: user.firstName,
|
||||
lastName: user.lastName,
|
||||
email: user.email,
|
||||
pronouns: user.pronouns,
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
getInfo();
|
||||
}, []);
|
||||
|
||||
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.REACT_APP_API_ROOT}/auth/update`, {
|
||||
...form,
|
||||
token: Cookies.get('token'),
|
||||
})
|
||||
.then(() => {
|
||||
navigate('/');
|
||||
})
|
||||
.catch((e) => setError(() => e.response.data));
|
||||
}
|
||||
|
||||
return (
|
||||
<EditUserForm
|
||||
form={form}
|
||||
error={error}
|
||||
handleSubmit={handleSubmit}
|
||||
handleChange={handleChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default EditUser;
|
||||
85
client/src/controllers/Login.jsx
Normal file
85
client/src/controllers/Login.jsx
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import axios from 'axios';
|
||||
import Cookies from 'js-cookie';
|
||||
|
||||
import LoginForm from '../components/LoginForm';
|
||||
|
||||
function Login({ setToken }) {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [form, setForm] = useState({
|
||||
email: '',
|
||||
password: '',
|
||||
remember: false,
|
||||
});
|
||||
const [error, setError] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
async function logout() {
|
||||
await axios
|
||||
.post(`${process.env.REACT_APP_API_ROOT}/auth/logout`, {
|
||||
token: Cookies.get('token'),
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e);
|
||||
});
|
||||
|
||||
Cookies.remove('token');
|
||||
Cookies.remove('userID');
|
||||
|
||||
setToken(() => false);
|
||||
}
|
||||
|
||||
if (Cookies.get('token')) {
|
||||
logout();
|
||||
}
|
||||
}, [setToken]);
|
||||
|
||||
function handleChange(e) {
|
||||
const name = e.target.name;
|
||||
const value = e.target.value;
|
||||
|
||||
setForm((form) => {
|
||||
return { ...form, [name]: value };
|
||||
});
|
||||
}
|
||||
|
||||
function handleCheckboxChange(e) {
|
||||
const name = e.target.name;
|
||||
const checked = e.target.checked;
|
||||
|
||||
setForm(() => {
|
||||
return { ...form, [name]: checked };
|
||||
});
|
||||
}
|
||||
|
||||
async function handleSubmit(e) {
|
||||
e.preventDefault();
|
||||
await axios
|
||||
.post(`${process.env.REACT_APP_API_ROOT}/auth/login`, form)
|
||||
.then((response) => {
|
||||
Cookies.set('token', response.data.token, {
|
||||
expires: form.remember ? 3650 : 1,
|
||||
});
|
||||
Cookies.set('userID', response.data.userID, {
|
||||
expires: form.remember ? 3650 : 1,
|
||||
});
|
||||
setToken(true);
|
||||
navigate('/');
|
||||
})
|
||||
.catch((e) => setError(() => e.response.data));
|
||||
}
|
||||
|
||||
return (
|
||||
<LoginForm
|
||||
form={form}
|
||||
error={error}
|
||||
handleSubmit={handleSubmit}
|
||||
handleChange={handleChange}
|
||||
handleCheckboxChange={handleCheckboxChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default Login;
|
||||
101
client/src/controllers/MainPage.jsx
Normal file
101
client/src/controllers/MainPage.jsx
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import axios from 'axios';
|
||||
import Cookies from 'js-cookie';
|
||||
|
||||
import MainPageBase from '../components/MainPageBase';
|
||||
|
||||
function MainPage() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [ownedOrgs, setOwnedOrgs] = useState([]);
|
||||
const [orgs, setOrgs] = useState([]);
|
||||
const [form, setForm] = useState({
|
||||
name: '',
|
||||
id: '',
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (Cookies.get('token')) {
|
||||
getMemberOrgs();
|
||||
getOwnedOrgs();
|
||||
} else {
|
||||
navigate('/login');
|
||||
}
|
||||
}, [navigate]);
|
||||
|
||||
function handleChange(e) {
|
||||
const name = e.target.name;
|
||||
const value = e.target.value;
|
||||
|
||||
setForm((form) => {
|
||||
return { ...form, [name]: value };
|
||||
});
|
||||
}
|
||||
|
||||
async function getOwnedOrgs() {
|
||||
await axios
|
||||
.get(
|
||||
`${process.env.REACT_APP_API_ROOT}/auth?token=${Cookies.get(
|
||||
'token'
|
||||
)}`
|
||||
)
|
||||
.then((response) => {
|
||||
setOwnedOrgs(() => response.data);
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e);
|
||||
});
|
||||
}
|
||||
|
||||
async function getMemberOrgs() {
|
||||
await axios
|
||||
.get(
|
||||
`${
|
||||
process.env.REACT_APP_API_ROOT
|
||||
}/auth/orgs?token=${Cookies.get('token')}`
|
||||
)
|
||||
.then((response) => {
|
||||
setOrgs(() => response.data);
|
||||
});
|
||||
}
|
||||
|
||||
async function createOrg(e) {
|
||||
e.preventDefault();
|
||||
console.log('create');
|
||||
|
||||
await axios
|
||||
.post(`${process.env.REACT_APP_API_ROOT}/org/create`, {
|
||||
token: Cookies.get('token'),
|
||||
orgName: form.name,
|
||||
})
|
||||
.then(() => {
|
||||
getOwnedOrgs();
|
||||
});
|
||||
}
|
||||
|
||||
async function joinOrg(e) {
|
||||
e.preventDefault();
|
||||
|
||||
await axios
|
||||
.post(`${process.env.REACT_APP_API_ROOT}/org/${form.id}/add`, {
|
||||
token: Cookies.get('token'),
|
||||
})
|
||||
.then(() => {
|
||||
getMemberOrgs();
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<MainPageBase
|
||||
form={form}
|
||||
orgs={orgs}
|
||||
ownedOrgs={ownedOrgs}
|
||||
joinOrg={joinOrg}
|
||||
createOrg={createOrg}
|
||||
handleChange={handleChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default MainPage;
|
||||
|
|
@ -1,12 +1,13 @@
|
|||
import React from 'react';
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import axios from 'axios';
|
||||
import Cookies from 'js-cookie';
|
||||
|
||||
import Organizations from '../utils/Organizations';
|
||||
import Organizations from '../components/Organizations';
|
||||
|
||||
function JoinedOrgs() {
|
||||
async function getOrgs(setOrgs) {
|
||||
const [orgs, setOrgs] = useState([]);
|
||||
|
||||
async function getOrgs() {
|
||||
axios
|
||||
.get(
|
||||
`${
|
||||
|
|
@ -18,7 +19,9 @@ function JoinedOrgs() {
|
|||
});
|
||||
}
|
||||
|
||||
return <Organizations retrieveOrgs={getOrgs} />;
|
||||
useEffect(getOrgs);
|
||||
|
||||
return <Organizations orgs={orgs} />;
|
||||
}
|
||||
|
||||
export default JoinedOrgs;
|
||||
136
client/src/controllers/OrgDashboard.jsx
Normal file
136
client/src/controllers/OrgDashboard.jsx
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import axios from 'axios';
|
||||
import Cookies from 'js-cookie';
|
||||
|
||||
import Dashboard from '../components/Dashboard';
|
||||
|
||||
function OrgDashboard() {
|
||||
const { orgID } = useParams();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [org, setOrg] = useState({
|
||||
id: orgID,
|
||||
name: '',
|
||||
owner: {
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
email: '',
|
||||
},
|
||||
member: [],
|
||||
createdAt: '',
|
||||
memberCount: 0,
|
||||
});
|
||||
const [rows, setRows] = useState([]);
|
||||
const [status, setStatus] = useState(true);
|
||||
const [selection, setSelection] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
async function getData() {
|
||||
await axios
|
||||
.get(
|
||||
`${
|
||||
process.env.REACT_APP_API_ROOT
|
||||
}/org/${orgID}?token=${Cookies.get('token')}`
|
||||
)
|
||||
.then((response) => {
|
||||
const res = response.data;
|
||||
|
||||
setOrg(() => {
|
||||
return { ...res.org, memberCount: res.memberCount };
|
||||
});
|
||||
|
||||
setRows(() => res.org.member);
|
||||
setStatus(() => res.status);
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e);
|
||||
navigate('/');
|
||||
});
|
||||
}
|
||||
|
||||
getData();
|
||||
}, [navigate, orgID]);
|
||||
|
||||
async function handleDelete() {
|
||||
await axios
|
||||
.delete(`${process.env.REACT_APP_API_ROOT}/org/delete`, {
|
||||
params: {
|
||||
token: Cookies.get('token'),
|
||||
orgID,
|
||||
},
|
||||
})
|
||||
.then(navigate('/'));
|
||||
}
|
||||
|
||||
function copyID() {
|
||||
navigator.clipboard.writeText(orgID);
|
||||
}
|
||||
|
||||
function copyJoinLink() {
|
||||
navigator.clipboard.writeText(
|
||||
`${process.env.REACT_APP_DOMAIN_ROOT}/register?orgID=${orgID}`
|
||||
);
|
||||
}
|
||||
|
||||
async function removeSelected() {
|
||||
const doomedUserIDs = selection.map((row) => row.id);
|
||||
|
||||
await axios.post(
|
||||
`${process.env.REACT_APP_API_ROOT}/org/${orgID}/delete`,
|
||||
{
|
||||
token: Cookies.get('token'),
|
||||
orgID,
|
||||
doomedUserIDs,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async function leaveOrg() {
|
||||
await axios
|
||||
.post(`${process.env.REACT_APP_API_ROOT}/org/${orgID}/delete`, {
|
||||
token: Cookies.get('token'),
|
||||
orgID,
|
||||
})
|
||||
.then(() => navigate('/'))
|
||||
.catch((e) => {
|
||||
console.log(e);
|
||||
navigate('/');
|
||||
});
|
||||
}
|
||||
|
||||
async function play(id) {
|
||||
await axios
|
||||
.get(
|
||||
`${process.env.REACT_APP_DOMAIN_ROOT}/public/audio/${id}.mp3`,
|
||||
{
|
||||
responseType: 'blob',
|
||||
}
|
||||
)
|
||||
.then((res) => {
|
||||
const uploadedFile = new File([res.data], 'userAudio.mp3', {
|
||||
type: res.data.type,
|
||||
lastModified: Date.now(),
|
||||
});
|
||||
const audioFile = new Audio(URL.createObjectURL(uploadedFile));
|
||||
audioFile.play();
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Dashboard
|
||||
org={org}
|
||||
rows={rows}
|
||||
setSelection={setSelection}
|
||||
onClick={play}
|
||||
status={status}
|
||||
handleDelete={handleDelete}
|
||||
copyID={copyID}
|
||||
copyJoinLink={copyJoinLink}
|
||||
removeSelected={removeSelected}
|
||||
leaveOrg={leaveOrg}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default OrgDashboard;
|
||||
|
|
@ -1,12 +1,13 @@
|
|||
import React from 'react';
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import axios from 'axios';
|
||||
import Cookies from 'js-cookie';
|
||||
|
||||
import Organizations from '../utils/Organizations';
|
||||
import Organizations from '../components/Organizations';
|
||||
|
||||
function JoinedOrgs() {
|
||||
async function getOrgs(setOrgs) {
|
||||
const [orgs, setOrgs] = useState([]);
|
||||
|
||||
async function getOrgs() {
|
||||
axios
|
||||
.get(
|
||||
`${process.env.REACT_APP_API_ROOT}/auth?token=${Cookies.get(
|
||||
|
|
@ -18,7 +19,9 @@ function JoinedOrgs() {
|
|||
});
|
||||
}
|
||||
|
||||
return <Organizations retrieveOrgs={getOrgs} />;
|
||||
useEffect(getOrgs);
|
||||
|
||||
return <Organizations orgs={orgs} />;
|
||||
}
|
||||
|
||||
export default JoinedOrgs;
|
||||
45
client/src/controllers/Recover.jsx
Normal file
45
client/src/controllers/Recover.jsx
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
import React, { useState } from 'react';
|
||||
import axios from 'axios';
|
||||
|
||||
import RecoverForm from '../components/RecoverForm';
|
||||
|
||||
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.REACT_APP_API_ROOT}/auth/reset-password?${form.email}`
|
||||
)
|
||||
.then((response) => {
|
||||
setSuccess(() => response.data);
|
||||
})
|
||||
.catch((e) => setError(() => e.response.data));
|
||||
}
|
||||
|
||||
return (
|
||||
<RecoverForm
|
||||
form={form}
|
||||
error={error}
|
||||
success={success}
|
||||
handleChange={handleChange}
|
||||
handleSubmit={handleSubmit}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default Recover;
|
||||
55
client/src/controllers/Register.jsx
Normal file
55
client/src/controllers/Register.jsx
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
import React, { useState } from 'react';
|
||||
import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||
import axios from 'axios';
|
||||
|
||||
import RegisterForm from '../components/RegisterForm';
|
||||
|
||||
function Register() {
|
||||
const navigate = useNavigate();
|
||||
const [params] = useSearchParams();
|
||||
|
||||
const [form, setForm] = useState({
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
email: '',
|
||||
pronouns: '',
|
||||
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();
|
||||
|
||||
const { confirmPassword, ...filteredForm } = form;
|
||||
await axios
|
||||
.post(`${process.env.REACT_APP_API_ROOT}/auth/register`, {
|
||||
...filteredForm,
|
||||
orgID: params.orgID,
|
||||
})
|
||||
.then(() => {
|
||||
navigate('/login');
|
||||
})
|
||||
.catch((e) => setError(() => e.response.data));
|
||||
}
|
||||
|
||||
return (
|
||||
<RegisterForm
|
||||
form={form}
|
||||
handleSubmit={handleSubmit}
|
||||
handleChange={handleChange}
|
||||
error={error}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default Register;
|
||||
51
client/src/controllers/Reset.jsx
Normal file
51
client/src/controllers/Reset.jsx
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
import React, { useState } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import axios from 'axios';
|
||||
|
||||
import ResetForm from '../components/ResetForm';
|
||||
|
||||
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.REACT_APP_API_ROOT}/auth/reset-password/${id}`,
|
||||
{
|
||||
password: form.password,
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
navigate('/login');
|
||||
})
|
||||
.catch((e) => setError(() => e.response.data));
|
||||
}
|
||||
|
||||
return (
|
||||
<ResetForm
|
||||
form={form}
|
||||
handleSubmit={handleSubmit}
|
||||
handleChange={handleChange}
|
||||
error={error}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default Reset;
|
||||
42
client/src/controllers/UpdateOrganization.jsx
Normal file
42
client/src/controllers/UpdateOrganization.jsx
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import React, { useState } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import axios from 'axios';
|
||||
import Cookies from 'js-cookie';
|
||||
|
||||
import UpdateOrganizationForm from '../components/UpdateOrganizationForm';
|
||||
|
||||
function UpdateOrganization() {
|
||||
const { orgID } = useParams();
|
||||
const navigate = useNavigate();
|
||||
|
||||
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_API_ROOT}/org/update`, {
|
||||
token: Cookies.get('token'),
|
||||
orgName: name,
|
||||
orgID,
|
||||
})
|
||||
.then(() => {
|
||||
navigate(`/org/${orgID}`);
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<UpdateOrganizationForm
|
||||
name={name}
|
||||
handleSubmit={handleSubmit}
|
||||
handleChange={handleChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default UpdateOrganization;
|
||||
Reference in a new issue