Part 1 of code refactor

This commit is contained in:
Ceferino Patino 2022-12-15 13:42:44 -06:00
commit 6d7d0fc216
21 changed files with 466 additions and 145 deletions

9
client/Dockerfile Normal file
View file

@ -0,0 +1,9 @@
# syntax=docker/dockerfile:1
FROM node:16-alpine
WORKDIR /app
COPY ["package.json", "./"]
RUN apk update
RUN npm install
COPY . .
EXPOSE 3000/tcp
CMD npm start

View file

@ -12,9 +12,11 @@
"@testing-library/react": "^12.1.3",
"@testing-library/user-event": "^13.5.0",
"axios": "^0.26.1",
"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",
"web-vitals": "^2.1.4"
@ -23,9 +25,7 @@
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"env": "cp ../.env .env",
"env:remove": "rm -f .env"
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [

View file

@ -1,7 +1,7 @@
import React, { useState } from 'react';
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
import Navbar from './components/Navbar';
import { Layout } from './containers';
import { ContextProvider } from './contexts';
import MainPage from './controllers/MainPage';
import Form from './components/Form';
@ -17,43 +17,35 @@ import OrgDashboard from './controllers/OrgDashboard';
import UpdateOrganization from './controllers/UpdateOrganization';
function App() {
const [token, setToken] = useState(true);
return (
<React.Fragment>
<BrowserRouter>
<BrowserRouter>
<ContextProvider>
<Routes>
<Route path='' element={<Navbar token={token} />}>
<Route index element={<MainPage />} />
<Route path='' element={<Layout />}>
{/* <Route index element={<MainPage />} /> */}
<Route
path='login'
element={<Login setToken={setToken} />}
/>
<Route path='register' element={<Register />} />
<Route path='update' element={<EditUser />} />
<Route path='recover' element={<Form />}>
<Route path='/login' element={<Login />} />
<Route path='/register' element={<Register />} />
{/* <Route path='update' element={<EditUser />} /> */}
{/* <Route path='recover' element={<Form />}>
<Route index element={<Recover />} />
<Route path=':id' element={<Reset />} />
</Route>
</Route> */}
<Route path='org'>
{/* <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 path='update' element={<UpdateOrganization />} />
</Route>
</Route>
</Route> */}
<Route path='*' element={<Navigate to='' />} />
</Route>
</Routes>
</BrowserRouter>
</React.Fragment>
</ContextProvider>
</BrowserRouter>
);
}

View file

@ -5,17 +5,11 @@ import { Box, Checkbox, FormControlLabel, Typography } from '@mui/material';
import Form from './Form';
import { FormSubmit, FormTextField } from './StyledElements';
function Login({
form,
error,
handleSubmit,
handleChange,
handleCheckboxChange,
}) {
function Login({ form, error, handleSubmit, handleChange, handleCheckboxChange }) {
return (
<Form>
<form onSubmit={handleSubmit}>
<Typography variant='h4' sx={{ marginTop: '1vh 0vh' }}>
<Typography variant='h5' sx={{ margin: '1vh 0vh', fontWeight: 'bold' }}>
Login
</Typography>
<FormTextField
@ -62,8 +56,7 @@ function Login({
Dont have an account? <Link to='/register'>Register</Link>
</Typography>
<Typography variant='body1'>
Forgot your password?{' '}
<Link to='/recover'>Recover Password</Link>
Forgot your password? <Link to='/recover'>Recover Password</Link>
</Typography>
</form>
</Form>

View file

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

View file

@ -1,100 +1,53 @@
import React from 'react';
import { Link, Outlet } from 'react-router-dom';
import {
AppBar,
Button,
CssBaseline,
IconButton,
Toolbar,
Typography,
useMediaQuery,
} from '@mui/material';
import { createTheme, ThemeProvider } from '@mui/material/styles';
import DarkModeIcon from '@mui/icons-material/DarkMode';
import LightModeIcon from '@mui/icons-material/LightMode';
import MailIcon from '@mui/icons-material/Mail';
import MenuIcon from '@mui/icons-material/Menu';
import { AppBar, Box, Button, IconButton, Toolbar, Typography } from '@mui/material';
import { Link } from 'react-router-dom';
function Navbar({ token }) {
const linkStyle = { textDecoration: 'none', color: 'inherit' };
const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)');
const darkTheme = createTheme({
palette: {
mode: prefersDarkMode ? 'dark' : 'light',
},
breakpoints: {
values: {
xs: 0,
sm: 768,
md: 1024,
},
},
});
function ButtonGroup({ token }) {
if (token) {
return (
<React.Fragment>
<Button color='inherit'>
<Link to='/update' style={linkStyle}>
Account
</Link>
</Button>
<Button color='inherit'>
<Link to='/login' style={linkStyle}>
Logout
</Link>
</Button>
</React.Fragment>
);
} else {
return (
<React.Fragment>
<Button color='inherit'>
<Link to='/login' style={linkStyle}>
Login
</Link>
</Button>
<Button color='inherit'>
<Link to='/register' style={linkStyle}>
Register
</Link>
</Button>
</React.Fragment>
);
}
export default function Navbar({ logout, userData, mode, toggleMode }) {
function NavbarControls() {
return userData.username ? (
<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 (
<React.Fragment>
<ThemeProvider theme={darkTheme}>
<CssBaseline />
<AppBar position='static'>
<Toolbar>
<IconButton
size='large'
edge='start'
color='inherit'
aria-label='menu'
sx={{ mr: 2 }}
>
<Link to='/' style={linkStyle}>
<MenuIcon />
</Link>
</IconButton>
<Typography
variant='h6'
component='div'
sx={{ flexGrow: 1 }}
>
KnowItAll
</Typography>
<ButtonGroup token={token} />
</Toolbar>
</AppBar>
<Outlet />
</ThemeProvider>
</React.Fragment>
<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>
);
}
export default Navbar;

View file

@ -1,6 +1,5 @@
import React from 'react';
import { Link } from 'react-router-dom';
import { Box, TextField, Typography } from '@mui/material';
import { Box, Typography } from '@mui/material';
import Form from './Form';
import { FormSubmit, FormTextField } from './StyledElements';
@ -37,7 +36,7 @@ function RegisterForm({ form, handleSubmit, handleChange, error }) {
onChange={handleChange}
value={form.pronouns}
/>
<TextField
<FormTextField
required
label='Password'
name='password'
@ -45,7 +44,7 @@ function RegisterForm({ form, handleSubmit, handleChange, error }) {
value={form.password}
other={{ type: 'password' }}
/>
<TextField
<FormTextField
required
label='Confirm Password'
name='confirmPassword'

View file

@ -77,12 +77,4 @@ const FormSubmit = ({ color, children, type, other }) => {
);
};
export {
Form,
FormField,
FormButton,
DynamicStack,
DynamicPaper,
FormSubmit,
FormTextField,
};
export { Form, FormField, FormButton, DynamicStack, DynamicPaper, FormSubmit, FormTextField };

View file

@ -0,0 +1,4 @@
import Navbar from './Navbar';
import MobileNavbar from './MobileNavbar';
export { Navbar, MobileNavbar };

View file

@ -0,0 +1,39 @@
import { Box, CssBaseline } from '@mui/material';
import { createTheme, ThemeProvider } from '@mui/material/styles';
import { Helmet } from 'react-helmet';
import { Outlet } from 'react-router-dom';
import { useMode } from '../contexts';
import { Navbar } from '../controllers';
function Layout() {
const { mode } = useMode();
const theme = createTheme({
palette: { mode },
breakpoints: {
values: {
xs: 0,
sm: 768,
md: 1024,
},
},
});
return (
<ThemeProvider theme={theme}>
<CssBaseline enableColorScheme />
<Helmet>
<title>KnowItAll</title>
</Helmet>
<main>
<Navbar />
<Box sx={{ height: { xs: '', sm: '92vh', md: '92vh' } }}>
<Outlet />
</Box>
</main>
</ThemeProvider>
);
}
export default Layout;

View file

@ -0,0 +1,3 @@
import Layout from './Layout';
export { Layout };

View file

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

View file

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

View file

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

View file

@ -0,0 +1,38 @@
import { createContext, useContext, useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { remember } from '../services/userServices';
const initialUserData = { loading: true };
const UserContext = createContext();
export default function UserProvider({ children }) {
const [userData, setUserData] = useState(initialUserData);
const navigate = useNavigate();
useEffect(() => {
const loadUserData = async () => {
const { user, error } = await remember();
if (error) {
navigate('/login');
} else {
setUserData(user || { loading: false });
}
};
if (userData.loading) loadUserData();
}, [userData.loading]);
return (
<UserContext.Provider
value={{
userData,
setUserData,
}}
>
{children}
</UserContext.Provider>
);
}
export const useUser = () => useContext(UserContext);

View file

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

View file

@ -0,0 +1,44 @@
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 { logout } from '../services/userServices';
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 = () => {
console.log('Changed mode');
setMode(() => (mode === 'light' ? 'dark' : 'light'));
};
const sm = useMediaQuery((theme) => theme.breakpoints.only('sm'));
const md = useMediaQuery((theme) => theme.breakpoints.only('md'));
const logoutUser = async () => {
await logout();
Cookies.remove('token');
setUserData({});
navigate('/login');
};
return md || sm
? Navbar({ logout: logoutUser, userData, mode, toggleMode })
: MobileNavbar({
logout: logoutUser,
userData,
open,
setOpen: toggleOpen,
mode,
toggleMode,
});
}

View file

@ -0,0 +1,3 @@
import Navbar from './NavbarController';
export { Navbar };

View file

@ -0,0 +1,10 @@
import Cookies from 'js-cookie';
import { createRequest } from './requests';
async function addMember() {}
async function createOrg() {}
async function deleteOrg() {}
async function getOrg() {}
async function removeMember() {}
async function updateOrgDetails() {}

View file

@ -0,0 +1,13 @@
import axios from 'axios';
function createRequest(path) {
var request = axios.create({
baseURL: `${process.env.REACT_APP_API_ROOT}${path}`,
});
request.defaults.headers.common['Content-Type'] = 'application/json';
return request;
}
export { createRequest };

View file

@ -0,0 +1,94 @@
import Cookies from 'js-cookie';
import { createRequest } from './requests';
async function login({ username, password, remember }) {
const encoded = Buffer.from(`${username}:${password}`).toString('base64');
const instance = createRequest('/auth');
instance.defaults.headers.common['Authorization'] = `basic ${encoded}`;
try {
const { data } = await instance.post('/login', { remember });
if (remember) {
Cookies.set('token', data.token, { expires: 365 });
} else {
Cookies.set('token', data.token, { expires: 1 });
}
return data;
} catch (error) {
console.log(error.response.data);
return error;
}
}
async function register({ data }) {
const instance = createRequest('/auth');
try {
await instance.post('/register', { data });
} catch (error) {
console.log(error.response.data);
return error;
}
}
async function logout() {
const instance = createRequest('/auth');
instance.defaults.headers.common['Authorization'] = `bearer ${Cookies.get('token')}`;
try {
await instance.post('/logout');
} catch (error) {
console.log(error.response.data);
return error;
}
}
async function remember() {
const instance = createRequest('/auth');
instance.defaults.headers.common['Authorization'] = `bearer ${Cookies.get('token')}`;
if (Cookies.get('token')) {
try {
const { data } = await instance.post('/remember');
return data.user;
} catch (error) {
console.log(error.response.data);
return error.response.data;
}
}
}
async function getUser(userID) {
const instance = createRequest('/auth');
instance.defaults.headers.common['Authorization'] = `bearer ${Cookies.get('token')}`;
if (Cookies.get('token')) {
try {
const { data } = await instance.get(`/${userID}`);
return data.user;
} catch (error) {
console.log(error.response.data);
return error.response.data;
}
}
}
async function requestReset() {}
async function resetPassword() {}
async function getMemberOrgs() {}
async function getOwnedOrgs() {}
async function setAudio() {}
async function updateUserDetails() {}
export { login, logout, register, remember, getUser };