Added typescript and refactoring
This commit is contained in:
parent
17590cc43d
commit
db1e41b949
158 changed files with 10769 additions and 20499 deletions
|
|
@ -1,2 +0,0 @@
|
|||
node_modules
|
||||
.env
|
||||
12
.eslintrc.yml
Normal file
12
.eslintrc.yml
Normal 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
25
client/.eslintrc.yml
Normal 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
41
client/.gitignore
vendored
|
|
@ -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
13
client/index.html
Normal 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>
|
||||
|
|
@ -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 |
|
|
@ -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>
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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
56
client/src/App.tsx
Normal 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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
80
client/src/components/EditUserForm.tsx
Normal file
80
client/src/components/EditUserForm.tsx
Normal 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;
|
||||
|
|
@ -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>
|
||||
|
|
@ -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;
|
||||
77
client/src/components/LoginForm.tsx
Normal file
77
client/src/components/LoginForm.tsx
Normal 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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
57
client/src/components/MemberUnit.tsx
Normal file
57
client/src/components/MemberUnit.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
125
client/src/components/MobileNavbar.tsx
Normal file
125
client/src/components/MobileNavbar.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
88
client/src/components/Navbar.tsx
Normal file
88
client/src/components/Navbar.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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;
|
||||
44
client/src/components/OrgUnit.tsx
Normal file
44
client/src/components/OrgUnit.tsx
Normal 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;
|
||||
|
|
@ -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;
|
||||
35
client/src/components/OrganizationList.tsx
Normal file
35
client/src/components/OrganizationList.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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;
|
||||
47
client/src/components/RecordMp3.tsx
Normal file
47
client/src/components/RecordMp3.tsx
Normal 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;
|
||||
|
|
@ -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;
|
||||
37
client/src/components/RecoverForm.tsx
Normal file
37
client/src/components/RecoverForm.tsx
Normal 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;
|
||||
|
|
@ -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;
|
||||
90
client/src/components/RegisterForm.tsx
Normal file
90
client/src/components/RegisterForm.tsx
Normal 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;
|
||||
|
|
@ -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;
|
||||
51
client/src/components/ResetForm.tsx
Normal file
51
client/src/components/ResetForm.tsx
Normal 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;
|
||||
|
|
@ -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;
|
||||
26
client/src/components/StackItem.tsx
Normal file
26
client/src/components/StackItem.tsx
Normal 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;
|
||||
|
|
@ -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 };
|
||||
119
client/src/components/StyledElements.tsx
Normal file
119
client/src/components/StyledElements.tsx
Normal 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,
|
||||
};
|
||||
|
|
@ -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;
|
||||
31
client/src/components/UpdateOrganizationForm.tsx
Normal file
31
client/src/components/UpdateOrganizationForm.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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
|
||||
};
|
||||
27
client/src/components/index.ts
Normal file
27
client/src/components/index.ts
Normal 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,
|
||||
};
|
||||
|
|
@ -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 (
|
||||
|
|
@ -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>
|
||||
|
|
@ -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 (
|
||||
|
|
@ -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;
|
||||
14
client/src/containers/MainPage.tsx
Normal file
14
client/src/containers/MainPage.tsx
Normal 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 />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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 (
|
||||
|
|
@ -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 (
|
||||
|
|
@ -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 (
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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
|
||||
};
|
||||
25
client/src/containers/index.ts
Normal file
25
client/src/containers/index.ts
Normal 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,
|
||||
};
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
import { ErrorProvider, ModeProvider, UserProvider } from '.';
|
||||
|
||||
export default function ContextProvider({ children }) {
|
||||
return (
|
||||
<ModeProvider>
|
||||
<ErrorProvider>
|
||||
<UserProvider>{children}</UserProvider>
|
||||
</ErrorProvider>
|
||||
</ModeProvider>
|
||||
);
|
||||
}
|
||||
19
client/src/contexts/ContextProvider.tsx
Normal file
19
client/src/contexts/ContextProvider.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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);
|
||||
20
client/src/contexts/ErrorContext.ts
Normal file
20
client/src/contexts/ErrorContext.ts
Normal 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;
|
||||
};
|
||||
22
client/src/contexts/ErrorProvider.tsx
Normal file
22
client/src/contexts/ErrorProvider.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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);
|
||||
22
client/src/contexts/ModeContext.ts
Normal file
22
client/src/contexts/ModeContext.ts
Normal 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;
|
||||
};
|
||||
27
client/src/contexts/ModeProvider.tsx
Normal file
27
client/src/contexts/ModeProvider.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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);
|
||||
23
client/src/contexts/UserContext.ts
Normal file
23
client/src/contexts/UserContext.ts
Normal 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;
|
||||
};
|
||||
63
client/src/contexts/UserProvider.tsx
Normal file
63
client/src/contexts/UserProvider.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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 };
|
||||
8
client/src/contexts/index.ts
Normal file
8
client/src/contexts/index.ts
Normal 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,
|
||||
};
|
||||
113
client/src/controllers/Dashboard.tsx
Normal file
113
client/src/controllers/Dashboard.tsx
Normal 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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -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;
|
||||
67
client/src/controllers/EditUserForm.tsx
Normal file
67
client/src/controllers/EditUserForm.tsx
Normal 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;
|
||||
|
|
@ -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;
|
||||
89
client/src/controllers/LoginForm.tsx
Normal file
89
client/src/controllers/LoginForm.tsx
Normal 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;
|
||||
|
|
@ -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;
|
||||
70
client/src/controllers/MainPage.tsx
Normal file
70
client/src/controllers/MainPage.tsx
Normal 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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
21
client/src/controllers/MemberOrgsDashboard.tsx
Normal file
21
client/src/controllers/MemberOrgsDashboard.tsx
Normal 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} />;
|
||||
}
|
||||
61
client/src/controllers/Navbar.tsx
Normal file
61
client/src/controllers/Navbar.tsx
Normal 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,
|
||||
});
|
||||
}
|
||||
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
|
|
@ -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;
|
||||
21
client/src/controllers/OwnedOrgsDashboard.tsx
Normal file
21
client/src/controllers/OwnedOrgsDashboard.tsx
Normal 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} />;
|
||||
}
|
||||
103
client/src/controllers/RecordMp3.jsx
Normal file
103
client/src/controllers/RecordMp3.jsx
Normal 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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -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;
|
||||
52
client/src/controllers/RecoverForm.tsx
Normal file
52
client/src/controllers/RecoverForm.tsx
Normal 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;
|
||||
|
|
@ -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;
|
||||
65
client/src/controllers/RegisterForm.tsx
Normal file
65
client/src/controllers/RegisterForm.tsx
Normal 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;
|
||||
|
|
@ -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;
|
||||
47
client/src/controllers/ResetForm.tsx
Normal file
47
client/src/controllers/ResetForm.tsx
Normal 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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -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;
|
||||
39
client/src/controllers/UpdateOrganizationForm.tsx
Normal file
39
client/src/controllers/UpdateOrganizationForm.tsx
Normal 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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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,
|
||||
};
|
||||
25
client/src/controllers/index.ts
Normal file
25
client/src/controllers/index.ts
Normal 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
8
client/src/environment.d.ts
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
declare global {
|
||||
namespace NodeJS {
|
||||
interface ProcessEnv {
|
||||
REACT_APP_DOMAIN_ROOT: string;
|
||||
REACT_APP_API_ROOT: string;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
9
client/src/main.jsx
Normal 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
Reference in a new issue