diff --git a/client/src/App.jsx b/client/src/App.jsx
index baed3cc..a45a0cc 100644
--- a/client/src/App.jsx
+++ b/client/src/App.jsx
@@ -2,19 +2,19 @@ import React, { useState } from 'react';
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
import Navbar from './components/Navbar';
-import MainPage from './components/MainPage';
+import MainPage from './controllers/MainPage';
-import Form from './components/utils/Form';
-import Login from './components/forms/Login';
-import Register from './components/forms/Register';
-import EditUser from './components/forms/EditUser';
-import Recover from './components/forms/Recover';
-import Reset from './components/forms/Reset';
+import Form from './components/Form';
+import Login from './controllers/Login';
+import Register from './controllers/Register';
+import EditUser from './controllers/EditUser';
+import Recover from './controllers/Recover';
+import Reset from './controllers/Reset';
-import OwnedOrgs from './components/orgs/OwnedOrgs';
-import MemberOrgs from './components/orgs/MemberOrgs';
-import OrgDashboard from './components/orgs/OrgDashboard';
-import UpdateOrganization from './components/forms/UpdateOrganization';
+import OwnedOrgs from './controllers/OwnedOrgs';
+import MemberOrgs from './controllers/MemberOrgs';
+import OrgDashboard from './controllers/OrgDashboard';
+import UpdateOrganization from './controllers/UpdateOrganization';
function App() {
const [token, setToken] = useState(true);
diff --git a/client/src/components/Dashboard.jsx b/client/src/components/Dashboard.jsx
new file mode 100644
index 0000000..a7821f0
--- /dev/null
+++ b/client/src/components/Dashboard.jsx
@@ -0,0 +1,221 @@
+import React, { useState } from 'react';
+import { Link } from 'react-router-dom';
+import {
+ Accordion,
+ AccordionDetails,
+ AccordionSummary,
+ Box,
+ Button,
+ ButtonGroup,
+ Collapse,
+ Grid,
+ IconButton,
+ Stack,
+ Typography,
+ useMediaQuery,
+} from '@mui/material';
+import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
+import PlayArrowIcon from '@mui/icons-material/PlayArrow';
+import { DataGrid } from '@mui/x-data-grid';
+
+import MemberUnit from './MemberUnit';
+import { DynamicPaper, DynamicStack } from './StyledElements';
+
+function Dashboard({
+ rows,
+ setSelection,
+ onClick,
+ org,
+ status,
+ handleDelete,
+ copyID,
+ copyJoinLink,
+ removeSelected,
+ leaveOrg,
+}) {
+ const xs = useMediaQuery((theme) => theme.breakpoints.only('xs'));
+ const sm = useMediaQuery((theme) => theme.breakpoints.only('sm'));
+ const md = useMediaQuery((theme) => theme.breakpoints.only('md'));
+
+ const [open, setOpen] = useState(false);
+
+ const linkStyle = { textDecoration: 'none', color: 'inherit' };
+ const columns = [
+ { field: 'id', headerName: 'ID', flex: 1 },
+ { field: 'firstName', headerName: 'First Name', flex: 3 },
+ { field: 'lastName', headerName: 'Last Name', flex: 3 },
+ { field: 'nickname', headerName: 'Nickname', flex: 2 },
+ {
+ field: 'namePronounciation',
+ headerName: 'Pronounciation',
+ flex: 2,
+ sortable: false,
+ renderCell: (params) => (
+ onClick(params.id)}
+ >
+
+
+ ),
+ },
+ { field: 'pronouns', headerName: 'Pronouns', flex: 2 },
+ { field: 'email', headerName: 'Email', flex: 10, sortable: false },
+ ];
+
+ function handleOpen() {
+ setOpen((initial) => (initial ? false : true));
+ }
+
+ const buttonGroupOrientation = () => {
+ if (xs) return 'vertical';
+ };
+
+ function calculateCollapsedSize() {
+ if (xs) return '37vh';
+ if (sm) return '63vh';
+ if (md) return '68vh';
+ }
+
+ function calculateTableHeight() {
+ return open ? '64vh' : '100%';
+ }
+
+ return (
+
+
+
+ }>
+
+ Organizations/{org.name}
+
+
+
+
+
+
+
+
+ Organization ID: {org.orgID}
+
+
+ Members: {org.memberCount}
+
+
+ Created On: {org.createdAt}
+
+
+
+
+
+
+ Owner:{' '}
+ {`${org.owner.firstName} ${org.owner.lastName}`}
+
+
+ Email: {org.owner.email}
+
+
+
+
+
+ {status ? (
+
+
+
+
+
+
+
+
+
+
+
+ ) : (
+
+ )}
+
+
+
+
+
+
+
+
+
+ {(xs || sm) &&
+ rows.map((member) => {
+ return (
+
+ );
+ })}
+ {md && (
+
+ {
+ setSelection(() => newSelection.rows);
+ }}
+ checkboxSelection
+ />
+
+ )}
+
+
+
+ );
+}
+
+export default Dashboard;
diff --git a/client/src/components/EditUserForm.jsx b/client/src/components/EditUserForm.jsx
new file mode 100644
index 0000000..5cf0c3a
--- /dev/null
+++ b/client/src/components/EditUserForm.jsx
@@ -0,0 +1,64 @@
+import React from 'react';
+import { Link } from 'react-router-dom';
+import { Box, Typography } from '@mui/material';
+
+import Form from './Form';
+import RecordMp3 from './RecordMp3';
+import { FormSubmit, FormTextField } from './StyledElements';
+
+function EditUserForm({ form, error, handleSubmit, handleChange }) {
+ const linkStyle = { textDecoration: 'none', color: 'inherit' };
+
+ return (
+
+
+
+ );
+}
+
+export default EditUserForm;
diff --git a/client/src/components/Form.jsx b/client/src/components/Form.jsx
new file mode 100644
index 0000000..50d4e1d
--- /dev/null
+++ b/client/src/components/Form.jsx
@@ -0,0 +1,45 @@
+import React from 'react';
+import { Outlet } from 'react-router-dom';
+import { Box, Grid, Paper, Typography } from '@mui/material';
+import { useTheme } from '@mui/material/styles';
+
+function Form({ children, text }) {
+ const theme = useTheme();
+
+ return (
+
+
+
+
+
+ {text && (
+
+ {text}
+
+ )}
+ {children}
+
+
+
+
+
+
+ );
+}
+
+export default Form;
diff --git a/client/src/components/LoginForm.jsx b/client/src/components/LoginForm.jsx
new file mode 100644
index 0000000..abe93f7
--- /dev/null
+++ b/client/src/components/LoginForm.jsx
@@ -0,0 +1,73 @@
+import React from 'react';
+import { Link } from 'react-router-dom';
+import { Box, Checkbox, FormControlLabel, Typography } from '@mui/material';
+
+import Form from '../Form';
+import { FormSubmit, FormTextField } from '../StyledElements';
+
+function Login({
+ form,
+ error,
+ handleSubmit,
+ handleChange,
+ handleCheckboxChange,
+}) {
+ return (
+
+
+ );
+}
+
+export default Login;
diff --git a/client/src/components/MainPage.jsx b/client/src/components/MainPage.jsx
deleted file mode 100644
index 3bafc82..0000000
--- a/client/src/components/MainPage.jsx
+++ /dev/null
@@ -1,214 +0,0 @@
-import React, { useState, useEffect } from 'react';
-import { Link, useNavigate } from 'react-router-dom';
-
-import axios from 'axios';
-import Cookies from 'js-cookie';
-
-import {
- Button,
- Typography,
- Box,
- Grid,
- Stack,
- TextField,
- FormGroup,
-} from '@mui/material';
-import { styled } from '@mui/material/styles';
-
-import OrgUnit from './utils/OrgUnit';
-import StackItem from './utils/StackItem';
-
-const Form = styled(FormGroup)(({ theme }) => ({
- [theme.breakpoints.only('xs')]: {
- column: true,
- },
-}));
-const FormField = styled(TextField)(({ theme }) => ({
- [theme.breakpoints.only('xs')]: {
- column: true,
- width: '75%',
- },
-}));
-const FormButton = styled(Button)(({ theme }) => ({
- disableElevation: true,
- color: theme.palette.primary.main,
- [theme.breakpoints.only('xs')]: {
- column: true,
- width: '25%',
- },
-}));
-
-function MainPage() {
- const navigate = useNavigate();
-
- const [ownedOrgs, setOwnedOrgs] = useState([]);
- const [orgs, setOrgs] = useState([]);
- const [form, setForm] = useState({
- name: '',
- id: '',
- });
-
- const linkStyle = { textDecoration: 'none', color: 'inherit' };
-
- useEffect(() => {
- if (Cookies.get('token')) {
- getMemberOrgs();
- getOwnedOrgs();
- } else {
- navigate('/login');
- }
- }, [navigate]);
-
- function handleChange(e) {
- const name = e.target.name;
- const value = e.target.value;
-
- setForm((form) => {
- return { ...form, [name]: value };
- });
- }
-
- async function getOwnedOrgs() {
- await axios
- .get(
- `${process.env.REACT_APP_API_ROOT}/auth?token=${Cookies.get(
- 'token'
- )}`
- )
- .then((response) => {
- setOwnedOrgs(() => response.data);
- })
- .catch((e) => {
- console.log(e);
- });
- }
-
- async function getMemberOrgs() {
- await axios
- .get(
- `${
- process.env.REACT_APP_API_ROOT
- }/auth/orgs?token=${Cookies.get('token')}`
- )
- .then((response) => {
- setOrgs(() => response.data);
- });
- }
-
- async function createOrg(e) {
- e.preventDefault();
- console.log('create');
-
- await axios
- .post(`${process.env.REACT_APP_API_ROOT}/org/create`, {
- token: Cookies.get('token'),
- orgName: form.name,
- })
- .then(() => {
- getOwnedOrgs();
- });
- }
-
- async function joinOrg(e) {
- e.preventDefault();
-
- await axios
- .post(`${process.env.REACT_APP_API_ROOT}/org/${form.id}/add`, {
- token: Cookies.get('token'),
- })
- .then(() => {
- getMemberOrgs();
- });
- }
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Your Organizations
-
-
- {ownedOrgs.length === 0 && (
-
- You do own any organizations
-
- )}
-
- {ownedOrgs.map((org) => (
-
- ))}
-
-
-
-
- Joined Organizations
-
-
- {orgs.length === 0 && (
-
- You are not a member in any
- organizations
-
- )}
-
- {orgs.map((org) => (
-
- ))}
-
-
-
-
-
-
-
- );
-}
-
-export default MainPage;
diff --git a/client/src/components/MainPageBase.jsx b/client/src/components/MainPageBase.jsx
new file mode 100644
index 0000000..9f7c97c
--- /dev/null
+++ b/client/src/components/MainPageBase.jsx
@@ -0,0 +1,106 @@
+import React from 'react';
+import { Link } from 'react-router-dom';
+import { Box, Grid, Stack, Typography } from '@mui/material';
+
+import OrgUnit from './OrgUnit';
+import StackItem from './StackItem';
+import { Form, FormButton, FormField } from './StyledElements';
+
+function MainPageBase({
+ form,
+ orgs,
+ ownedOrgs,
+ joinOrg,
+ createOrg,
+ handleChange,
+}) {
+ const linkStyle = { textDecoration: 'none', color: 'inherit' };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Your Organizations
+
+
+ {ownedOrgs.length === 0 && (
+
+ You do own any organizations
+
+ )}
+
+ {ownedOrgs.map((org) => (
+
+ ))}
+
+
+
+
+ Joined Organizations
+
+
+ {orgs.length === 0 && (
+
+ You are not a member in any organizations
+
+ )}
+
+ {orgs.map((org) => (
+
+ ))}
+
+
+
+
+
+
+ );
+}
+
+export default MainPageBase;
diff --git a/client/src/components/utils/MemberUnit.jsx b/client/src/components/MemberUnit.jsx
similarity index 99%
rename from client/src/components/utils/MemberUnit.jsx
rename to client/src/components/MemberUnit.jsx
index 8f0ab0b..61b5e8d 100644
--- a/client/src/components/utils/MemberUnit.jsx
+++ b/client/src/components/MemberUnit.jsx
@@ -1,12 +1,11 @@
import React from 'react';
-
import {
Accordion,
- AccordionSummary,
AccordionDetails,
- Typography,
- IconButton,
+ AccordionSummary,
Grid,
+ IconButton,
+ Typography,
} from '@mui/material';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import PlayArrowIcon from '@mui/icons-material/PlayArrow';
diff --git a/client/src/components/Navbar.jsx b/client/src/components/Navbar.jsx
index 7b2ad64..aac79dc 100644
--- a/client/src/components/Navbar.jsx
+++ b/client/src/components/Navbar.jsx
@@ -1,17 +1,16 @@
import React from 'react';
import { Link, Outlet } from 'react-router-dom';
-
import {
AppBar,
Button,
+ CssBaseline,
+ IconButton,
Toolbar,
Typography,
- IconButton,
- CssBaseline,
useMediaQuery,
} from '@mui/material';
+import { createTheme, ThemeProvider } from '@mui/material/styles';
import MenuIcon from '@mui/icons-material/Menu';
-import { ThemeProvider, createTheme } from '@mui/material/styles';
function Navbar({ token }) {
const linkStyle = { textDecoration: 'none', color: 'inherit' };
diff --git a/client/src/components/utils/OrgUnit.jsx b/client/src/components/OrgUnit.jsx
similarity index 99%
rename from client/src/components/utils/OrgUnit.jsx
rename to client/src/components/OrgUnit.jsx
index b869482..e053c5e 100644
--- a/client/src/components/utils/OrgUnit.jsx
+++ b/client/src/components/OrgUnit.jsx
@@ -1,11 +1,10 @@
import React from 'react';
import { Link } from 'react-router-dom';
-
import {
- Typography,
Accordion,
- AccordionSummary,
AccordionDetails,
+ AccordionSummary,
+ Typography,
} from '@mui/material';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
diff --git a/client/src/components/utils/Organizations.jsx b/client/src/components/Organizations.jsx
similarity index 72%
rename from client/src/components/utils/Organizations.jsx
rename to client/src/components/Organizations.jsx
index 1371129..66fe020 100644
--- a/client/src/components/utils/Organizations.jsx
+++ b/client/src/components/Organizations.jsx
@@ -1,19 +1,12 @@
-import React, { useEffect, useState } from 'react';
-
-import { Paper, Typography, Box } from '@mui/material';
+import React from 'react';
+import { Box, Paper, Typography } from '@mui/material';
import { useTheme } from '@mui/material/styles';
import OrgUnit from './OrgUnit';
-function Organizations({ retrieveOrgs }) {
+function Organizations({ orgs }) {
const theme = useTheme();
- const [orgs, setOrgs] = useState([]);
-
- useEffect(() => {
- retrieveOrgs(setOrgs);
- }, [retrieveOrgs]);
-
return (
+
+ Recover my Password
+
+
+ {error && (
+
+
+ {error}
+
+
+ )}
+ {success && (
+
+
+ {success}
+
+
+ )}
+
+ Recover my password
+
+
+ );
+}
+
+export default RecoverForm;
diff --git a/client/src/components/RegisterForm.jsx b/client/src/components/RegisterForm.jsx
new file mode 100644
index 0000000..9aad97a
--- /dev/null
+++ b/client/src/components/RegisterForm.jsx
@@ -0,0 +1,78 @@
+import React from 'react';
+import { Link } from 'react-router-dom';
+import { Box, TextField, Typography } from '@mui/material';
+
+import Form from '../Form';
+import { FormSubmit, FormTextField } from '../StyledElements';
+
+function RegisterForm({ form, handleSubmit, handleChange, error }) {
+ return (
+
+
+ );
+}
+
+export default RegisterForm;
diff --git a/client/src/components/ResetForm.jsx b/client/src/components/ResetForm.jsx
new file mode 100644
index 0000000..c26d419
--- /dev/null
+++ b/client/src/components/ResetForm.jsx
@@ -0,0 +1,46 @@
+import React from 'react';
+import { Box, Typography } from '@mui/material';
+
+import { FormSubmit, FormTextField } from '../StyledElements';
+
+function ResetForm({ form, handleSubmit, handleChange, error }) {
+ return (
+
+ );
+}
+
+export default ResetForm;
diff --git a/client/src/components/utils/StackItem.jsx b/client/src/components/StackItem.jsx
similarity index 99%
rename from client/src/components/utils/StackItem.jsx
rename to client/src/components/StackItem.jsx
index e4bb285..2bc4807 100644
--- a/client/src/components/utils/StackItem.jsx
+++ b/client/src/components/StackItem.jsx
@@ -1,5 +1,4 @@
import React from 'react';
-
import { Paper, Typography } from '@mui/material';
import { useTheme } from '@mui/material/styles';
diff --git a/client/src/components/StyledElements.jsx b/client/src/components/StyledElements.jsx
new file mode 100644
index 0000000..c8b11cb
--- /dev/null
+++ b/client/src/components/StyledElements.jsx
@@ -0,0 +1,88 @@
+import { Button, FormGroup, Paper, Stack, TextField } from '@mui/material';
+import { styled } from '@mui/material/styles';
+
+const Form = styled(FormGroup)(({ theme }) => ({
+ [theme.breakpoints.only('xs')]: {
+ column: true,
+ },
+}));
+const FormField = styled(TextField)(({ theme }) => ({
+ [theme.breakpoints.only('xs')]: {
+ column: true,
+ width: '75%',
+ },
+}));
+const FormButton = styled(Button)(({ theme }) => ({
+ disableElevation: true,
+ color: theme.palette.primary.main,
+ [theme.breakpoints.only('xs')]: {
+ column: true,
+ width: '25%',
+ },
+}));
+
+const DynamicStack = styled(Stack)(({ theme }) => ({
+ [theme.breakpoints.up('sm')]: {
+ spacing: 2,
+ alignItems: 'flex-end',
+ },
+}));
+
+const DynamicPaper = styled(Paper)(({ theme }) => ({
+ ...theme.typography.body2,
+ padding: '2vh',
+ textAlign: 'center',
+ color: theme.palette.text.secondary,
+ flexGrow: 1,
+ margin: '0 2vh',
+ [theme.breakpoints.only('xs')]: {
+ height: '80vh',
+ },
+ [theme.breakpoints.only('sm')]: {
+ height: '80vh',
+ },
+ [theme.breakpoints.only('md')]: {
+ height: '82vh',
+ },
+}));
+
+const FormTextField = ({ label, name, value, onChange, required, other }) => {
+ return (
+
+ );
+};
+
+const FormSubmit = ({ color, children, type, other }) => {
+ return (
+
+ );
+};
+
+export {
+ Form,
+ FormField,
+ FormButton,
+ DynamicStack,
+ DynamicPaper,
+ FormSubmit,
+ FormTextField,
+};
diff --git a/client/src/components/UpdateOrganizationForm.jsx b/client/src/components/UpdateOrganizationForm.jsx
new file mode 100644
index 0000000..455e74f
--- /dev/null
+++ b/client/src/components/UpdateOrganizationForm.jsx
@@ -0,0 +1,29 @@
+import React from 'react';
+import { Typography } from '@mui/material';
+
+import Form from '../Form';
+import { FormSubmit, FormTextField } from '../StyledElements';
+
+function UpdateOrganizationForm({ name, handleSubmit, handleChange }) {
+ return (
+
+
+ );
+}
+
+export default UpdateOrganizationForm;
diff --git a/client/src/components/forms/EditUser.jsx b/client/src/components/forms/EditUser.jsx
deleted file mode 100644
index 936de71..0000000
--- a/client/src/components/forms/EditUser.jsx
+++ /dev/null
@@ -1,148 +0,0 @@
-import React, { useState, useEffect } from 'react';
-import { Link, useNavigate } from 'react-router-dom';
-
-import axios from 'axios';
-import Cookies from 'js-cookie';
-
-import { Box, Typography, TextField, Button } from '@mui/material';
-
-import Form from '../utils/Form';
-import RecordMp3 from '../utils/RecordMp3';
-
-function EditUser() {
- const navigate = useNavigate();
-
- const [form, setForm] = useState({
- firstName: '',
- lastName: '',
- email: '',
- pronouns: '',
- });
- const [error, setError] = useState('');
-
- const linkStyle = { textDecoration: 'none', color: 'inherit' };
-
- useEffect(() => {
- async function getInfo() {
- axios
- .get(
- `${process.env.REACT_APP_API_ROOT}/auth/${Cookies.get(
- 'userID'
- )}?token=${Cookies.get('token')}`
- )
- .then((response) => {
- const user = response.data;
-
- setForm(() => {
- return {
- firstName: user.firstName,
- lastName: user.lastName,
- email: user.email,
- pronouns: user.pronouns,
- };
- });
- });
- }
- getInfo();
- }, []);
-
- function handleChange(e) {
- const name = e.target.name;
- const value = e.target.value;
-
- setForm((form) => {
- return { ...form, [name]: value };
- });
- }
-
- async function handleSubmit(e) {
- e.preventDefault();
- await axios
- .patch(`${process.env.REACT_APP_API_ROOT}/auth/update`, {
- ...form,
- token: Cookies.get('token'),
- })
- .then(() => {
- navigate('/');
- })
- .catch((e) => setError(() => e.response.data));
- }
-
- return (
-
-
-
- );
-}
-
-export default EditUser;
diff --git a/client/src/components/forms/Login.jsx b/client/src/components/forms/Login.jsx
deleted file mode 100644
index f5a2eb2..0000000
--- a/client/src/components/forms/Login.jsx
+++ /dev/null
@@ -1,154 +0,0 @@
-import React, { useState, useEffect } from 'react';
-import { Link, useNavigate } from 'react-router-dom';
-
-import axios from 'axios';
-import Cookies from 'js-cookie';
-
-import {
- Box,
- Typography,
- TextField,
- Button,
- FormControlLabel,
- Checkbox,
-} from '@mui/material';
-
-import Form from '../utils/Form';
-
-function Login({ setToken }) {
- const navigate = useNavigate();
-
- const [form, setForm] = useState({
- email: '',
- password: '',
- remember: false,
- });
- const [error, setError] = useState('');
-
- useEffect(() => {
- async function logout() {
- await axios
- .post(`${process.env.REACT_APP_API_ROOT}/auth/logout`, {
- token: Cookies.get('token'),
- })
- .catch((e) => {
- console.log(e);
- });
-
- Cookies.remove('token');
- Cookies.remove('userID');
-
- setToken(() => false);
- }
-
- if (Cookies.get('token')) {
- logout();
- }
- }, [setToken]);
-
- function handleChange(e) {
- const name = e.target.name;
- const value = e.target.value;
-
- setForm((form) => {
- return { ...form, [name]: value };
- });
- }
-
- function handleCheckboxChange(e) {
- const name = e.target.name;
- const checked = e.target.checked;
-
- setForm(() => {
- return { ...form, [name]: checked };
- });
- }
-
- async function handleSubmit(e) {
- e.preventDefault();
- await axios
- .post(`${process.env.REACT_APP_API_ROOT}/auth/login`, form)
- .then((response) => {
- Cookies.set('token', response.data.token, {
- expires: form.remember ? 3650 : 1,
- });
- Cookies.set('userID', response.data.userID, {
- expires: form.remember ? 3650 : 1,
- });
- setToken(true);
- navigate('/');
- })
- .catch((e) => setError(() => e.response.data));
- }
-
- return (
-
-
- );
-}
-
-export default Login;
diff --git a/client/src/components/forms/Recover.jsx b/client/src/components/forms/Recover.jsx
deleted file mode 100644
index 70ac74f..0000000
--- a/client/src/components/forms/Recover.jsx
+++ /dev/null
@@ -1,76 +0,0 @@
-import React, { useState } from 'react';
-import axios from 'axios';
-
-import { Box, Typography, TextField, Button } from '@mui/material';
-
-function Recover() {
- const [form, setForm] = useState({
- email: '',
- });
- const [error, setError] = useState('');
- const [success, setSuccess] = useState('');
-
- function handleChange(e) {
- const name = e.target.name;
- const value = e.target.value;
-
- setForm((form) => {
- return { ...form, [name]: value };
- });
- }
-
- async function handleSubmit(e) {
- e.preventDefault();
- await axios
- .get(
- `${process.env.REACT_APP_API_ROOT}/auth/reset-password?${form.email}`
- )
- .then((response) => {
- setSuccess(() => response.data);
- })
- .catch((e) => setError(() => e.response.data));
- }
-
- return (
-
- );
-}
-
-export default Recover;
diff --git a/client/src/components/forms/Register.jsx b/client/src/components/forms/Register.jsx
deleted file mode 100644
index 721600b..0000000
--- a/client/src/components/forms/Register.jsx
+++ /dev/null
@@ -1,140 +0,0 @@
-import React, { useState } from 'react';
-import { Link, useNavigate, useSearchParams } from 'react-router-dom';
-
-import axios from 'axios';
-
-import { Box, Typography, TextField, Button } from '@mui/material';
-
-import Form from '../utils/Form';
-
-function Register() {
- const navigate = useNavigate();
- const [params] = useSearchParams();
-
- const [form, setForm] = useState({
- firstName: '',
- lastName: '',
- email: '',
- pronouns: '',
- password: '',
- confirmPassword: '',
- });
- const [error, setError] = useState('');
-
- function handleChange(e) {
- const name = e.target.name;
- const value = e.target.value;
-
- setForm((form) => {
- return { ...form, [name]: value };
- });
- }
-
- async function handleSubmit(e) {
- e.preventDefault();
-
- const { confirmPassword, ...filteredForm } = form;
- await axios
- .post(`${process.env.REACT_APP_API_ROOT}/auth/register`, {
- ...filteredForm,
- orgID: params.orgID,
- })
- .then(() => {
- navigate('/login');
- })
- .catch((e) => setError(() => e.response.data));
- }
-
- return (
-
-
- );
-}
-
-export default Register;
diff --git a/client/src/components/forms/Reset.jsx b/client/src/components/forms/Reset.jsx
deleted file mode 100644
index f605f1f..0000000
--- a/client/src/components/forms/Reset.jsx
+++ /dev/null
@@ -1,90 +0,0 @@
-import React, { useState } from 'react';
-import { useNavigate, useParams } from 'react-router-dom';
-
-import axios from 'axios';
-
-import { Box, Typography, TextField, Button } from '@mui/material';
-
-function Reset() {
- const navigate = useNavigate();
- const { id } = useParams();
-
- const [form, setForm] = useState({
- password: '',
- confirmPassword: '',
- });
- const [error, setError] = useState('');
-
- function handleChange(e) {
- const name = e.target.name;
- const value = e.target.value;
-
- setForm((form) => {
- return { ...form, [name]: value };
- });
- }
-
- async function handleSubmit(e) {
- e.preventDefault();
- await axios
- .patch(
- `${process.env.REACT_APP_API_ROOT}/auth/reset-password/${id}`,
- {
- password: form.password,
- }
- )
- .then(() => {
- navigate('/login');
- })
- .catch((e) => setError(() => e.response.data));
- }
-
- return (
-
- );
-}
-
-export default Reset;
diff --git a/client/src/components/forms/UpdateOrganization.jsx b/client/src/components/forms/UpdateOrganization.jsx
deleted file mode 100644
index d9af585..0000000
--- a/client/src/components/forms/UpdateOrganization.jsx
+++ /dev/null
@@ -1,66 +0,0 @@
-import React, { useState } from 'react';
-import { useParams, useNavigate } from 'react-router-dom';
-
-import axios from 'axios';
-import Cookies from 'js-cookie';
-
-import { Typography, TextField, Button } from '@mui/material';
-
-import Form from '../utils/Form';
-
-function UpdateOrganization() {
- const { orgID } = useParams();
- const navigate = useNavigate();
-
- const [name, setName] = useState('');
-
- function handleChange(e) {
- const value = e.target.value;
-
- setName(() => value);
- }
-
- async function handleSubmit(e) {
- e.preventDefault();
- await axios
- .patch(`${process.env.REACT_APP_API_ROOT}/org/update`, {
- token: Cookies.get('token'),
- orgName: name,
- orgID,
- })
- .then(() => {
- navigate(`/org/${orgID}`);
- });
- }
-
- return (
-
-
- );
-}
-
-export default UpdateOrganization;
diff --git a/client/src/components/orgs/OrgDashboard.jsx b/client/src/components/orgs/OrgDashboard.jsx
deleted file mode 100644
index 226b51c..0000000
--- a/client/src/components/orgs/OrgDashboard.jsx
+++ /dev/null
@@ -1,263 +0,0 @@
-import React, { useState, useEffect } from 'react';
-import { useNavigate, useParams, Link } from 'react-router-dom';
-
-import axios from 'axios';
-import Cookies from 'js-cookie';
-
-import {
- Typography,
- Grid,
- Stack,
- Box,
- ButtonGroup,
- Button,
- Accordion,
- AccordionSummary,
- AccordionDetails,
- useMediaQuery,
-} from '@mui/material';
-import { styled } from '@mui/material/styles';
-import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
-
-import Dashboard from '../utils/Dashboard';
-
-const DynamicStack = styled(Stack)(({ theme }) => ({
- [theme.breakpoints.up('sm')]: {
- spacing: 2,
- alignItems: 'flex-end',
- },
-}));
-
-function OrgDashboard() {
- const xs = useMediaQuery((theme) => theme.breakpoints.only('xs'));
- const { orgID } = useParams();
- const navigate = useNavigate();
-
- const [org, setOrg] = useState({
- id: orgID,
- name: '',
- owner: {
- firstName: '',
- lastName: '',
- email: '',
- },
- member: [],
- createdAt: '',
- memberCount: 0,
- });
- const [rows, setRows] = useState([]);
- const [status, setStatus] = useState(true);
- const [selection, setSelection] = useState(null);
- const [open, setOpen] = useState(false);
-
- const linkStyle = { textDecoration: 'none', color: 'inherit' };
- const buttonGroupOrientation = () => {
- if (xs) return 'vertical';
- };
-
- useEffect(() => {
- async function getData() {
- await axios
- .get(
- `${
- process.env.REACT_APP_API_ROOT
- }/org/${orgID}?token=${Cookies.get('token')}`
- )
- .then((response) => {
- const res = response.data;
-
- setOrg(() => {
- return { ...res.org, memberCount: res.memberCount };
- });
-
- setRows(() => res.org.member);
- setStatus(() => res.status);
- })
- .catch((e) => {
- console.log(e);
- navigate('/');
- });
- }
-
- getData();
- }, [navigate, orgID]);
-
- async function handleDelete() {
- await axios
- .delete(`${process.env.REACT_APP_API_ROOT}/org/delete`, {
- params: {
- token: Cookies.get('token'),
- orgID,
- },
- })
- .then(navigate('/'));
- }
-
- function copyID() {
- navigator.clipboard.writeText(orgID);
- }
-
- function copyJoinLink() {
- navigator.clipboard.writeText(
- `${process.env.REACT_APP_DOMAIN_ROOT}/register?orgID=${orgID}`
- );
- }
-
- async function removeSelected() {
- const doomedUserIDs = selection.map((row) => row.id);
-
- await axios.post(
- `${process.env.REACT_APP_API_ROOT}/org/${orgID}/delete`,
- {
- token: Cookies.get('token'),
- orgID,
- doomedUserIDs,
- }
- );
- }
-
- async function leaveOrg() {
- await axios
- .post(`${process.env.REACT_APP_API_ROOT}/org/${orgID}/delete`, {
- token: Cookies.get('token'),
- orgID,
- })
- .then(() => navigate('/'))
- .catch((e) => {
- console.log(e);
- navigate('/');
- });
- }
-
- function handleOpen() {
- setOpen((initial) => (initial ? false : true));
- }
-
- async function play(id) {
- axios
- .get(
- `${process.env.REACT_APP_DOMAIN_ROOT}/public/audio/${id}.mp3`,
- {
- responseType: 'blob',
- }
- )
- .then((res) => {
- const uploadedFile = new File([res.data], 'userAudio.mp3', {
- type: res.data.type,
- lastModified: Date.now(),
- });
- const audioFile = new Audio(URL.createObjectURL(uploadedFile));
- audioFile.play();
- });
- }
-
- return (
-
-
- }>
-
- Organizations/{org.name}
-
-
-
-
-
-
-
-
- Organization ID: {orgID}
-
-
- Members: {org.memberCount}
-
-
- Created On: {org.createdAt}
-
-
-
-
-
-
- Owner:{' '}
- {`${org.owner.firstName} ${org.owner.lastName}`}
-
-
- Email: {org.owner.email}
-
-
-
-
-
- {status ? (
-
-
-
-
-
-
-
-
-
-
-
- ) : (
-
- )}
-
-
-
-
-
-
-
- );
-}
-
-export default OrgDashboard;
diff --git a/client/src/components/utils/Dashboard.jsx b/client/src/components/utils/Dashboard.jsx
deleted file mode 100644
index 5ebb5c8..0000000
--- a/client/src/components/utils/Dashboard.jsx
+++ /dev/null
@@ -1,104 +0,0 @@
-import React from 'react';
-
-import { Paper, Box, IconButton, useMediaQuery, Collapse } from '@mui/material';
-import { styled } from '@mui/material/styles';
-import { DataGrid } from '@mui/x-data-grid';
-import PlayArrowIcon from '@mui/icons-material/PlayArrow';
-
-import MemberUnit from '../utils/MemberUnit';
-
-const DynamicPaper = styled(Paper)(({ theme }) => ({
- ...theme.typography.body2,
- padding: '2vh',
- textAlign: 'center',
- color: theme.palette.text.secondary,
- flexGrow: 1,
- margin: '0 2vh',
- [theme.breakpoints.only('xs')]: {
- height: '80vh',
- },
- [theme.breakpoints.only('sm')]: {
- height: '80vh',
- },
- [theme.breakpoints.only('md')]: {
- height: '82vh',
- },
-}));
-
-function Dashboard({ children, rows, setSelection, onClick, open }) {
- const xs = useMediaQuery((theme) => theme.breakpoints.only('xs'));
- const sm = useMediaQuery((theme) => theme.breakpoints.only('sm'));
- const md = useMediaQuery((theme) => theme.breakpoints.only('md'));
-
- const columns = [
- { field: 'id', headerName: 'ID', flex: 1 },
- { field: 'firstName', headerName: 'First Name', flex: 3 },
- { field: 'lastName', headerName: 'Last Name', flex: 3 },
- { field: 'nickname', headerName: 'Nickname', flex: 2 },
- {
- field: 'namePronounciation',
- headerName: 'Pronounciation',
- flex: 2,
- sortable: false,
- renderCell: (params) => (
- onClick(params.id)}
- >
-
-
- ),
- },
- { field: 'pronouns', headerName: 'Pronouns', flex: 2 },
- { field: 'email', headerName: 'Email', flex: 10, sortable: false },
- ];
-
- function calculateCollapsedSize() {
- if (xs) return '37vh';
- if (sm) return '63vh';
- if (md) return '68vh';
- }
-
- function calculateTableHeight() {
- return open ? '64vh' : '100%';
- }
-
- return (
-
- {children}
-
-
- {(xs || sm) &&
- rows.map((member) => {
- return (
-
- );
- })}
- {md && (
-
- {
- setSelection(() => newSelection.rows);
- }}
- checkboxSelection
- />
-
- )}
-
-
-
- );
-}
-
-export default Dashboard;
diff --git a/client/src/components/utils/Form.jsx b/client/src/components/utils/Form.jsx
deleted file mode 100644
index a4c6235..0000000
--- a/client/src/components/utils/Form.jsx
+++ /dev/null
@@ -1,40 +0,0 @@
-import React from 'react';
-import { Outlet } from 'react-router-dom';
-
-import { Box, Grid, Paper } from '@mui/material';
-import { useTheme } from '@mui/material/styles';
-
-function Form({ children }) {
- const theme = useTheme();
-
- return (
-
-
-
-
-
-
- {children}
-
-
-
-
-
-
-
- );
-}
-
-export default Form;
diff --git a/client/src/controllers/EditUser.jsx b/client/src/controllers/EditUser.jsx
new file mode 100644
index 0000000..cbeca99
--- /dev/null
+++ b/client/src/controllers/EditUser.jsx
@@ -0,0 +1,75 @@
+import React, { useEffect, useState } from 'react';
+import { useNavigate } from 'react-router-dom';
+import axios from 'axios';
+import Cookies from 'js-cookie';
+
+import EditUserForm from '../components/EditUserForm';
+
+function EditUser() {
+ const navigate = useNavigate();
+
+ const [form, setForm] = useState({
+ firstName: '',
+ lastName: '',
+ email: '',
+ pronouns: '',
+ });
+ const [error, setError] = useState('');
+
+ useEffect(() => {
+ async function getInfo() {
+ axios
+ .get(
+ `${process.env.REACT_APP_API_ROOT}/auth/${Cookies.get(
+ 'userID'
+ )}?token=${Cookies.get('token')}`
+ )
+ .then((response) => {
+ const user = response.data;
+
+ setForm(() => {
+ return {
+ firstName: user.firstName,
+ lastName: user.lastName,
+ email: user.email,
+ pronouns: user.pronouns,
+ };
+ });
+ });
+ }
+ getInfo();
+ }, []);
+
+ function handleChange(e) {
+ const name = e.target.name;
+ const value = e.target.value;
+
+ setForm((form) => {
+ return { ...form, [name]: value };
+ });
+ }
+
+ async function handleSubmit(e) {
+ e.preventDefault();
+ await axios
+ .patch(`${process.env.REACT_APP_API_ROOT}/auth/update`, {
+ ...form,
+ token: Cookies.get('token'),
+ })
+ .then(() => {
+ navigate('/');
+ })
+ .catch((e) => setError(() => e.response.data));
+ }
+
+ return (
+
+ );
+}
+
+export default EditUser;
diff --git a/client/src/controllers/Login.jsx b/client/src/controllers/Login.jsx
new file mode 100644
index 0000000..1399ac1
--- /dev/null
+++ b/client/src/controllers/Login.jsx
@@ -0,0 +1,85 @@
+import React, { useEffect, useState } from 'react';
+import { useNavigate } from 'react-router-dom';
+import axios from 'axios';
+import Cookies from 'js-cookie';
+
+import LoginForm from '../components/LoginForm';
+
+function Login({ setToken }) {
+ const navigate = useNavigate();
+
+ const [form, setForm] = useState({
+ email: '',
+ password: '',
+ remember: false,
+ });
+ const [error, setError] = useState('');
+
+ useEffect(() => {
+ async function logout() {
+ await axios
+ .post(`${process.env.REACT_APP_API_ROOT}/auth/logout`, {
+ token: Cookies.get('token'),
+ })
+ .catch((e) => {
+ console.log(e);
+ });
+
+ Cookies.remove('token');
+ Cookies.remove('userID');
+
+ setToken(() => false);
+ }
+
+ if (Cookies.get('token')) {
+ logout();
+ }
+ }, [setToken]);
+
+ function handleChange(e) {
+ const name = e.target.name;
+ const value = e.target.value;
+
+ setForm((form) => {
+ return { ...form, [name]: value };
+ });
+ }
+
+ function handleCheckboxChange(e) {
+ const name = e.target.name;
+ const checked = e.target.checked;
+
+ setForm(() => {
+ return { ...form, [name]: checked };
+ });
+ }
+
+ async function handleSubmit(e) {
+ e.preventDefault();
+ await axios
+ .post(`${process.env.REACT_APP_API_ROOT}/auth/login`, form)
+ .then((response) => {
+ Cookies.set('token', response.data.token, {
+ expires: form.remember ? 3650 : 1,
+ });
+ Cookies.set('userID', response.data.userID, {
+ expires: form.remember ? 3650 : 1,
+ });
+ setToken(true);
+ navigate('/');
+ })
+ .catch((e) => setError(() => e.response.data));
+ }
+
+ return (
+
+ );
+}
+
+export default Login;
diff --git a/client/src/controllers/MainPage.jsx b/client/src/controllers/MainPage.jsx
new file mode 100644
index 0000000..4aff989
--- /dev/null
+++ b/client/src/controllers/MainPage.jsx
@@ -0,0 +1,101 @@
+import React, { useEffect, useState } from 'react';
+import { useNavigate } from 'react-router-dom';
+import axios from 'axios';
+import Cookies from 'js-cookie';
+
+import MainPageBase from '../components/MainPageBase';
+
+function MainPage() {
+ const navigate = useNavigate();
+
+ const [ownedOrgs, setOwnedOrgs] = useState([]);
+ const [orgs, setOrgs] = useState([]);
+ const [form, setForm] = useState({
+ name: '',
+ id: '',
+ });
+
+ useEffect(() => {
+ if (Cookies.get('token')) {
+ getMemberOrgs();
+ getOwnedOrgs();
+ } else {
+ navigate('/login');
+ }
+ }, [navigate]);
+
+ function handleChange(e) {
+ const name = e.target.name;
+ const value = e.target.value;
+
+ setForm((form) => {
+ return { ...form, [name]: value };
+ });
+ }
+
+ async function getOwnedOrgs() {
+ await axios
+ .get(
+ `${process.env.REACT_APP_API_ROOT}/auth?token=${Cookies.get(
+ 'token'
+ )}`
+ )
+ .then((response) => {
+ setOwnedOrgs(() => response.data);
+ })
+ .catch((e) => {
+ console.log(e);
+ });
+ }
+
+ async function getMemberOrgs() {
+ await axios
+ .get(
+ `${
+ process.env.REACT_APP_API_ROOT
+ }/auth/orgs?token=${Cookies.get('token')}`
+ )
+ .then((response) => {
+ setOrgs(() => response.data);
+ });
+ }
+
+ async function createOrg(e) {
+ e.preventDefault();
+ console.log('create');
+
+ await axios
+ .post(`${process.env.REACT_APP_API_ROOT}/org/create`, {
+ token: Cookies.get('token'),
+ orgName: form.name,
+ })
+ .then(() => {
+ getOwnedOrgs();
+ });
+ }
+
+ async function joinOrg(e) {
+ e.preventDefault();
+
+ await axios
+ .post(`${process.env.REACT_APP_API_ROOT}/org/${form.id}/add`, {
+ token: Cookies.get('token'),
+ })
+ .then(() => {
+ getMemberOrgs();
+ });
+ }
+
+ return (
+
+ );
+}
+
+export default MainPage;
diff --git a/client/src/components/orgs/MemberOrgs.jsx b/client/src/controllers/MemberOrgs.jsx
similarity index 61%
rename from client/src/components/orgs/MemberOrgs.jsx
rename to client/src/controllers/MemberOrgs.jsx
index cfd10fb..17a88e6 100644
--- a/client/src/components/orgs/MemberOrgs.jsx
+++ b/client/src/controllers/MemberOrgs.jsx
@@ -1,12 +1,13 @@
-import React from 'react';
-
+import React, { useEffect, useState } from 'react';
import axios from 'axios';
import Cookies from 'js-cookie';
-import Organizations from '../utils/Organizations';
+import Organizations from '../components/Organizations';
function JoinedOrgs() {
- async function getOrgs(setOrgs) {
+ const [orgs, setOrgs] = useState([]);
+
+ async function getOrgs() {
axios
.get(
`${
@@ -18,7 +19,9 @@ function JoinedOrgs() {
});
}
- return ;
+ useEffect(getOrgs);
+
+ return ;
}
export default JoinedOrgs;
diff --git a/client/src/controllers/OrgDashboard.jsx b/client/src/controllers/OrgDashboard.jsx
new file mode 100644
index 0000000..ca29fff
--- /dev/null
+++ b/client/src/controllers/OrgDashboard.jsx
@@ -0,0 +1,136 @@
+import React, { useEffect, useState } from 'react';
+import { useNavigate, useParams } from 'react-router-dom';
+import axios from 'axios';
+import Cookies from 'js-cookie';
+
+import Dashboard from '../components/Dashboard';
+
+function OrgDashboard() {
+ const { orgID } = useParams();
+ const navigate = useNavigate();
+
+ const [org, setOrg] = useState({
+ id: orgID,
+ name: '',
+ owner: {
+ firstName: '',
+ lastName: '',
+ email: '',
+ },
+ member: [],
+ createdAt: '',
+ memberCount: 0,
+ });
+ const [rows, setRows] = useState([]);
+ const [status, setStatus] = useState(true);
+ const [selection, setSelection] = useState(null);
+
+ useEffect(() => {
+ async function getData() {
+ await axios
+ .get(
+ `${
+ process.env.REACT_APP_API_ROOT
+ }/org/${orgID}?token=${Cookies.get('token')}`
+ )
+ .then((response) => {
+ const res = response.data;
+
+ setOrg(() => {
+ return { ...res.org, memberCount: res.memberCount };
+ });
+
+ setRows(() => res.org.member);
+ setStatus(() => res.status);
+ })
+ .catch((e) => {
+ console.log(e);
+ navigate('/');
+ });
+ }
+
+ getData();
+ }, [navigate, orgID]);
+
+ async function handleDelete() {
+ await axios
+ .delete(`${process.env.REACT_APP_API_ROOT}/org/delete`, {
+ params: {
+ token: Cookies.get('token'),
+ orgID,
+ },
+ })
+ .then(navigate('/'));
+ }
+
+ function copyID() {
+ navigator.clipboard.writeText(orgID);
+ }
+
+ function copyJoinLink() {
+ navigator.clipboard.writeText(
+ `${process.env.REACT_APP_DOMAIN_ROOT}/register?orgID=${orgID}`
+ );
+ }
+
+ async function removeSelected() {
+ const doomedUserIDs = selection.map((row) => row.id);
+
+ await axios.post(
+ `${process.env.REACT_APP_API_ROOT}/org/${orgID}/delete`,
+ {
+ token: Cookies.get('token'),
+ orgID,
+ doomedUserIDs,
+ }
+ );
+ }
+
+ async function leaveOrg() {
+ await axios
+ .post(`${process.env.REACT_APP_API_ROOT}/org/${orgID}/delete`, {
+ token: Cookies.get('token'),
+ orgID,
+ })
+ .then(() => navigate('/'))
+ .catch((e) => {
+ console.log(e);
+ navigate('/');
+ });
+ }
+
+ async function play(id) {
+ await axios
+ .get(
+ `${process.env.REACT_APP_DOMAIN_ROOT}/public/audio/${id}.mp3`,
+ {
+ responseType: 'blob',
+ }
+ )
+ .then((res) => {
+ const uploadedFile = new File([res.data], 'userAudio.mp3', {
+ type: res.data.type,
+ lastModified: Date.now(),
+ });
+ const audioFile = new Audio(URL.createObjectURL(uploadedFile));
+ audioFile.play();
+ });
+ }
+
+ return (
+
+ );
+}
+
+export default OrgDashboard;
diff --git a/client/src/components/orgs/OwnedOrgs.jsx b/client/src/controllers/OwnedOrgs.jsx
similarity index 61%
rename from client/src/components/orgs/OwnedOrgs.jsx
rename to client/src/controllers/OwnedOrgs.jsx
index a19607a..a1e3a42 100644
--- a/client/src/components/orgs/OwnedOrgs.jsx
+++ b/client/src/controllers/OwnedOrgs.jsx
@@ -1,12 +1,13 @@
-import React from 'react';
-
+import React, { useEffect, useState } from 'react';
import axios from 'axios';
import Cookies from 'js-cookie';
-import Organizations from '../utils/Organizations';
+import Organizations from '../components/Organizations';
function JoinedOrgs() {
- async function getOrgs(setOrgs) {
+ const [orgs, setOrgs] = useState([]);
+
+ async function getOrgs() {
axios
.get(
`${process.env.REACT_APP_API_ROOT}/auth?token=${Cookies.get(
@@ -18,7 +19,9 @@ function JoinedOrgs() {
});
}
- return ;
+ useEffect(getOrgs);
+
+ return ;
}
export default JoinedOrgs;
diff --git a/client/src/controllers/Recover.jsx b/client/src/controllers/Recover.jsx
new file mode 100644
index 0000000..01a3d23
--- /dev/null
+++ b/client/src/controllers/Recover.jsx
@@ -0,0 +1,45 @@
+import React, { useState } from 'react';
+import axios from 'axios';
+
+import RecoverForm from '../components/RecoverForm';
+
+function Recover() {
+ const [form, setForm] = useState({
+ email: '',
+ });
+ const [error, setError] = useState('');
+ const [success, setSuccess] = useState('');
+
+ function handleChange(e) {
+ const name = e.target.name;
+ const value = e.target.value;
+
+ setForm((form) => {
+ return { ...form, [name]: value };
+ });
+ }
+
+ async function handleSubmit(e) {
+ e.preventDefault();
+ await axios
+ .get(
+ `${process.env.REACT_APP_API_ROOT}/auth/reset-password?${form.email}`
+ )
+ .then((response) => {
+ setSuccess(() => response.data);
+ })
+ .catch((e) => setError(() => e.response.data));
+ }
+
+ return (
+
+ );
+}
+
+export default Recover;
diff --git a/client/src/controllers/Register.jsx b/client/src/controllers/Register.jsx
new file mode 100644
index 0000000..f946069
--- /dev/null
+++ b/client/src/controllers/Register.jsx
@@ -0,0 +1,55 @@
+import React, { useState } from 'react';
+import { useNavigate, useSearchParams } from 'react-router-dom';
+import axios from 'axios';
+
+import RegisterForm from '../components/RegisterForm';
+
+function Register() {
+ const navigate = useNavigate();
+ const [params] = useSearchParams();
+
+ const [form, setForm] = useState({
+ firstName: '',
+ lastName: '',
+ email: '',
+ pronouns: '',
+ password: '',
+ confirmPassword: '',
+ });
+ const [error, setError] = useState('');
+
+ function handleChange(e) {
+ const name = e.target.name;
+ const value = e.target.value;
+
+ setForm((form) => {
+ return { ...form, [name]: value };
+ });
+ }
+
+ async function handleSubmit(e) {
+ e.preventDefault();
+
+ const { confirmPassword, ...filteredForm } = form;
+ await axios
+ .post(`${process.env.REACT_APP_API_ROOT}/auth/register`, {
+ ...filteredForm,
+ orgID: params.orgID,
+ })
+ .then(() => {
+ navigate('/login');
+ })
+ .catch((e) => setError(() => e.response.data));
+ }
+
+ return (
+
+ );
+}
+
+export default Register;
diff --git a/client/src/controllers/Reset.jsx b/client/src/controllers/Reset.jsx
new file mode 100644
index 0000000..98631bf
--- /dev/null
+++ b/client/src/controllers/Reset.jsx
@@ -0,0 +1,51 @@
+import React, { useState } from 'react';
+import { useNavigate, useParams } from 'react-router-dom';
+import axios from 'axios';
+
+import ResetForm from '../components/ResetForm';
+
+function Reset() {
+ const navigate = useNavigate();
+ const { id } = useParams();
+
+ const [form, setForm] = useState({
+ password: '',
+ confirmPassword: '',
+ });
+ const [error, setError] = useState('');
+
+ function handleChange(e) {
+ const name = e.target.name;
+ const value = e.target.value;
+
+ setForm((form) => {
+ return { ...form, [name]: value };
+ });
+ }
+
+ async function handleSubmit(e) {
+ e.preventDefault();
+ await axios
+ .patch(
+ `${process.env.REACT_APP_API_ROOT}/auth/reset-password/${id}`,
+ {
+ password: form.password,
+ }
+ )
+ .then(() => {
+ navigate('/login');
+ })
+ .catch((e) => setError(() => e.response.data));
+ }
+
+ return (
+
+ );
+}
+
+export default Reset;
diff --git a/client/src/controllers/UpdateOrganization.jsx b/client/src/controllers/UpdateOrganization.jsx
new file mode 100644
index 0000000..219d3b6
--- /dev/null
+++ b/client/src/controllers/UpdateOrganization.jsx
@@ -0,0 +1,42 @@
+import React, { useState } from 'react';
+import { useNavigate, useParams } from 'react-router-dom';
+import axios from 'axios';
+import Cookies from 'js-cookie';
+
+import UpdateOrganizationForm from '../components/UpdateOrganizationForm';
+
+function UpdateOrganization() {
+ const { orgID } = useParams();
+ const navigate = useNavigate();
+
+ const [name, setName] = useState('');
+
+ function handleChange(e) {
+ const value = e.target.value;
+
+ setName(() => value);
+ }
+
+ async function handleSubmit(e) {
+ e.preventDefault();
+ await axios
+ .patch(`${process.env.REACT_APP_API_ROOT}/org/update`, {
+ token: Cookies.get('token'),
+ orgName: name,
+ orgID,
+ })
+ .then(() => {
+ navigate(`/org/${orgID}`);
+ });
+ }
+
+ return (
+
+ );
+}
+
+export default UpdateOrganization;