Refactored all components into controllers and dummies

This commit is contained in:
Ceferino Patino 2022-04-13 19:14:15 -05:00
commit b62d177654
37 changed files with 1425 additions and 1347 deletions

View file

@ -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);

View 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;

View 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;

View 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;

View 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;

View file

@ -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;

View 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;

View file

@ -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';

View file

@ -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' };

View file

@ -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';

View file

@ -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={{

View file

@ -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(

View 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;

View 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;

View 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;

View file

@ -1,5 +1,4 @@
import React from 'react';
import { Paper, Typography } from '@mui/material';
import { useTheme } from '@mui/material/styles';

View 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,
};

View 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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View 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;

View 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;

View 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;

View file

@ -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;

View 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;

View file

@ -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;

View 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;

View 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;

View 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;

View 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;