From 6d7d0fc216c03f78b67f259f25c24659300b790f Mon Sep 17 00:00:00 2001 From: C4 Patino Date: Thu, 15 Dec 2022 13:42:44 -0600 Subject: [PATCH] Part 1 of code refactor --- client/Dockerfile | 9 ++ client/package.json | 6 +- client/src/App.jsx | 40 +++--- client/src/components/LoginForm.jsx | 13 +- client/src/components/MobileNavbar.jsx | 91 +++++++++++++ client/src/components/Navbar.jsx | 141 +++++++------------- client/src/components/RegisterForm.jsx | 7 +- client/src/components/StyledElements.jsx | 10 +- client/src/components/index.js | 4 + client/src/containers/Layout.jsx | 39 ++++++ client/src/containers/index.js | 3 + client/src/contexts/ContextProvider.jsx | 11 ++ client/src/contexts/ErrorContext.jsx | 11 ++ client/src/contexts/ModeContext.jsx | 16 +++ client/src/contexts/UserContext.jsx | 38 ++++++ client/src/contexts/index.js | 6 + client/src/controllers/NavbarController.jsx | 44 ++++++ client/src/controllers/index.js | 3 + client/src/services/orgServices.js | 10 ++ client/src/services/requests.js | 13 ++ client/src/services/userServices.js | 94 +++++++++++++ 21 files changed, 465 insertions(+), 144 deletions(-) create mode 100644 client/Dockerfile create mode 100644 client/src/components/MobileNavbar.jsx create mode 100644 client/src/components/index.js create mode 100644 client/src/containers/Layout.jsx create mode 100644 client/src/containers/index.js create mode 100644 client/src/contexts/ContextProvider.jsx create mode 100644 client/src/contexts/ErrorContext.jsx create mode 100644 client/src/contexts/ModeContext.jsx create mode 100644 client/src/contexts/UserContext.jsx create mode 100644 client/src/contexts/index.js create mode 100644 client/src/controllers/NavbarController.jsx create mode 100644 client/src/controllers/index.js create mode 100644 client/src/services/orgServices.js create mode 100644 client/src/services/requests.js create mode 100644 client/src/services/userServices.js diff --git a/client/Dockerfile b/client/Dockerfile new file mode 100644 index 0000000..5eb3f5c --- /dev/null +++ b/client/Dockerfile @@ -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 diff --git a/client/package.json b/client/package.json index 596b962..638f0bf 100644 --- a/client/package.json +++ b/client/package.json @@ -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": [ diff --git a/client/src/App.jsx b/client/src/App.jsx index a45a0cc..a6678ff 100644 --- a/client/src/App.jsx +++ b/client/src/App.jsx @@ -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 ( - - + + - }> - } /> + }> + {/* } /> */} - } - /> - } /> - } /> - }> + } /> + } /> + {/* } /> */} + {/* }> } /> } /> - + */} - + {/* } /> } /> } /> - } - /> + } /> - + */} } /> - - + + ); } diff --git a/client/src/components/LoginForm.jsx b/client/src/components/LoginForm.jsx index b19e76f..c75119a 100644 --- a/client/src/components/LoginForm.jsx +++ b/client/src/components/LoginForm.jsx @@ -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 (
- + Login Register - Forgot your password?{' '} - Recover Password + Forgot your password? Recover Password
diff --git a/client/src/components/MobileNavbar.jsx b/client/src/components/MobileNavbar.jsx new file mode 100644 index 0000000..ba230d0 --- /dev/null +++ b/client/src/components/MobileNavbar.jsx @@ -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 ? ( + + + + + + Logout + + + ) : ( + <> + + + + + + Login + + + + + + + + Register + + + + ); + } + + return ( + + + + + + + KnowItAll + + + + + + + + + + + + {mode === 'light' ? : } + + + Switch to {mode === 'light' ? 'dark' : 'light'} mode + + + + + + + ); +} diff --git a/client/src/components/Navbar.jsx b/client/src/components/Navbar.jsx index aac79dc..93b98f1 100644 --- a/client/src/components/Navbar.jsx +++ b/client/src/components/Navbar.jsx @@ -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 ( - - - - - ); - } else { - return ( - - - - - ); - } +export default function Navbar({ logout, userData, mode, toggleMode }) { + function NavbarControls() { + return userData.username ? ( + + ) : ( + <> + + + + ); } - return ( - - - - - - - - - - - - KnowItAll - - - - - - - - + + + + + + + KnowItAll + + + + {mode === 'light' ? : } + + + + ); } - -export default Navbar; diff --git a/client/src/components/RegisterForm.jsx b/client/src/components/RegisterForm.jsx index c54e459..80ed09b 100644 --- a/client/src/components/RegisterForm.jsx +++ b/client/src/components/RegisterForm.jsx @@ -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} /> - - { ); }; -export { - Form, - FormField, - FormButton, - DynamicStack, - DynamicPaper, - FormSubmit, - FormTextField, -}; +export { Form, FormField, FormButton, DynamicStack, DynamicPaper, FormSubmit, FormTextField }; diff --git a/client/src/components/index.js b/client/src/components/index.js new file mode 100644 index 0000000..bf0d334 --- /dev/null +++ b/client/src/components/index.js @@ -0,0 +1,4 @@ +import Navbar from './Navbar'; +import MobileNavbar from './MobileNavbar'; + +export { Navbar, MobileNavbar }; diff --git a/client/src/containers/Layout.jsx b/client/src/containers/Layout.jsx new file mode 100644 index 0000000..200ef24 --- /dev/null +++ b/client/src/containers/Layout.jsx @@ -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 ( + + + + KnowItAll + +
+ + + + +
+
+ ); +} + +export default Layout; diff --git a/client/src/containers/index.js b/client/src/containers/index.js new file mode 100644 index 0000000..d7314f6 --- /dev/null +++ b/client/src/containers/index.js @@ -0,0 +1,3 @@ +import Layout from './Layout'; + +export { Layout }; diff --git a/client/src/contexts/ContextProvider.jsx b/client/src/contexts/ContextProvider.jsx new file mode 100644 index 0000000..fc348fd --- /dev/null +++ b/client/src/contexts/ContextProvider.jsx @@ -0,0 +1,11 @@ +import { ErrorProvider, ModeProvider, UserProvider } from '.'; + +export default function ContextProvider({ children }) { + return ( + + + {children} + + + ); +} diff --git a/client/src/contexts/ErrorContext.jsx b/client/src/contexts/ErrorContext.jsx new file mode 100644 index 0000000..8496c62 --- /dev/null +++ b/client/src/contexts/ErrorContext.jsx @@ -0,0 +1,11 @@ +import { createContext, useContext, useState } from 'react'; + +const ErrorContext = createContext(); + +export default function ModeProvider({ children }) { + const [error, setError] = useState(''); + + return {children}; +} + +export const useError = () => useContext(ErrorContext); diff --git a/client/src/contexts/ModeContext.jsx b/client/src/contexts/ModeContext.jsx new file mode 100644 index 0000000..f25582c --- /dev/null +++ b/client/src/contexts/ModeContext.jsx @@ -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 {children}; +} + +export const useMode = () => useContext(ModeContext); diff --git a/client/src/contexts/UserContext.jsx b/client/src/contexts/UserContext.jsx new file mode 100644 index 0000000..3d4aa11 --- /dev/null +++ b/client/src/contexts/UserContext.jsx @@ -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 ( + + {children} + + ); +} + +export const useUser = () => useContext(UserContext); diff --git a/client/src/contexts/index.js b/client/src/contexts/index.js new file mode 100644 index 0000000..4a92329 --- /dev/null +++ b/client/src/contexts/index.js @@ -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 }; diff --git a/client/src/controllers/NavbarController.jsx b/client/src/controllers/NavbarController.jsx new file mode 100644 index 0000000..180336c --- /dev/null +++ b/client/src/controllers/NavbarController.jsx @@ -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, + }); +} diff --git a/client/src/controllers/index.js b/client/src/controllers/index.js new file mode 100644 index 0000000..1db7579 --- /dev/null +++ b/client/src/controllers/index.js @@ -0,0 +1,3 @@ +import Navbar from './NavbarController'; + +export { Navbar }; diff --git a/client/src/services/orgServices.js b/client/src/services/orgServices.js new file mode 100644 index 0000000..fe489ed --- /dev/null +++ b/client/src/services/orgServices.js @@ -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() {} diff --git a/client/src/services/requests.js b/client/src/services/requests.js new file mode 100644 index 0000000..1d3d9ac --- /dev/null +++ b/client/src/services/requests.js @@ -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 }; diff --git a/client/src/services/userServices.js b/client/src/services/userServices.js new file mode 100644 index 0000000..e895103 --- /dev/null +++ b/client/src/services/userServices.js @@ -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 };