Added typescript and refactoring

This commit is contained in:
Ceferino Patino 2024-02-21 22:46:42 -06:00
commit db1e41b949
158 changed files with 10769 additions and 20499 deletions

View file

@ -1,2 +0,0 @@
node_modules
.env

12
.eslintrc.yml Normal file
View file

@ -0,0 +1,12 @@
env:
browser: true
es2021: true
extends: [airbnb, "prettier"]
parserOptions:
ecmaVersion: latest
sourceType: module
project: ./tsconfig.json
ignorePatterns: ["vite.config.js"]
rules:
indent: ["error", 4]
comma-dangle: "off"

25
client/.eslintrc.yml Normal file
View file

@ -0,0 +1,25 @@
root: false
env: { browser: true, es2020: true }
extends:
- "eslint:recommended"
- "airbnb-typescript"
- "plugin:@typescript-eslint/recommended"
- "plugin:react-hooks/recommended"
ignorePatterns: ["dist", ".eslintrc.cjs"]
parser: "@typescript-eslint/parser"
plugins: ["react-refresh"]
rules:
"react-refresh/only-export-components":
["warn", { allowConstantExport: true }]
"react/jsx-indent": ["error", 4]
"react/jsx-indent-props": ["error", 4]
"react/jsx-props-no-spreading": "off"
"react/react-in-jsx-scope": "off"
"react/require-default-props": "off"
"@typescript-eslint/indent": ["error", 4]
"@typescript-eslint/quotes": ["error", "double"]
"@typescript-eslint/no-shadow": "off"
"@typescript-eslint/comma-dangle": "off"
"import/no-extraneous-dependencies": "off"
"import/no-cycle": "off"
"jsx-a11y/label-has-associated-control": "off"

41
client/.gitignore vendored
View file

@ -1,23 +1,24 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

13
client/index.html Normal file
View file

@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>

View file

@ -1,46 +1,47 @@
{
"name": "client",
"version": "0.1.0",
"private": true,
"dependencies": {
"@emotion/react": "^11.8.1",
"@emotion/styled": "^11.8.1",
"@mui/icons-material": "^5.4.4",
"@mui/material": "^5.4.4",
"@mui/x-data-grid": "^5.6.0",
"axios": "^0.26.1",
"buffer": "^6.0.3",
"js-cookie": "^3.0.1",
"mic-recorder-to-mp3": "^2.2.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-helmet": "^6.1.0",
"react-router-dom": "^6.2.2",
"react-scripts": "5.0.0",
"serve": "^14.2.1"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
"name": "client",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
},
"dependencies": {
"@emotion/react": "^11.11.3",
"@emotion/styled": "^11.11.0",
"@mui/icons-material": "^5.15.10",
"@mui/material": "^5.15.10",
"@mui/x-data-grid": "^6.19.4",
"axios": "^1.6.7",
"buffer": "^6.0.3",
"js-cookie": "^3.0.5",
"mic-recorder-to-mp3": "^2.2.2",
"prop-types": "^15.8.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-helmet": "^6.1.0",
"react-router-dom": "^6.22.1",
"recordrtc": "^5.6.2"
},
"devDependencies": {
"@mui/types": "^7.2.13",
"@types/js-cookie": "^3.0.6",
"@types/node": "^20.11.19",
"@types/react": "^18.2.55",
"@types/react-dom": "^18.2.19",
"@types/react-helmet": "^6.1.11",
"@types/recordrtc": "^5.6.14",
"@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser": "^6.21.0",
"@vitejs/plugin-react-swc": "^3.5.0",
"eslint": "^8.56.0",
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.5",
"typescript": "^5.2.2",
"vite": "^5.1.0"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View file

@ -1,42 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>KnowItAll</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
--></body>
</html>

View file

@ -1,15 +0,0 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View file

@ -1,52 +0,0 @@
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
import {
Layout,
Login,
Register,
MainPage,
EditUser,
Recover,
Reset,
OwnedOrgs,
MemberOrgs,
OrgDashboard,
} from './containers';
import { ContextProvider } from './contexts';
import UpdateOrganization from './controllers/UpdateOrganizationFormController';
function App() {
return (
<BrowserRouter>
<ContextProvider>
<Routes>
<Route path='' element={<Layout />}>
<Route index element={<MainPage />} />
<Route path='/login' element={<Login />} />
<Route path='/register' element={<Register />} />
<Route path='/update' element={<EditUser />} />
<Route path='recover'>
<Route index element={<Recover />} />
<Route path=':id' element={<Reset />} />
</Route>
<Route path='org'>
<Route index element={<OwnedOrgs />} />
<Route path='joined' element={<MemberOrgs />} />
<Route path=':orgID'>
<Route index element={<OrgDashboard />} />
<Route path='update' element={<UpdateOrganization />} />
</Route>
</Route>
<Route path='*' element={<Navigate to='' />} />
</Route>
</Routes>
</ContextProvider>
</BrowserRouter>
);
}
export default App;

56
client/src/App.tsx Normal file
View file

@ -0,0 +1,56 @@
import {
BrowserRouter, Routes, Route, Navigate,
} from "react-router-dom";
import {
Layout,
MainPage,
Login,
Register,
EditUser,
Recover,
Reset,
OwnedOrgs,
MemberOrgs,
OrgDashboard,
UpdateOrganization,
} from "./containers";
import { ContextProvider } from "./contexts";
function App() {
return (
<BrowserRouter>
<ContextProvider>
<Routes>
<Route path="" element={<Layout />}>
<Route index element={<MainPage />} />
<Route path="/login" element={<Login />} />
<Route path="/register" element={<Register />} />
<Route path="/update" element={<EditUser />} />
<Route path="recover">
<Route index element={<Recover />} />
<Route path=":id" element={<Reset />} />
</Route>
<Route path="org">
<Route index element={<OwnedOrgs />} />
<Route path="joined" element={<MemberOrgs />} />
<Route path=":orgID">
<Route index element={<OrgDashboard />} />
<Route
path="update"
element={<UpdateOrganization />}
/>
</Route>
</Route>
<Route path="*" element={<Navigate to="" />} />
</Route>
</Routes>
</ContextProvider>
</BrowserRouter>
);
}
export default App;

View file

@ -1,5 +1,5 @@
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import PlayArrowIcon from '@mui/icons-material/PlayArrow';
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import PlayArrowIcon from "@mui/icons-material/PlayArrow";
import {
Accordion,
AccordionDetails,
@ -13,14 +13,31 @@ import {
Stack,
Typography,
useMediaQuery,
} from '@mui/material';
import { DataGrid } from '@mui/x-data-grid';
import { Link } from 'react-router-dom';
Theme,
} from "@mui/material";
import { DataGrid } from "@mui/x-data-grid";
import { Link } from "react-router-dom";
import { MemberUnit } from '.';
import { DynamicPaper, DynamicStack } from './StyledElements';
import MemberUnit from "./MemberUnit";
import { DynamicPaper, DynamicStack } from "./StyledElements";
import { Organization, User } from "../types";
function Dashboard({
interface DashboardProps {
rows: User[];
open: boolean;
handleOpen: () => void;
setSelection: (newSelection: User[]) => void;
onClick: (id: number) => void;
org: Organization;
status: boolean;
handleDelete: () => void;
copyID: () => void;
copyJoinLink: () => void;
removeSelected: () => void;
leaveOrg: () => void;
}
export default function Dashboard({
rows,
open,
handleOpen,
@ -33,28 +50,28 @@ function Dashboard({
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'));
}: DashboardProps) {
const xs = useMediaQuery((theme: Theme) => theme.breakpoints.only("xs"));
const sm = useMediaQuery((theme: Theme) => theme.breakpoints.only("sm"));
const md = useMediaQuery((theme: Theme) => theme.breakpoints.only("md"));
const linkStyle = { textDecoration: 'none', color: 'inherit' };
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: "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',
field: "namePronounciation",
headerName: "Pronounciation",
flex: 2,
sortable: false,
renderCell: (params) => (
renderCell: (params: User) => (
<IconButton
size='large'
edge='start'
color='inherit'
aria-label='menu'
size="large"
edge="start"
color="inherit"
aria-label="menu"
sx={{ mr: 2 }}
onClick={() => onClick(params.id)}
>
@ -62,53 +79,63 @@ function Dashboard({
</IconButton>
),
},
{ field: 'pronouns', headerName: 'Pronouns', flex: 2 },
{ field: 'email', headerName: 'Email', flex: 10, sortable: false },
{ field: "pronouns", headerName: "Pronouns", flex: 2 },
{
field: "email",
headerName: "Email",
flex: 10,
sortable: false,
},
];
const buttonGroupOrientation = () => {
if (xs) return 'vertical';
};
const buttonGroupOrientation = () => (xs ? "vertical" : "horizontal");
function calculateCollapsedSize() {
if (xs) return '37vh';
if (sm) return '63vh';
if (md) return '68vh';
if (xs) return "37vh";
if (sm) return "63vh";
return "68vh";
}
function calculateTableHeight() {
return open ? '64vh' : '100%';
return open ? "64vh" : "100%";
}
return (
<>
<Box sx={{ padding: '2vh' }}>
<Box sx={{ padding: "2vh" }}>
<Accordion expanded={open} onChange={handleOpen}>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<Typography variant='h6'>Organizations / {org.name}</Typography>
<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}>
<Box>
<Grid
container
columns={{ xs: 3, sm: 12, md: 12 }}
spacing={1}
>
<Grid item xs={3}>
<Stack>
<Typography variant='body1'>
<Typography variant="body1">
Organization ID: {org.id}
</Typography>
<Typography variant='body1'>
<Typography variant="body1">
Members: {org.memberCount}
</Typography>
<Typography variant='body1'>
<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 variant="body1">
Owner:{" "}
{`${org.owner.firstName} ${org.owner.lastName}`}
</Typography>
<Typography variant='body1'>
<Typography variant="body1">
Email: {org.owner.email}
</Typography>
</Stack>
@ -118,35 +145,53 @@ function Dashboard({
{status ? (
<>
<ButtonGroup
variant='outlined'
variant="outlined"
orientation={buttonGroupOrientation()}
>
<Button color='warning'>
<Link to='update' style={linkStyle}>
<Button color="warning">
<Link
to="update"
style={linkStyle}
>
Edit {org.name}
</Link>
</Button>
<Button color='error' onClick={handleDelete}>
<Button
color="error"
onClick={handleDelete}
>
Delete {org.name}
</Button>
</ButtonGroup>
<ButtonGroup
variant='outlined'
variant="outlined"
orientation={buttonGroupOrientation()}
>
<Button color='success' onClick={copyID}>
<Button
color="success"
onClick={copyID}
>
Copy Org ID
</Button>
<Button color='success' onClick={copyJoinLink}>
<Button
color="success"
onClick={copyJoinLink}
>
Copy Org Join Link
</Button>
<Button color='error' onClick={removeSelected}>
<Button
color="error"
onClick={removeSelected}
>
Remove People
</Button>
</ButtonGroup>
</>
) : (
<Button color='error' onClick={leaveOrg}>
<Button
color="error"
onClick={leaveOrg}
>
Leave Org
</Button>
)}
@ -157,12 +202,12 @@ function Dashboard({
</AccordionDetails>
</Accordion>
</Box>
<Collapse collapsedSize={calculateCollapsedSize()} in={open ? false : true}>
<Collapse collapsedSize={calculateCollapsedSize()} in={!open}>
<DynamicPaper>
{(xs || sm) &&
rows.map((member) => {
return <MemberUnit member={member} onClick={onClick} />;
})}
rows.map((member) => (
<MemberUnit member={member} onClick={onClick} />
))}
{md && (
<Box height={calculateTableHeight()}>
<DataGrid
@ -182,5 +227,3 @@ function Dashboard({
</>
);
}
export default Dashboard;

View file

@ -1,63 +0,0 @@
import { Box, Typography } from '@mui/material';
import { Link } from 'react-router-dom';
import { Form } from '.';
import { RecordMp3 } from '../controllers';
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,80 @@
import { Box, Typography } from "@mui/material";
import { Link } from "react-router-dom";
import Form from "./Form";
import { RecordMp3 } from "../controllers";
import { FormSubmit, FormTextField } from "./StyledElements";
interface EditUserFormProps {
form: {
firstName: string;
lastName: string;
email: string;
pronouns: string;
};
error: string;
handleSubmit: (event: React.FormEvent<HTMLFormElement>) => void;
handleChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
}
function EditUserForm({
form,
error,
handleSubmit,
handleChange,
}: EditUserFormProps) {
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

@ -1,35 +1,51 @@
import { Alert, Box, Card, CardContent, Grid, Typography } from '@mui/material';
import { useTheme } from '@mui/material/styles';
import { Outlet } from 'react-router-dom';
import {
Alert, Box, Card, CardContent, Grid, Typography,
} from "@mui/material";
import { useTheme } from "@mui/material/styles";
import { Outlet } from "react-router-dom";
function Form({ children, text, error }) {
interface FormProps {
children: React.ReactNode;
text?: string;
error?: string;
}
function Form({ children, text, error }: FormProps) {
const theme = useTheme();
return (
<Box sx={{ flexGrow: 1 }}>
<Grid container spacing={2} alignItems='center' style={{ height: '95vh' }}>
<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}>
<Card
sx={{
...theme.typography.body2,
padding: '2vh',
textAlign: 'center',
padding: "2vh",
textAlign: "center",
color: theme.palette.text.secondary,
}}
>
<CardContent>
{text && (
<Typography
variant='h5'
sx={{ margin: '1vh 0vh', fontWeight: 'bold' }}
variant="h5"
sx={{
margin: "1vh 0vh",
fontWeight: "bold",
}}
>
{text}
</Typography>
)}
{children}
<Outlet />
{error && <Alert severity='error'>{error}</Alert>}
{error && <Alert severity="error">{error}</Alert>}
</CardContent>
</Card>
</Grid>

View file

@ -1,55 +0,0 @@
import { Checkbox, FormControlLabel, Typography } from '@mui/material';
import { Link } from 'react-router-dom';
import { Form } from '.';
import { FormSubmit, FormTextField } from './StyledElements';
function Login({ form, handleSubmit, handleChange, handleCheckboxChange, error }) {
return (
<Form text='Login' error={error}>
<form onSubmit={handleSubmit}>
<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>
<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

@ -0,0 +1,77 @@
import { Checkbox, FormControlLabel, Typography } from "@mui/material";
import { Link } from "react-router-dom";
import Form from "./Form";
import { FormSubmit, FormTextField } from "./StyledElements";
interface LoginProps {
form: {
email: string;
password: string;
remember: boolean;
};
handleSubmit: (event: React.FormEvent<HTMLFormElement>) => void;
handleChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
handleCheckboxChange: (event: React.SyntheticEvent<Element, Event>) => void;
error: string;
}
function Login({
form,
handleSubmit,
handleChange,
handleCheckboxChange,
error,
}: LoginProps) {
return (
<Form text="Login" error={error}>
<form onSubmit={handleSubmit}>
<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>
<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,57 +1,88 @@
import { Box, Grid, Stack, Typography } from '@mui/material';
import { Link } from 'react-router-dom';
import {
Box, Grid, Stack, Typography,
} from "@mui/material";
import { Link } from "react-router-dom";
import OrgUnit from './OrgUnit';
import StackItem from './StackItem';
import { Form, FormButton, FormField } from './StyledElements';
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' };
import { Organization } from "../types";
interface MainPageProps {
form: { id: string; name: string };
handleChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
orgs: Organization[];
ownedOrgs: Organization[];
joinOrg: (event: React.FormEvent<HTMLFormElement>) => void;
createOrg: (event: React.FormEvent<HTMLFormElement>) => void;
}
export default function MainPage({
form,
handleChange,
orgs,
ownedOrgs,
joinOrg,
createOrg,
}: MainPageProps) {
const linkStyle = { textDecoration: "none", color: "inherit" };
return (
<Box sx={{ flexGrow: 1 }}>
<Grid container spacing={2} alignItems='center' style={{ height: '95vh' }}>
<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'>
<StackItem text="Join an Organization">
<form onSubmit={joinOrg}>
<Form>
<FormField
label='Organization ID'
name='id'
label="Organization ID"
name="id"
value={form.id}
onChange={handleChange}
/>
<FormButton type='submit' variant='outlined'>
<FormButton
type="submit"
variant="outlined"
>
Join Organization
</FormButton>
</Form>
</form>
</StackItem>
<StackItem text='Create an Organization'>
<StackItem text="Create an Organization">
<form onSubmit={createOrg}>
<Form>
<FormField
label='Organization Name'
name='name'
label="Organization Name"
name="name"
value={form.name}
onChange={handleChange}
/>
<FormButton type='submit' variant='outlined'>
<FormButton
type="submit"
variant="outlined"
>
Create Organization
</FormButton>
</Form>
</form>
</StackItem>
<StackItem>
<Typography variant='h6'>
<Link to='org' style={linkStyle}>
<Typography variant="h6">
<Link to="org" style={linkStyle}>
Your Organizations
</Link>
</Typography>
{ownedOrgs.length === 0 && (
<Typography variant='body2'>
<Typography variant="body2">
You do own any organizations
</Typography>
)}
@ -61,13 +92,13 @@ function MainPageBase({ form, orgs, ownedOrgs, joinOrg, createOrg, handleChange
))}
</StackItem>
<StackItem>
<Typography variant='h6'>
<Link to='org/joined' style={linkStyle}>
<Typography variant="h6">
<Link to="org/joined" style={linkStyle}>
Joined Organizations
</Link>
</Typography>
{orgs.length === 0 && (
<Typography variant='body2'>
<Typography variant="body2">
You are not a member in any organizations
</Typography>
)}
@ -83,5 +114,3 @@ function MainPageBase({ form, orgs, ownedOrgs, joinOrg, createOrg, handleChange
</Box>
);
}
export default MainPageBase;

View file

@ -1,46 +0,0 @@
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import PlayArrowIcon from '@mui/icons-material/PlayArrow';
import {
Accordion,
AccordionDetails,
AccordionSummary,
Grid,
IconButton,
Typography,
} from '@mui/material';
function MemberUnit({ member, onClick }) {
const { id, firstName, lastName, nickname, pronouns, email } = member;
return (
<Accordion>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<Typography>
{firstName} {lastName} ({nickname}): {pronouns}
</Typography>
</AccordionSummary>
<AccordionDetails>
<Grid container spacing={1}>
<Grid item textAlign='left' xs={11} sm={11}>
<Typography variant='body1'>ID: {id}</Typography>
<Typography variant='body1'>Email: {email}</Typography>
</Grid>
<Grid item xs={1} sm={1} justifyContent='flex-end'>
<IconButton
size='large'
edge='start'
color='inherit'
aria-label='menu'
sx={{ mr: 2 }}
onClick={() => onClick(member.id)}
>
<PlayArrowIcon />
</IconButton>
</Grid>
</Grid>
</AccordionDetails>
</Accordion>
);
}
export default MemberUnit;

View file

@ -0,0 +1,57 @@
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import PlayArrowIcon from "@mui/icons-material/PlayArrow";
import {
Accordion,
AccordionDetails,
AccordionSummary,
Grid,
IconButton,
Typography,
} from "@mui/material";
import { User } from "../types";
interface MemberUnitProps {
member: User;
onClick: (id: number) => void;
}
export default function MemberUnit({ member, onClick }: MemberUnitProps) {
const { id, firstName, lastName, nickname, pronouns, email } = member;
return (
<Accordion>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<Typography>
{firstName} {lastName} ({nickname}
): {pronouns}
</Typography>
</AccordionSummary>
<AccordionDetails>
<Grid container spacing={1}>
<Grid item textAlign="left" xs={11} sm={11}>
<Typography variant="body1">
ID:
{id}
</Typography>
<Typography variant="body1">
Email:
{email}
</Typography>
</Grid>
<Grid item xs={1} sm={1} justifyContent="flex-end">
<IconButton
size="large"
edge="start"
color="inherit"
aria-label="menu"
sx={{ mr: 2 }}
onClick={() => onClick(member.id)}
>
<PlayArrowIcon />
</IconButton>
</Grid>
</Grid>
</AccordionDetails>
</Accordion>
);
}

View file

@ -1,89 +0,0 @@
import AppRegistrationIcon from '@mui/icons-material/AppRegistration';
import DarkModeIcon from '@mui/icons-material/DarkMode';
import LightModeIcon from '@mui/icons-material/LightMode';
import LoginIcon from '@mui/icons-material/Login';
import LogoutIcon from '@mui/icons-material/Logout';
import MenuIcon from '@mui/icons-material/Menu';
import {
AppBar,
Box,
Button,
Drawer,
IconButton,
List,
ListItem,
ListItemButton,
ListItemIcon,
ListItemText,
Toolbar,
Typography,
} from '@mui/material';
import { Link } from 'react-router-dom';
export default function MobileNavbar({ logout, userData, open, setOpen, mode, toggleMode }) {
function ControlButtons() {
return userData.username ? (
<ListItem>
<ListItemButton color='inherit' component={Link} to='/login' onClick={logout}>
<ListItemIcon>
<LogoutIcon />
</ListItemIcon>
<ListItemText>Logout</ListItemText>
</ListItemButton>
</ListItem>
) : (
<>
<ListItem>
<ListItemButton color='inherit' component={Link} to='/login'>
<ListItemIcon>
<LoginIcon />
</ListItemIcon>
<ListItemText>Login</ListItemText>
</ListItemButton>
</ListItem>
<ListItem>
<ListItemButton color='inherit' component={Link} to='/register'>
<ListItemIcon>
<AppRegistrationIcon />
</ListItemIcon>
<ListItemText>Register</ListItemText>
</ListItemButton>
</ListItem>
</>
);
}
return (
<AppBar position='static'>
<Toolbar>
<IconButton component={Link} to='/' sx={{ margin: '0 1vw 0 0 ' }}>
<MenuIcon />
</IconButton>
<Typography variant='h6' component='div'>
KnowItAll
</Typography>
<Box sx={{ flexGrow: 1 }} />
<Button color='inherit' onClick={setOpen}>
Menu
</Button>
</Toolbar>
<Drawer anchor='right' open={open} onClose={setOpen}>
<List>
<ControlButtons />
<ListItem>
<ListItemButton onClick={toggleMode}>
<ListItemIcon>
{mode === 'light' ? <DarkModeIcon /> : <LightModeIcon />}
</ListItemIcon>
<ListItemText>
Switch to {mode === 'light' ? 'dark' : 'light'} mode
</ListItemText>
</ListItemButton>
</ListItem>
</List>
</Drawer>
</AppBar>
);
}

View file

@ -0,0 +1,125 @@
import AppRegistrationIcon from "@mui/icons-material/AppRegistration";
import DarkModeIcon from "@mui/icons-material/DarkMode";
import LightModeIcon from "@mui/icons-material/LightMode";
import LoginIcon from "@mui/icons-material/Login";
import LogoutIcon from "@mui/icons-material/Logout";
import MenuIcon from "@mui/icons-material/Menu";
import {
AppBar,
Box,
Button,
Drawer,
IconButton,
List,
ListItem,
ListItemButton,
ListItemIcon,
ListItemText,
Toolbar,
Typography,
} from "@mui/material";
import { Link } from "react-router-dom";
import { User, ThemeMode } from "../types";
interface ControlButtonsProps {
userData: User;
logout: () => void;
}
function ControlButtons({ userData, logout }: ControlButtonsProps) {
return userData.username ? (
<ListItem>
<ListItemButton
color="inherit"
component={Link}
to="/login"
onClick={logout}
>
<ListItemIcon>
<LogoutIcon />
</ListItemIcon>
<ListItemText>Logout</ListItemText>
</ListItemButton>
</ListItem>
) : (
<>
<ListItem>
<ListItemButton color="inherit" component={Link} to="/login">
<ListItemIcon>
<LoginIcon />
</ListItemIcon>
<ListItemText>Login</ListItemText>
</ListItemButton>
</ListItem>
<ListItem>
<ListItemButton color="inherit" component={Link} to="/register">
<ListItemIcon>
<AppRegistrationIcon />
</ListItemIcon>
<ListItemText>Register</ListItemText>
</ListItemButton>
</ListItem>
</>
);
}
interface MobileNavbarProps {
userData: User;
logout: () => void;
open: boolean;
toggleOpen: () => void;
mode: ThemeMode;
toggleMode: () => void;
}
export default function MobileNavbar({
logout,
userData,
open,
toggleOpen,
mode,
toggleMode,
}: MobileNavbarProps) {
return (
<AppBar position="static">
<Toolbar>
<IconButton
component={Link}
to="/"
sx={{ margin: "0 1vw 0 0 " }}
>
<MenuIcon />
</IconButton>
<Typography variant="h6" component="div">
KnowItAll
</Typography>
<Box sx={{ flexGrow: 1 }} />
<Button color="inherit" onClick={toggleOpen}>
Menu
</Button>
</Toolbar>
<Drawer anchor="right" open={open} onClose={toggleOpen}>
<List>
<ControlButtons userData={userData} logout={logout} />
<ListItem>
<ListItemButton onClick={toggleMode}>
<ListItemIcon>
{mode === "light" ? (
<DarkModeIcon />
) : (
<LightModeIcon />
)}
</ListItemIcon>
<ListItemText>
Switch to {mode === "light" ? "dark" : "light"}{" "}
mode
</ListItemText>
</ListItemButton>
</ListItem>
</List>
</Drawer>
</AppBar>
);
}

View file

@ -1,57 +0,0 @@
import DarkModeIcon from '@mui/icons-material/DarkMode';
import LightModeIcon from '@mui/icons-material/LightMode';
import MenuIcon from '@mui/icons-material/Menu';
import { AppBar, Box, Button, IconButton, Toolbar, Typography } from '@mui/material';
import { Link } from 'react-router-dom';
export default function Navbar({ logout, userData, mode, toggleMode }) {
function NavbarControls() {
return userData.email ? (
<>
<Button color='inherit' component={Link} to='/update'>
Account
</Button>
<Button
color='inherit'
component={Link}
to='/login'
onClick={logout}
sx={{ margin: '0 1vh' }}
>
Logout
</Button>
</>
) : (
<>
<Button color='inherit' component={Link} to='/login'>
Login
</Button>
<Button color='inherit' component={Link} to='/register'>
Register
</Button>
</>
);
}
return (
<AppBar position='static'>
<Toolbar>
<IconButton
color='inherit'
component={Link}
to='/'
sx={{ margin: '1vh 1vw 1vh 0' }}
>
<MenuIcon />
</IconButton>
<Typography variant='h6' component='div'>
KnowItAll
</Typography>
<Box sx={{ flexGrow: 1 }} />
<IconButton color='inherit' onClick={toggleMode} sx={{ margin: '0 1vh' }}>
{mode === 'light' ? <DarkModeIcon /> : <LightModeIcon />}
</IconButton>
<NavbarControls />
</Toolbar>
</AppBar>
);
}

View file

@ -0,0 +1,88 @@
import DarkModeIcon from "@mui/icons-material/DarkMode";
import LightModeIcon from "@mui/icons-material/LightMode";
import MenuIcon from "@mui/icons-material/Menu";
import {
AppBar,
Box,
Button,
IconButton,
Toolbar,
Typography,
} from "@mui/material";
import { Link } from "react-router-dom";
import { User, ThemeMode } from "../types";
interface NavbarControlsProps {
userData: User;
logout: () => void;
}
function NavbarControls({ userData, logout }: NavbarControlsProps) {
return userData.email ? (
<>
<Button color="inherit" component={Link} to="/update">
Account
</Button>
<Button
color="inherit"
component={Link}
to="/login"
onClick={logout}
sx={{ margin: "0 1vh" }}
>
Logout
</Button>
</>
) : (
<>
<Button color="inherit" component={Link} to="/login">
Login
</Button>
<Button color="inherit" component={Link} to="/register">
Register
</Button>
</>
);
}
interface NavbarProps {
userData: User;
logout: () => void;
mode: ThemeMode;
toggleMode: () => void;
}
export default function Navbar({
userData,
logout,
mode,
toggleMode,
}: NavbarProps) {
return (
<AppBar position="static">
<Toolbar>
<IconButton
color="inherit"
component={Link}
to="/"
sx={{ margin: "1vh 1vw 1vh 0" }}
>
<MenuIcon />
</IconButton>
<Typography variant="h6" component="div">
KnowItAll
</Typography>
<Box sx={{ flexGrow: 1 }} />
<IconButton
color="inherit"
onClick={toggleMode}
sx={{ margin: "0 1vh" }}
>
{mode === "light" ? <DarkModeIcon /> : <LightModeIcon />}
</IconButton>
<NavbarControls userData={userData} logout={logout} />
</Toolbar>
</AppBar>
);
}

View file

@ -1,27 +0,0 @@
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import { Accordion, AccordionDetails, AccordionSummary, Typography } from '@mui/material';
import { Link } from 'react-router-dom';
function OrgUnit({ org }) {
const { id, name, memberCount, createdAt } = org;
const linkStyle = { textDecoration: 'none', color: 'inherit' };
return (
<Accordion>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<Typography>
<Link to={`/org/${id}`} style={linkStyle}>
{name}
</Link>
</Typography>
</AccordionSummary>
<AccordionDetails>
<Typography variant='body1'>Members: {memberCount}</Typography>
<Typography variant='body1'>Created On: {createdAt}</Typography>
</AccordionDetails>
</Accordion>
);
}
export default OrgUnit;

View file

@ -0,0 +1,44 @@
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import {
Accordion,
AccordionDetails,
AccordionSummary,
Typography,
} from "@mui/material";
import { Link } from "react-router-dom";
import { Organization } from "../types";
interface OrgUnitProps {
org: Organization;
}
function OrgUnit({ org }: OrgUnitProps) {
const { id, name, memberCount, createdAt } = org;
const linkStyle = { textDecoration: "none", color: "inherit" };
return (
<Accordion>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<Typography>
<Link to={`/org/${id}`} style={linkStyle}>
{name}
</Link>
</Typography>
</AccordionSummary>
<AccordionDetails>
<Typography variant="body1">
Members:
{memberCount}
</Typography>
<Typography variant="body1">
Created On:
{createdAt}
</Typography>
</AccordionDetails>
</Accordion>
);
}
export default OrgUnit;

View file

@ -1,31 +0,0 @@
import { Box, Paper, Typography } from '@mui/material';
import { useTheme } from '@mui/material/styles';
import { OrgUnit } from '.';
function OrganizationList({ orgs }) {
const theme = useTheme();
return (
<Paper
sx={{
...theme.typography.body2,
padding: '2vh',
color: theme.palette.text.secondary,
flexGrow: 1,
height: '89vh',
margin: '2vh',
}}
>
<Typography variant='h6'>Organizations</Typography>
<hr />
<Box sx={{ overflowY: 'auto' }}>
{orgs.map((org, index) => (
<OrgUnit org={org} key={index} />
))}
</Box>
</Paper>
);
}
export default OrganizationList;

View file

@ -0,0 +1,35 @@
import { Box, Paper, Typography } from "@mui/material";
import { useTheme } from "@mui/material/styles";
import OrgUnit from "./OrgUnit";
import { Organization } from "../types";
interface OrganizationListProps {
orgs: Organization[];
}
export default function OrganizationList({ orgs }: OrganizationListProps) {
const theme = useTheme();
return (
<Paper
sx={{
...theme.typography.body2,
padding: "2vh",
color: theme.palette.text.secondary,
flexGrow: 1,
height: "89vh",
margin: "2vh",
}}
>
<Typography variant="h6">Organizations</Typography>
<hr />
<Box sx={{ overflowY: "auto" }}>
{orgs.map((org) => (
<OrgUnit org={org} key={org.id} />
))}
</Box>
</Paper>
);
}

View file

@ -1,35 +0,0 @@
import Mic from '@mui/icons-material/Mic';
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({ togglePlay, record, uploadRecord, handleSubmit }) {
return (
<form onSubmit={handleSubmit}>
<IconButton onClick={togglePlay}>
<PlayArrow />
</IconButton>
<IconButton onClick={record}>
<Mic />
</IconButton>
<input
id='icon-button-file'
type='file'
accept='audio/*'
hidden
onChange={uploadRecord}
/>
<label htmlFor='icon-button-file'>
<IconButton component='span'>
<Upload />
</IconButton>
</label>
<IconButton type='submit'>
<SaveAs />
</IconButton>
</form>
);
}
export default RecordMp3;

View file

@ -0,0 +1,47 @@
import Mic from "@mui/icons-material/Mic";
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";
interface RecordMp3Props {
togglePlay: () => void;
record: () => void;
uploadRecord: (event: React.ChangeEvent<HTMLInputElement>) => void;
handleSubmit: (event: React.FormEvent<HTMLFormElement>) => void;
}
function RecordMp3({
togglePlay,
record,
uploadRecord,
handleSubmit,
}: RecordMp3Props) {
return (
<form onSubmit={handleSubmit}>
<IconButton onClick={togglePlay}>
<PlayArrow />
</IconButton>
<IconButton onClick={record}>
<Mic />
</IconButton>
<input
id="icon-button-file"
type="file"
accept="audio/*"
hidden
onChange={uploadRecord}
/>
<label htmlFor="icon-button-file">
<IconButton component="span">
<Upload />
</IconButton>
</label>
<IconButton type="submit">
<SaveAs />
</IconButton>
</form>
);
}
export default RecordMp3;

View file

@ -1,23 +0,0 @@
import { Form } from '.';
import { FormSubmit, FormTextField } from './StyledElements';
function RecoverForm({ form, error, handleSubmit, handleChange }) {
return (
<Form text='Recover Password' error={error}>
<form onSubmit={handleSubmit}>
<FormTextField
required
label='Email'
name='email'
onChange={handleChange}
value={form.email}
/>
<FormSubmit color='primary' type='submit'>
Recover my password
</FormSubmit>
</form>
</Form>
);
}
export default RecoverForm;

View file

@ -0,0 +1,37 @@
import Form from "./Form";
import { FormSubmit, FormTextField } from "./StyledElements";
interface RecoverFormProps {
form: {
email: string;
};
error: string;
handleSubmit: (e: React.FormEvent<HTMLFormElement>) => void;
handleChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
}
function RecoverForm({
form,
error,
handleSubmit,
handleChange,
}: RecoverFormProps) {
return (
<Form text="Recover Password" error={error}>
<form onSubmit={handleSubmit}>
<FormTextField
required
label="Email"
name="email"
onChange={handleChange}
value={form.email}
/>
<FormSubmit color="primary" type="submit">
Recover my password
</FormSubmit>
</form>
</Form>
);
}
export default RecoverForm;

View file

@ -1,70 +0,0 @@
import { Typography } from '@mui/material';
import { Link } from 'react-router-dom';
import Form from './Form';
import { FormSubmit, FormTextField } from './StyledElements';
function RegisterForm({ form, handleSubmit, handleChange, error }) {
return (
<Form text='Create an Account' error={error}>
<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}
/>
<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' }}
/>
<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,90 @@
import { Typography } from "@mui/material";
import { Link } from "react-router-dom";
import Form from "./Form";
import { FormSubmit, FormTextField } from "./StyledElements";
interface RegisterFormProps {
form: {
firstName: string;
lastName: string;
email: string;
pronouns: string;
password: string;
confirmPassword: string;
};
handleSubmit: (e: React.FormEvent<HTMLFormElement>) => Promise<void>;
handleChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
error: string;
}
function RegisterForm({
form,
handleSubmit,
handleChange,
error,
}: RegisterFormProps) {
return (
<Form text="Create an Account" error={error}>
<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
label="Pronouns"
name="pronouns"
onChange={handleChange}
value={form.pronouns}
/>
<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" }}
/>
<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

@ -1,36 +0,0 @@
import { Form } from '.';
import { FormSubmit, FormTextField } from './StyledElements';
function ResetForm({ form, handleSubmit, handleChange, error }) {
return (
<Form text='Reset Password' error={error}>
<form onSubmit={handleSubmit}>
<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' }}
/>
<FormSubmit
color='primary'
type='submit'
other={{ disabled: form.password !== form.confirmPassword }}
>
Reset my Password
</FormSubmit>
</form>
</Form>
);
}
export default ResetForm;

View file

@ -0,0 +1,51 @@
import Form from "./Form";
import { FormSubmit, FormTextField } from "./StyledElements";
interface ResetFormProps {
form: {
password: string;
confirmPassword: string;
};
error: string;
handleChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
handleSubmit: (e: React.FormEvent<HTMLFormElement>) => void;
}
function ResetForm({
form,
handleSubmit,
handleChange,
error,
}: ResetFormProps) {
return (
<Form text="Reset Password" error={error}>
<form onSubmit={handleSubmit}>
<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" }}
/>
<FormSubmit
color="primary"
type="submit"
other={{ disabled: form.password !== form.confirmPassword }}
>
Reset my Password
</FormSubmit>
</form>
</Form>
);
}
export default ResetForm;

View file

@ -1,21 +0,0 @@
import { Paper, Typography } from '@mui/material';
import { useTheme } from '@mui/material/styles';
function StackItem({ text, children }) {
const theme = useTheme();
const paperStyle = {
...theme.typography.body2,
padding: '2vh',
color: theme.palette.text.secondary,
};
return (
<Paper sx={paperStyle}>
<Typography variant='h6'>{text}</Typography>
{children}
</Paper>
);
}
export default StackItem;

View file

@ -0,0 +1,26 @@
import { Paper, Typography } from "@mui/material";
import { useTheme } from "@mui/material/styles";
interface StackItemProps {
text?: string;
children: React.ReactNode;
}
function StackItem({ text, children }: StackItemProps) {
const theme = useTheme();
const paperStyle = {
...theme.typography.body2,
padding: "2vh",
color: theme.palette.text.secondary,
};
return (
<Paper sx={paperStyle}>
<Typography variant="h6">{text}</Typography>
{children}
</Paper>
);
}
export default StackItem;

View file

@ -1,80 +0,0 @@
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 { DynamicPaper, DynamicStack, Form, FormButton, FormField, FormSubmit, FormTextField };

View file

@ -0,0 +1,119 @@
import {
Button,
FormGroup,
Paper,
Stack,
TextField,
TextFieldProps,
ButtonProps,
} 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",
},
}));
interface FormTextFieldProps {
label: string;
name: string;
value: string;
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
required?: boolean;
other?: TextFieldProps;
}
function FormTextField({
label,
name,
value,
onChange,
required,
other,
}: FormTextFieldProps) {
return (
<TextField
required={required}
fullWidth
label={label}
name={name}
variant="outlined"
onChange={onChange}
value={value}
sx={{ margin: "1vh 0vh" }}
{...other}
/>
);
}
interface FormSubmitProps {
color: "primary" | "secondary" | "error";
children: React.ReactNode;
type: "button" | "submit";
other?: ButtonProps;
}
function FormSubmit({ color, children, type, other }: FormSubmitProps) {
return (
<Button
variant="contained"
color={color}
sx={{ margin: "1vh 0vh" }}
type={type}
fullWidth
{...other}
>
{children}
</Button>
);
}
export {
DynamicPaper,
DynamicStack,
Form,
FormButton,
FormField,
FormSubmit,
FormTextField,
};

View file

@ -1,23 +0,0 @@
import { Form } from '.';
import { FormSubmit, FormTextField } from './StyledElements';
function UpdateOrganizationForm({ name, handleSubmit, handleChange }) {
return (
<Form text='Rename Organization'>
<form onSubmit={handleSubmit}>
<FormTextField
required
label='Name'
name='name'
onChange={handleChange}
value={name}
/>
<FormSubmit color='primary' type='submit'>
Rename
</FormSubmit>
</form>
</Form>
);
}
export default UpdateOrganizationForm;

View file

@ -0,0 +1,31 @@
import Form from "./Form";
import { FormSubmit, FormTextField } from "./StyledElements";
interface UpdateOrganizationFormProps {
name: string;
handleChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
handleSubmit: (e: React.FormEvent<HTMLFormElement>) => void;
}
export default function UpdateOrganizationForm({
name,
handleSubmit,
handleChange,
}: UpdateOrganizationFormProps) {
return (
<Form text="Rename Organization">
<form onSubmit={handleSubmit}>
<FormTextField
required
label="Name"
name="name"
onChange={handleChange}
value={name}
/>
<FormSubmit color="primary" type="submit">
Rename
</FormSubmit>
</form>
</Form>
);
}

View file

@ -1,23 +0,0 @@
import Dashboard from './Dashboard';
import EditUserForm from './EditUserForm';
import Form from './Form';
import LoginForm from './LoginForm';
import MainPageBase from './MainPageBase';
import MemberUnit from './MemberUnit';
import MobileNavbar from './MobileNavbar';
import Navbar from './Navbar';
import OrgUnit from './OrgUnit';
import OrganizationList from './OrganizationList';
import RecordMp3 from './RecordMp3';
import RecoverForm from './RecoverForm';
import ResetForm from './ResetForm';
import StackItem from './StackItem';
import UpdateOrganizationForm from './UpdateOrganizationForm';
export {
Dashboard, EditUserForm,
Form, LoginForm,
MainPageBase, MemberUnit, MobileNavbar, Navbar, OrgUnit, OrganizationList, RecordMp3,
RecoverForm,
ResetForm, StackItem, UpdateOrganizationForm
};

View file

@ -0,0 +1,27 @@
import Navbar from "./Navbar";
import MobileNavbar from "./MobileNavbar";
import MainPage from "./MainPage";
import LoginForm from "./LoginForm";
import RegisterForm from "./RegisterForm";
import EditUserForm from "./EditUserForm";
import RecordMp3 from "./RecordMp3";
import RecoverForm from "./RecoverForm";
import ResetForm from "./ResetForm";
import OrganizationList from "./OrganizationList";
import Dashboard from "./Dashboard";
import UpdateOrganizationForm from "./UpdateOrganizationForm";
export {
Navbar,
MobileNavbar,
MainPage,
LoginForm,
RegisterForm,
EditUserForm,
RecordMp3,
RecoverForm,
ResetForm,
OrganizationList,
Dashboard,
UpdateOrganizationForm,
};

View file

@ -1,6 +1,6 @@
import Helmet from 'react-helmet';
import Helmet from "react-helmet";
import { EditUserForm } from '../controllers';
import { EditUserForm } from "../controllers";
function Login() {
return (

View file

@ -1,10 +1,20 @@
import { Box, CssBaseline } from '@mui/material';
import { ThemeProvider, createTheme } from '@mui/material/styles';
import { Helmet } from 'react-helmet';
import { Outlet } from 'react-router-dom';
import { Box, CssBaseline } from "@mui/material";
import { ThemeProvider, createTheme } from "@mui/material/styles";
import { Helmet } from "react-helmet";
import { Outlet } from "react-router-dom";
import { useMode } from '../contexts';
import { Navbar } from '../controllers';
import { useMode } from "../contexts";
import { Navbar } from "../controllers";
declare module "@mui/material/styles" {
interface BreakpointOverrides {
xs: true;
sm: true;
md: true;
lg: false;
xl: false;
}
}
function Layout() {
const { mode } = useMode();
@ -28,7 +38,7 @@ function Layout() {
</Helmet>
<main>
<Navbar />
<Box sx={{ height: { xs: '', sm: '92vh', md: '92vh' } }}>
<Box sx={{ height: { xs: "", sm: "92vh", md: "92vh" } }}>
<Outlet />
</Box>
</main>

View file

@ -1,6 +1,6 @@
import Helmet from 'react-helmet';
import Helmet from "react-helmet";
import { LoginForm } from '../controllers';
import { LoginForm } from "../controllers";
function Login() {
return (

View file

@ -1,16 +0,0 @@
import Helmet from 'react-helmet';
import { MainPageDisplay } from '../controllers';
function MainPage() {
return (
<>
<Helmet>
<title>KnowItAll</title>
</Helmet>
<MainPageDisplay />
</>
);
}
export default MainPage;

View file

@ -0,0 +1,14 @@
import Helmet from "react-helmet";
import { MainPage as MainPageController } from "../controllers";
export default function MainPage() {
return (
<>
<Helmet>
<title>KnowItAll</title>
</Helmet>
<MainPageController />
</>
);
}

View file

@ -1,8 +1,8 @@
import Helmet from 'react-helmet';
import Helmet from "react-helmet";
import { MemberOrgsDashboard } from '../controllers';
import { MemberOrgsDashboard } from "../controllers";
function MemberOrgs() {
export default function MemberOrgs() {
return (
<>
<Helmet>
@ -12,5 +12,3 @@ function MemberOrgs() {
</>
);
}
export default MemberOrgs;

View file

@ -1,8 +1,8 @@
import Helmet from 'react-helmet';
import Helmet from "react-helmet";
import { Dashboard } from '../controllers';
import { Dashboard } from "../controllers";
function OrgDashboard() {
export default function OrgDashboard() {
return (
<>
<Helmet>
@ -12,5 +12,3 @@ function OrgDashboard() {
</>
);
}
export default OrgDashboard;

View file

@ -1,6 +1,6 @@
import Helmet from 'react-helmet';
import Helmet from "react-helmet";
import { OwnedOrgsDashboard } from '../controllers';
import { OwnedOrgsDashboard } from "../controllers";
function OwnedOrgs() {
return (

View file

@ -1,6 +1,6 @@
import Helmet from 'react-helmet';
import Helmet from "react-helmet";
import { RecoverForm } from '../controllers';
import { RecoverForm } from "../controllers";
function Login() {
return (

View file

@ -1,6 +1,6 @@
import Helmet from 'react-helmet';
import Helmet from "react-helmet";
import { RegisterForm } from '../controllers';
import { RegisterForm } from "../controllers";
function Register() {
return (

View file

@ -1,8 +1,8 @@
import Helmet from 'react-helmet';
import Helmet from "react-helmet";
import { ResetForm } from '../controllers';
import { ResetForm } from "../controllers";
function Reset() {
export default function Reset() {
return (
<>
<Helmet>
@ -12,5 +12,3 @@ function Reset() {
</>
);
}
export default Reset;

View file

@ -1,8 +1,8 @@
import Helmet from 'react-helmet';
import Helmet from "react-helmet";
import { UpdateOrganizationForm } from '../controllers';
import { UpdateOrganizationForm } from "../controllers";
function UpdateOrganization() {
export default function UpdateOrganization() {
return (
<>
<Helmet>
@ -12,5 +12,3 @@ function UpdateOrganization() {
</>
);
}
export default UpdateOrganization;

View file

@ -1,17 +0,0 @@
import EditUser from './EditUser';
import Layout from './Layout';
import Login from './Login';
import MainPage from './MainPage';
import MemberOrgs from './MemberOrgs';
import OrgDashboard from './OrgDashboard';
import OwnedOrgs from './OwnedOrgs';
import Recover from './Recover';
import Register from './Register';
import Reset from './Reset';
import UpdateOrganization from './UpdateOrganization';
export {
EditUser, Layout,
Login, MainPage, MemberOrgs,
OrgDashboard, OwnedOrgs, Recover, Register, Reset, UpdateOrganization
};

View file

@ -0,0 +1,25 @@
import Layout from "./Layout";
import MainPage from "./MainPage";
import Login from "./Login";
import Register from "./Register";
import EditUser from "./EditUser";
import Recover from "./Recover";
import Reset from "./Reset";
import OwnedOrgs from "./OwnedOrgs";
import MemberOrgs from "./MemberOrgs";
import OrgDashboard from "./OrgDashboard";
import UpdateOrganization from "./UpdateOrganization";
export {
Layout,
MainPage,
Login,
Register,
EditUser,
Recover,
Reset,
OwnedOrgs,
MemberOrgs,
OrgDashboard,
UpdateOrganization,
};

View file

@ -1,11 +0,0 @@
import { ErrorProvider, ModeProvider, UserProvider } from '.';
export default function ContextProvider({ children }) {
return (
<ModeProvider>
<ErrorProvider>
<UserProvider>{children}</UserProvider>
</ErrorProvider>
</ModeProvider>
);
}

View file

@ -0,0 +1,19 @@
import { ReactNode } from "react";
import ErrorProvider from "./ErrorProvider";
import ModeProvider from "./ModeProvider";
import UserProvider from "./UserProvider";
interface ContextProviderProps {
children: ReactNode;
}
export default function ContextProvider({ children }: ContextProviderProps) {
return (
<ModeProvider>
<ErrorProvider>
<UserProvider>{children}</UserProvider>
</ErrorProvider>
</ModeProvider>
);
}

View file

@ -1,11 +0,0 @@
import { createContext, useContext, useState } from 'react';
const ErrorContext = createContext();
export default function ModeProvider({ children }) {
const [error, setError] = useState('');
return <ErrorContext.Provider value={{ error, setError }}>{children}</ErrorContext.Provider>;
}
export const useError = () => useContext(ErrorContext);

View file

@ -0,0 +1,20 @@
import React, { createContext, useContext } from "react";
interface ErrorContextData {
error: string;
setError: React.Dispatch<React.SetStateAction<string>>;
}
const ErrorContext = createContext<ErrorContextData | undefined>(undefined);
export default ErrorContext;
export const useError = () => {
const context = useContext(ErrorContext);
if (!context) {
throw new Error("useError must be used within a ErrorProvider");
}
return context;
};

View file

@ -0,0 +1,22 @@
import { useMemo, useState } from "react";
import ErrorContext from "./ErrorContext";
interface ErrorProviderProps {
children: React.ReactNode;
}
export default function ErrorProvider({ children }: ErrorProviderProps) {
const [error, setError] = useState("");
const contextValue = useMemo(
() => ({ error, setError }),
[error, setError]
);
return (
<ErrorContext.Provider value={contextValue}>
{children}
</ErrorContext.Provider>
);
}

View file

@ -1,16 +0,0 @@
import Cookies from 'js-cookie';
import { createContext, useContext, useEffect, useState } from 'react';
const ModeContext = createContext();
export default function ModeProvider({ children }) {
const [mode, setMode] = useState(Cookies.get('theme') || 'light');
useEffect(() => {
Cookies.set('theme', mode);
}, [mode]);
return <ModeContext.Provider value={{ mode, setMode }}>{children}</ModeContext.Provider>;
}
export const useMode = () => useContext(ModeContext);

View file

@ -0,0 +1,22 @@
import React, { createContext, useContext } from "react";
import { ThemeMode } from "../types";
interface ModeContextData {
mode: ThemeMode;
setMode: React.Dispatch<React.SetStateAction<ThemeMode>>;
}
const ModeContext = createContext<ModeContextData | undefined>(undefined);
export default ModeContext;
export const useMode = () => {
const context = useContext(ModeContext);
if (!context) {
throw new Error("useMode must be used within a ModeProvider");
}
return context;
};

View file

@ -0,0 +1,27 @@
import Cookies from "js-cookie";
import { useEffect, useMemo, useState } from "react";
import { ThemeMode } from "../types";
import ModeContext from "./ModeContext";
interface ModeProviderProps {
children: React.ReactNode;
}
export default function ModeProvider({ children }: ModeProviderProps) {
const [mode, setMode] = useState<ThemeMode>(
(Cookies.get("theme") as ThemeMode) || "light"
);
const contextValue = useMemo(() => ({ mode, setMode }), [mode, setMode]);
useEffect(() => {
Cookies.set("theme", mode);
}, [mode]);
return (
<ModeContext.Provider value={contextValue}>
{children}
</ModeContext.Provider>
);
}

View file

@ -1,50 +0,0 @@
import Cookies from 'js-cookie';
import { createContext, useCallback, useContext, useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { createRequest } from '../utils/requests';
const UserContext = createContext();
export default function UserProvider({ children }) {
const [userData, setUserData] = useState({});
const [loading, setLoading] = useState(true);
const navigate = useNavigate();
const loadUserData = useCallback(async () => {
if (!loading) return;
if (!Cookies.get('token')) {
setLoading(() => false);
return;
}
try {
const instance = createRequest();
const response = await instance.post('/auth/remember');
setUserData(() => response.data.user);
} catch (error) {
Cookies.remove('token');
navigate('/login');
} finally {
setLoading(() => false);
}
}, [navigate, loading]);
useEffect(loadUserData, [loadUserData]);
return (
<UserContext.Provider
value={{
loading,
userData,
setUserData,
}}
>
{children}
</UserContext.Provider>
);
}
export const useUser = () => useContext(UserContext);

View file

@ -0,0 +1,23 @@
import React, { createContext, useContext } from "react";
import { User } from "../types";
interface UserContextData {
loading: boolean;
userData: User;
setUserData: React.Dispatch<React.SetStateAction<User>>;
}
const UserContext = createContext<UserContextData | undefined>(undefined);
export default UserContext;
export const useUser = () => {
const context = useContext(UserContext);
if (!context) {
throw new Error("useUser must be used within a UserProvider");
}
return context;
};

View file

@ -0,0 +1,63 @@
import Cookies from "js-cookie";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useNavigate } from "react-router-dom";
import UserContext from "./UserContext";
import createRequest from "../utils/requests";
import { User } from "../types";
interface UserProviderProps {
children: React.ReactNode;
}
export default function UserProvider({ children }: UserProviderProps) {
const [userData, setUserData] = useState<User>({
id: -1,
firstName: "",
lastName: "",
pronouns: "",
nickname: "",
username: "",
email: "",
age: 0,
});
const [loading, setLoading] = useState(true);
const navigate = useNavigate();
const contextValue = useMemo(
() => ({ loading, userData, setUserData }),
[loading, userData, setUserData]
);
const loadUserData = useCallback(async () => {
if (!loading) return;
if (!Cookies.get("token")) {
setLoading(() => false);
}
try {
const instance = createRequest();
const response = await instance.post("/auth/remember");
setUserData(() => response.data.user);
} catch (error) {
Cookies.remove("token");
navigate("/login");
} finally {
setLoading(() => false);
}
}, [navigate, loading]);
useEffect(() => {
loadUserData();
}, [loadUserData]);
return (
<UserContext.Provider value={contextValue}>
{children}
</UserContext.Provider>
);
}

View file

@ -1,6 +0,0 @@
import ModeProvider, { useMode } from './ModeContext';
import UserProvider, { useUser } from './UserContext';
import ErrorProvider, { useError } from './ErrorContext';
import ContextProvider from './ContextProvider';
export { ModeProvider, useMode, UserProvider, useUser, ErrorProvider, useError, ContextProvider };

View file

@ -0,0 +1,8 @@
import { useMode } from "./ModeContext";
import { useUser } from "./UserContext";
import { useError } from "./ErrorContext";
import ContextProvider from "./ContextProvider";
export {
useMode, useUser, useError, ContextProvider,
};

View file

@ -0,0 +1,113 @@
import axios from "axios";
import { useCallback, useEffect, useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { Dashboard as DashboardComponent } from "../components";
import createRequest from "../utils/requests";
export default function Dashboard() {
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 getOrg = useCallback(async () => {
try {
const instance = createRequest();
const response = await instance.get(`/org/${orgID}`);
const orgData = {
...response.data.org,
memberCount: response.data.memberCount,
};
setOrg(() => orgData);
setRows(() => response.data.org.member);
setStatus(() => response.data.owner);
} catch (error) {
navigate("/");
}
}, [navigate, orgID]);
useEffect(() => {
getOrg();
}, [getOrg]);
const handleOpen = useCallback(() => {
setOpen((initial) => !initial);
}, []);
const handleDelete = useCallback(async () => {
const instance = createRequest();
await instance.delete(`/org/${orgID}`);
navigate("/");
}, [navigate, orgID]);
const copyID = useCallback(() => {
navigator.clipboard.writeText(orgID);
}, [orgID]);
const copyJoinLink = useCallback(() => {
navigator.clipboard.writeText(
`${process.env.REACT_APP_DOMAIN_ROOT}/register?orgID=${orgID}`
);
}, [orgID]);
const removeSelected = useCallback(async () => {
const doomedUserIDs = selection.map((row) => row.id);
const instance = createRequest();
await instance.post(`/org/${orgID}/remove`, { doomedUserIDs });
}, [orgID, selection]);
const leaveOrg = useCallback(async () => {
const instance = createRequest();
await instance.post(`/org/${orgID}/remove`, {});
navigate("/");
}, [navigate, orgID]);
const play = useCallback(async (id) => {
const request = await axios.get(
`${process.env.REACT_APP_DOMAIN_ROOT}/public/audio/${id}.mp3`,
{ responseType: "blob" }
);
const uploadedFile = new File([request.data], "userAudio.mp3", {
type: request.data.type,
lastModified: Date.now(),
});
const audioFile = new Audio(URL.createObjectURL(uploadedFile));
audioFile.play();
}, []);
return (
<DashboardComponent
org={org}
rows={rows}
open={open}
handleOpen={handleOpen}
setSelection={setSelection}
onClick={play}
status={status}
handleDelete={handleDelete}
copyID={copyID}
copyJoinLink={copyJoinLink}
removeSelected={removeSelected}
leaveOrg={leaveOrg}
/>
);
}

View file

@ -1,118 +0,0 @@
import axios from 'axios';
import { useCallback, useEffect, useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { Dashboard } from '../components';
import { createRequest } from '../utils/requests';
function DashboardController() {
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 getOrg = useCallback(async () => {
try {
const instance = createRequest();
const response = await instance.get(`/org/${orgID}`);
const orgData = { ...response.data.org, memberCount: response.data.memberCount };
setOrg(() => orgData);
setRows(() => response.data.org.member);
setStatus(() => response.data.owner);
} catch (error) {
navigate('/');
}
}, [navigate, orgID]);
useEffect(getOrg, [getOrg]);
function handleOpen() {
setOpen((initial) => (initial ? false : true));
}
async function handleDelete() {
try {
const instance = createRequest();
await instance.delete(`/org/${orgID}`);
navigate('/');
} catch (error) {}
}
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);
try {
const instance = createRequest();
await instance.post(`/org/${orgID}/remove`, { doomedUserIDs });
} catch (error) {}
}
async function leaveOrg() {
try {
const instance = createRequest();
await instance.post(`/org/${orgID}/remove`, {});
navigate('/');
} catch (error) {}
}
async function play(id) {
try {
const request = await axios.get(
`${process.env.REACT_APP_DOMAIN_ROOT}/public/audio/${id}.mp3`,
{ responseType: 'blob' }
);
const uploadedFile = new File([request.data], 'userAudio.mp3', {
type: request.data.type,
lastModified: Date.now(),
});
const audioFile = new Audio(URL.createObjectURL(uploadedFile));
audioFile.play();
} catch (error) {}
}
return (
<Dashboard
org={org}
rows={rows}
open={open}
handleOpen={handleOpen}
setSelection={setSelection}
onClick={play}
status={status}
handleDelete={handleDelete}
copyID={copyID}
copyJoinLink={copyJoinLink}
removeSelected={removeSelected}
leaveOrg={leaveOrg}
/>
);
}
export default DashboardController;

View file

@ -0,0 +1,67 @@
import { useCallback, useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import { AxiosError } from "axios";
import { EditUserForm } from "../components";
import { useUser, useError } from "../contexts";
import createRequest from "../utils/requests";
function EditUserFormController() {
const navigate = useNavigate();
const { userData } = useUser();
const [form, setForm] = useState({
firstName: "",
lastName: "",
email: "",
pronouns: "",
});
const { error } = useError();
const handleChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
const { name } = e.target;
const { value } = e.target;
setForm((form) => ({ ...form, [name]: value }));
},
[]
);
const handleSubmit = useCallback(
async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
try {
const instance = createRequest();
await instance.post("/auth/update", form);
navigate("/");
} catch (e) {
const error = e as AxiosError;
if (error.response?.status === 401) navigate("/login");
}
},
[form, navigate]
);
useEffect(() => {
setForm(() => ({
firstName: userData.firstName,
lastName: userData.lastName,
email: userData.email,
pronouns: userData.pronouns,
}));
}, [userData]);
return (
<EditUserForm
form={form}
error={error}
handleSubmit={handleSubmit}
handleChange={handleChange}
/>
);
}
export default EditUserFormController;

View file

@ -1,59 +0,0 @@
import { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { EditUserForm } from '../components';
import { useUser } from '../contexts';
import { createRequest } from '../utils/requests';
function EditUserFormController() {
const navigate = useNavigate();
const { userData } = useUser();
const [form, setForm] = useState({
firstName: '',
lastName: '',
email: '',
pronouns: '',
});
const [error, setError] = useState('');
function handleChange(e) {
const name = e.target.name;
const value = e.target.value;
setForm((form) => ({ ...form, [name]: value }));
}
async function handleSubmit(e) {
e.preventDefault();
try {
const instance = createRequest();
await instance.post('/auth/update', form);
navigate('/');
} catch (error) {
setError(() => error.response.data);
}
}
useEffect(() => {
setForm(() => ({
firstName: userData.firstName,
lastName: userData.lastName,
email: userData.email,
pronouns: userData.pronouns,
}));
}, [userData]);
return (
<EditUserForm
form={form}
error={error}
handleSubmit={handleSubmit}
handleChange={handleChange}
/>
);
}
export default EditUserFormController;

View file

@ -0,0 +1,89 @@
import { Buffer } from "buffer";
import Cookies from "js-cookie";
import React, { useCallback, useState } from "react";
import { useNavigate } from "react-router-dom";
import { LoginForm } from "../components";
import { useError, useUser } from "../contexts";
import createRequest from "../utils/requests";
import { APIError } from "../types";
function LoginFormController() {
const navigate = useNavigate();
const { error, setError } = useError();
const { setUserData } = useUser();
const [form, setForm] = useState({
email: "",
password: "",
remember: false,
});
const handleChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
const { name } = e.target;
const { value } = e.target;
setForm((prevForm) => ({ ...prevForm, [name]: value }));
},
[]
);
const handleCheckboxChange = useCallback(
(e: React.SyntheticEvent<Element, Event>) => {
const target = e.target as HTMLInputElement;
const { name } = target;
const { checked } = target;
setForm((prevForm) => ({ ...prevForm, [name]: checked }));
},
[]
);
const handleSubmit = useCallback(
async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const encoded = Buffer.from(
`${form.email}:${form.password}`
).toString("base64");
const instance = createRequest();
instance.defaults.headers.common.Authorization = `basic ${encoded}`;
try {
const response = await instance.post("/auth/login", {
remember: form.remember,
});
const expires = form.remember ? 365 : 1;
setUserData(() => response.data.user);
Cookies.set("token", response.data.token, { expires });
navigate("/");
} catch (e) {
const error = e as APIError;
if (error.response?.data?.error) {
setError(error.response.data.error);
} else {
setError("An error occurred");
}
}
},
[form, navigate, setUserData, setError]
);
return (
<LoginForm
form={form}
handleSubmit={handleSubmit}
handleChange={handleChange}
handleCheckboxChange={handleCheckboxChange}
error={error}
/>
);
}
export default LoginFormController;

View file

@ -1,63 +0,0 @@
import { Buffer } from 'buffer';
import Cookies from 'js-cookie';
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { LoginForm } from '../components';
import { useError, useUser } from '../contexts';
import { createRequest } from '../utils/requests';
function LoginFormController({ setToken }) {
const navigate = useNavigate();
const { error, setError } = useError();
const { setUserData } = useUser();
const [form, setForm] = useState({
email: '',
password: '',
remember: false,
});
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();
const encoded = Buffer.from(`${form.email}:${form.password}`).toString('base64');
const instance = createRequest();
instance.defaults.headers.common['Authorization'] = `basic ${encoded}`;
try {
const response = await instance.post('/auth/login', { remember: form.remember });
const expires = form.remember ? 365 : 1;
setUserData(() => response.data.user);
Cookies.set('token', response.data.token, { expires });
navigate('/');
} catch (error) {
setError(() => error.response.data);
}
}
return <LoginForm {...{ form, handleSubmit, handleChange, handleCheckboxChange, error }} />;
}
export default LoginFormController;

View file

@ -0,0 +1,70 @@
import React, { useCallback, useEffect, useState } from "react";
import { MainPage as MainPageComponent } from "../components";
import createRequest from "../utils/requests";
import { Organization } from "../types";
export default function MainPage() {
const [ownedOrgs, setOwnedOrgs] = useState<Organization[]>([]);
const [orgs, setOrgs] = useState<Organization[]>([]);
const [form, setForm] = useState({
name: "",
id: "",
});
const getOwnedOrgs = useCallback(async () => {
const instance = createRequest();
const response = await instance.get("/auth/orgs");
setOwnedOrgs(() => response.data.orgs);
}, []);
const getMemberOrgs = useCallback(async () => {
const instance = createRequest();
const response = await instance.get("/auth/orgs/member");
setOrgs(() => response.data.orgs);
}, []);
useEffect(() => {
getOwnedOrgs();
getMemberOrgs();
}, [getOwnedOrgs, getMemberOrgs]);
const handleChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
const { name } = e.target;
const { value } = e.target;
setForm((form) => ({ ...form, [name]: value }));
},
[]
);
const makeOrg = useCallback(
async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const instance = createRequest();
instance.post("/org/create", form).then(getOwnedOrgs);
},
[form, getOwnedOrgs]
);
const joinOrg = useCallback(
async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const instance = createRequest();
instance.post(`/org/${form.id}/add`, {}).then(getMemberOrgs);
},
[form, getMemberOrgs]
);
return (
<MainPageComponent
form={form}
orgs={orgs}
ownedOrgs={ownedOrgs}
handleChange={handleChange}
joinOrg={joinOrg}
createOrg={makeOrg}
/>
);
}

View file

@ -1,68 +0,0 @@
import { useCallback, useEffect, useState } from 'react';
import { MainPageBase } from '../components';
import { createRequest } from '../utils/requests';
function MainPageController() {
const [ownedOrgs, setOwnedOrgs] = useState([]);
const [orgs, setOrgs] = useState([]);
const [form, setForm] = useState({
name: '',
id: '',
});
const getOwnedOrgs = useCallback(async () => {
try {
const instance = createRequest();
const response = await instance.get('/auth/orgs');
setOwnedOrgs(() => response.data.orgs);
} catch (error) {}
}, []);
const getMemberOrgs = useCallback(async () => {
try {
const instance = createRequest();
const response = await instance.get('/auth/orgs/member');
setOrgs(() => response.data.orgs);
} catch (error) {}
}, []);
useEffect(() => {
getOwnedOrgs();
getMemberOrgs();
}, [getOwnedOrgs, getMemberOrgs]);
function handleChange(e) {
const name = e.target.name;
const value = e.target.value;
setForm((form) => {
return { ...form, [name]: value };
});
}
async function makeOrg(e) {
e.preventDefault();
const instance = createRequest();
instance.post('/org/create', form).then(getOwnedOrgs);
}
async function joinOrg(e) {
e.preventDefault();
const instance = createRequest();
instance.post(`/org/${form.id}/add`, {}).then(getMemberOrgs);
}
return (
<MainPageBase
form={form}
orgs={orgs}
ownedOrgs={ownedOrgs}
handleChange={handleChange}
joinOrg={joinOrg}
createOrg={makeOrg}
/>
);
}
export default MainPageController;

View file

@ -1,22 +0,0 @@
import { useCallback, useEffect, useState } from 'react';
import { OrganizationList } from '../components';
import { createRequest } from '../utils/requests';
function MemberOrgsDashboard() {
const [orgs, setOrgs] = useState([]);
const getOrgs = useCallback(async () => {
try {
const instance = createRequest();
const response = await instance.get('/auth/orgs');
setOrgs(() => response.data.orgs);
} catch (error) {}
}, []);
useEffect(getOrgs, [getOrgs]);
return <OrganizationList orgs={orgs} />;
}
export default MemberOrgsDashboard;

View file

@ -0,0 +1,21 @@
import { useCallback, useEffect, useState } from "react";
import { OrganizationList } from "../components";
import createRequest from "../utils/requests";
import { Organization } from "../types";
export default function MemberOrgsDashboard() {
const [orgs, setOrgs] = useState<Organization[]>([]);
const getOrgs = useCallback(async () => {
const instance = createRequest();
const response = await instance.get("/auth/orgs");
setOrgs(() => response.data.orgs);
}, []);
useEffect(() => {
getOrgs();
}, [getOrgs]);
return <OrganizationList orgs={orgs} />;
}

View file

@ -0,0 +1,61 @@
import { useMediaQuery, Theme } from "@mui/material";
import Cookies from "js-cookie";
import { useState } from "react";
import { useNavigate } from "react-router-dom";
import { MobileNavbar, Navbar } from "../components";
import { useMode, useUser } from "../contexts";
import createRequest from "../utils/requests";
export default function NavbarController() {
const navigate = useNavigate();
const { userData, setUserData } = useUser();
const { mode, setMode } = useMode();
const [open, setOpen] = useState(false);
const toggleOpen = () => setOpen((initial) => !initial);
const toggleMode = () => {
setMode(() => (mode === "light" ? "dark" : "light"));
};
const sm = useMediaQuery((theme: Theme) => theme.breakpoints.only("sm"));
const md = useMediaQuery((theme: Theme) => theme.breakpoints.only("md"));
const logoutUser = async () => {
try {
const instance = createRequest();
await instance.post("/auth/logout");
} finally {
Cookies.remove("token");
setUserData({
email: "",
firstName: "",
username: "",
age: 0,
lastName: "",
pronouns: "",
nickname: "",
id: 0,
});
navigate("/login");
}
};
return md || sm
? Navbar({
logout: logoutUser,
userData,
mode,
toggleMode,
})
: MobileNavbar({
logout: logoutUser,
userData,
open,
toggleOpen,
mode,
toggleMode,
});
}

View file

@ -1,48 +0,0 @@
import { useMediaQuery } from '@mui/material';
import Cookies from 'js-cookie';
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { MobileNavbar, Navbar } from '../components';
import { useMode, useUser } from '../contexts';
import { createRequest } from '../utils/requests';
export default function NavbarController() {
const navigate = useNavigate();
const { userData, setUserData } = useUser();
const { mode, setMode } = useMode();
const [open, setOpen] = useState(false);
const toggleOpen = () => setOpen((initial) => !initial);
const toggleMode = () => {
setMode(() => (mode === 'light' ? 'dark' : 'light'));
};
const sm = useMediaQuery((theme) => theme.breakpoints.only('sm'));
const md = useMediaQuery((theme) => theme.breakpoints.only('md'));
const logoutUser = async () => {
try {
const instance = createRequest();
await instance.post('/auth/logout');
} catch (error) {
} finally {
Cookies.remove('token');
setUserData({});
navigate('/login');
}
};
return md || sm
? Navbar({ logout: logoutUser, userData, mode, toggleMode })
: MobileNavbar({
logout: logoutUser,
userData,
open,
setOpen: toggleOpen,
mode,
toggleMode,
});
}

View file

@ -1,22 +0,0 @@
import { useCallback, useEffect, useState } from 'react';
import { OrganizationList } from '../components';
import { createRequest } from '../utils/requests';
function OwnedOrgsDashboard() {
const [orgs, setOrgs] = useState([]);
const getOrgs = useCallback(async () => {
try {
const instance = createRequest();
const response = await instance.get('/auth/orgs/member');
setOrgs(() => response.data.orgs);
} catch (error) {}
}, []);
useEffect(getOrgs, [getOrgs]);
return <OrganizationList orgs={orgs} />;
}
export default OwnedOrgsDashboard;

View file

@ -0,0 +1,21 @@
import { useCallback, useEffect, useState } from "react";
import { OrganizationList } from "../components";
import { Organization } from "../types";
import createRequest from "../utils/requests";
export default function OwnedOrgsDashboard() {
const [orgs, setOrgs] = useState<Organization[]>([]);
const getOrgs = useCallback(async () => {
const instance = createRequest();
const response = await instance.get("/auth/orgs/member");
setOrgs(() => response.data.orgs);
}, []);
useEffect(() => {
getOrgs();
}, [getOrgs]);
return <OrganizationList orgs={orgs} />;
}

View file

@ -0,0 +1,103 @@
import axios from "axios";
import MicRecorder from "mic-recorder-to-mp3";
import { useCallback, useEffect, useState } from "react";
import { RecordMp3 as RecordMp3Component } from "../components";
import { useUser } from "../contexts";
import createRequest from "../utils/requests";
export default function RecordMp3() {
const { userData } = useUser();
const [recorder] = useState(
new MicRecorder({
bitRate: 128,
})
);
const [recording, setRecording] = useState(false);
const [uploadedFile, setUploadedFile] = useState();
const [audioRecording, setAudioRecording] = useState(null);
const getData = useCallback(async () => {
const response = await axios.get(
`${process.env.REACT_APP_DOMAIN_ROOT}/public/audio/${userData.id}.mp3`,
{ responseType: "blob" }
);
setUploadedFile(
() =>
new File([response.data], "userAudio.mp3", {
type: response.data.type,
lastModified: Date.now(),
})
);
}, [userData.id]);
useEffect(() => {
getData();
}, [getData]);
const togglePlay = useCallback(() => {
if (audioRecording && !audioRecording.ended) {
audioRecording.pause();
audioRecording.currentTime = 0;
setAudioRecording(() => null);
} else {
const audioFile = new Audio(URL.createObjectURL(uploadedFile));
setAudioRecording(() => audioFile);
audioFile.play();
}
}, [audioRecording, uploadedFile]);
const record = useCallback(() => {
if (recording) {
setRecording(() => false);
recorder
.stop()
.getMp3()
.then(([buffer, blob]) => {
setUploadedFile(
() =>
new File(buffer, "userAudio.mp3", {
type: blob.type,
lastModified: Date.now(),
})
);
});
} else {
setRecording(() => true);
recorder.start();
}
}, [recorder, recording]);
const uploadRecord = useCallback(() => {
setUploadedFile(
() => document.getElementById("icon-button-file").files[0]
);
}, []);
const handleSubmit = useCallback(
async (e) => {
e.preventDefault();
const formData = new FormData();
formData.append("audioFile", uploadedFile, "userAudio.mp3");
const instance = createRequest();
instance.defaults.headers.common["Content-Type"] =
"multipart/form-data";
return instance.post("/auth/audio", formData);
},
[uploadedFile]
);
return (
<RecordMp3Component
togglePlay={togglePlay}
record={record}
uploadRecord={uploadRecord}
handleSubmit={handleSubmit}
/>
);
}

View file

@ -1,91 +0,0 @@
import axios from 'axios';
import MicRecorder from 'mic-recorder-to-mp3';
import { useCallback, useEffect, useState } from 'react';
import { RecordMp3 } from '../components';
import { useUser } from '../contexts';
import { createRequest } from '../utils/requests';
function RecordMp3Controller() {
const { userData } = useUser();
const [recorder] = useState(
new MicRecorder({
bitRate: 128,
})
);
const [recording, setRecording] = useState(false);
const [uploadedFile, setUploadedFile] = useState(null);
const [audioRecording, setAudioRecording] = useState(null);
const getData = useCallback(async () => {
try {
const response = await axios.get(
`${process.env.REACT_APP_DOMAIN_ROOT}/public/audio/${userData.id}.mp3`,
{ responseType: 'blob' }
);
setUploadedFile(() => {
return new File([response.data], 'userAudio.mp3', {
type: response.data.type,
lastModified: Date.now(),
});
});
} catch (error) {}
}, [userData.id]);
useEffect(getData, [getData]);
function togglePlay() {
if (audioRecording && !audioRecording.ended) {
audioRecording.pause();
audioRecording.currentTime = 0;
setAudioRecording(() => null);
} else {
const audioFile = new Audio(URL.createObjectURL(uploadedFile));
setAudioRecording(() => audioFile);
audioFile.play();
}
}
function record() {
if (recording) {
setRecording(() => false);
recorder
.stop()
.getMp3()
.then(([buffer, blob]) => {
setUploadedFile(() => {
return new File(buffer, 'userAudio.mp3', {
type: blob.type,
lastModified: Date.now(),
});
});
});
} else {
setRecording(() => true);
recorder.start();
}
}
function uploadRecord() {
setUploadedFile(() => {
return document.getElementById('icon-button-file').files[0];
});
}
async function handleSubmit(e) {
e.preventDefault();
var formData = new FormData();
formData.append('audioFile', uploadedFile, 'userAudio.mp3');
const instance = createRequest();
instance.defaults.headers.common['Content-Type'] = 'multipart/form-data';
return instance.post('/auth/audio', formData);
}
return <RecordMp3 {...{ togglePlay, record, uploadRecord, handleSubmit }} />;
}
export default RecordMp3Controller;

View file

@ -0,0 +1,52 @@
import React, { useCallback, useState } from "react";
import { RecoverForm } from "../components";
import createRequest from "../utils/requests";
import { APIError } from "../types";
function RecoverFormController() {
const [form, setForm] = useState({
email: "",
});
const [error, setError] = useState("");
const handleChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
const { name } = e.target;
const { value } = e.target;
setForm((form) => ({ ...form, [name]: value }));
},
[]
);
const handleSubmit = useCallback(
async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
try {
const instance = createRequest();
await instance.post("/auth/reset", form);
} catch (e) {
const error = e as APIError;
if (error.response.data.error) {
setError(error.response.data.error);
} else {
setError("An error occurred");
}
}
},
[form]
);
return (
<RecoverForm
form={form}
error={error}
handleChange={handleChange}
handleSubmit={handleSubmit}
/>
);
}
export default RecoverFormController;

View file

@ -1,40 +0,0 @@
import React, { useState } from 'react';
import { RecoverForm } from '../components';
import { createRequest } from '../utils/requests';
function RecoverFormController() {
const [form, setForm] = useState({});
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();
try {
const instance = createRequest();
await instance.post('/auth/reset', form);
} catch (error) {
setError(() => error.response.data);
}
}
return (
<RecoverForm
form={form}
error={error}
handleChange={handleChange}
handleSubmit={handleSubmit}
/>
);
}
export default RecoverFormController;

View file

@ -0,0 +1,65 @@
import { useState } from "react";
import { useNavigate, useSearchParams } from "react-router-dom";
import { RegisterForm } from "../components";
import { useError } from "../contexts";
import createRequest from "../utils/requests";
import { APIError } from "../types";
function RegisterFormController() {
const navigate = useNavigate();
const [params] = useSearchParams();
const { error, setError } = useError();
const [form, setForm] = useState({
firstName: "",
lastName: "",
email: "",
pronouns: "",
password: "",
confirmPassword: "",
});
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
const { name } = e.target;
const { value } = e.target;
setForm((form) => ({ ...form, [name]: value }));
}
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { confirmPassword, ...filteredForm } = form;
const fullForm = { ...filteredForm, orgID: params.get("orgID") };
try {
const instance = createRequest();
await instance.post("/auth/register", fullForm);
navigate("/login");
} catch (e) {
const error = e as APIError;
if (error.response.data.error) {
setError(error.response.data.error);
} else {
setError("An error occurred");
}
}
}
return (
<RegisterForm
{...{
form,
handleSubmit,
handleChange,
error,
}}
/>
);
}
export default RegisterFormController;

View file

@ -1,49 +0,0 @@
import React, { useState } from 'react';
import { useNavigate, useSearchParams } from 'react-router-dom';
import RegisterForm from '../components/RegisterForm';
import { useError } from '../contexts';
import { createRequest } from '../utils/requests';
function RegisterFormController() {
const navigate = useNavigate();
const [params] = useSearchParams();
const { error, setError } = useError();
const [form, setForm] = useState({
firstName: '',
lastName: '',
email: '',
pronouns: '',
password: '',
confirmPassword: '',
});
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;
const fullForm = { ...filteredForm, orgID: params.get('orgID') };
try {
const instance = createRequest();
await instance.post('/auth/register', fullForm);
navigate('/login');
} catch (error) {
setError(() => error.response.data);
}
}
return <RegisterForm {...{ form, handleSubmit, handleChange, error }} />;
}
export default RegisterFormController;

View file

@ -0,0 +1,47 @@
import { useCallback, useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { ResetForm as ResetFormComponent } from "../components";
import { useError } from "../contexts";
import createRequest from "../utils/requests";
export default function ResetForm() {
const navigate = useNavigate();
const { id } = useParams();
const [form, setForm] = useState({
password: "",
confirmPassword: "",
});
const { error } = useError();
const handleChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
const { name } = e.target;
const { value } = e.target;
setForm((form) => ({ ...form, [name]: value }));
},
[]
);
const handleSubmit = useCallback(
async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const instance = createRequest();
await instance.patch(`/reset/${id}`);
navigate("/login");
},
[id, navigate]
);
return (
<ResetFormComponent
form={form}
handleChange={handleChange}
handleSubmit={handleSubmit}
error={error}
/>
);
}

View file

@ -1,49 +0,0 @@
import { useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { ResetForm } from '../components';
import { useError } from '../contexts';
import { createRequest } from '../utils/requests';
function ResetFormController() {
const navigate = useNavigate();
const { id } = useParams();
const [form, setForm] = useState({
password: '',
confirmPassword: '',
});
const { error, setError } = useError();
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();
try {
const instance = createRequest();
await instance.patch(`/reset/${id}`);
navigate('/login');
} catch (error) {
setError(() => error.response.data);
}
}
return (
<ResetForm
form={form}
handleSubmit={handleSubmit}
handleChange={handleChange}
error={error}
/>
);
}
export default ResetFormController;

View file

@ -0,0 +1,39 @@
import { useCallback, useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { UpdateOrganizationForm as UpdateOrganizationFormComponent } from "../components";
import createRequest from "../utils/requests";
export default function UpdateOrganizationForm() {
const { orgID } = useParams();
const navigate = useNavigate();
const [name, setName] = useState("");
const handleChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
const { value } = e.target;
setName(() => value);
},
[]
);
const handleSubmit = useCallback(
(e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const instance = createRequest();
instance.patch(`/org/${orgID}`, { orgName: name });
navigate(`/org/${orgID}`);
},
[name, orgID, navigate]
);
return (
<UpdateOrganizationFormComponent
name={name}
handleSubmit={handleSubmit}
handleChange={handleChange}
/>
);
}

View file

@ -1,35 +0,0 @@
import { useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { UpdateOrganizationForm } from '../components';
import { createRequest } from '../utils/requests';
function UpdateOrganizationFormController() {
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();
const instance = createRequest();
await instance.patch(`/org/${orgID}`, { orgName: name });
navigate(`/org/${orgID}`);
}
return (
<UpdateOrganizationForm
name={name}
handleSubmit={handleSubmit}
handleChange={handleChange}
/>
);
}
export default UpdateOrganizationFormController;

View file

@ -1,27 +0,0 @@
import Navbar from './NavbarController';
import LoginForm from './LoginFormController';
import RegisterForm from './RegisterFormController';
import MainPageDisplay from './MainPageController';
import EditUserForm from './EditUserFormController';
import RecordMp3 from './RecordMp3Controller';
import RecoverForm from './RecoverFormController';
import ResetForm from './ResetFormController';
import OwnedOrgsDashboard from './OwnedOrgsDashboard';
import MemberOrgsDashboard from './MemberOrgsDashboard';
import Dashboard from './DashboardController';
import UpdateOrganizationForm from './UpdateOrganizationFormController';
export {
Navbar,
LoginForm,
RegisterForm,
MainPageDisplay,
EditUserForm,
RecordMp3,
RecoverForm,
ResetForm,
OwnedOrgsDashboard,
MemberOrgsDashboard,
Dashboard,
UpdateOrganizationForm,
};

View file

@ -0,0 +1,25 @@
import Navbar from "./Navbar";
import MainPage from "./MainPage";
import LoginForm from "./LoginForm";
import RegisterForm from "./RegisterForm";
import EditUserForm from "./EditUserForm";
import RecoverForm from "./RecoverForm";
import ResetForm from "./ResetForm";
import OwnedOrgsDashboard from "./OwnedOrgsDashboard";
import Dashboard from "./Dashboard";
import UpdateOrganizationForm from "./UpdateOrganizationForm";
import MemberOrgsDashboard from "./MemberOrgsDashboard";
export {
Navbar,
MainPage,
LoginForm,
RegisterForm,
EditUserForm,
RecoverForm,
ResetForm,
OwnedOrgsDashboard,
Dashboard,
UpdateOrganizationForm,
MemberOrgsDashboard,
};

8
client/src/environment.d.ts vendored Normal file
View file

@ -0,0 +1,8 @@
declare global {
namespace NodeJS {
interface ProcessEnv {
REACT_APP_DOMAIN_ROOT: string;
REACT_APP_API_ROOT: string;
}
}
}

View file

@ -1,10 +0,0 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);

9
client/src/main.jsx Normal file
View file

@ -0,0 +1,9 @@
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode>
<App />
</React.StrictMode>
);

Some files were not shown because too many files have changed in this diff Show more