From 7cb89328d4dc1876d417e0243d86ea877f945c2e Mon Sep 17 00:00:00 2001 From: bluesimp1102 Date: Sun, 9 Jun 2024 13:26:49 -0500 Subject: [PATCH 1/8] fix: remove POST method requirement for redirecting --- mesh/auth/views.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/mesh/auth/views.py b/mesh/auth/views.py index 694f3657..e0b229f6 100644 --- a/mesh/auth/views.py +++ b/mesh/auth/views.py @@ -45,13 +45,17 @@ def login_view(request): except Exception as e: return JsonResponse({"error": str(e)}, status=500) -@require_POST def logout_view(request): """ - Handles user logout requests. + Handle user logout requests. + + This view handles POST requests to log out the user by ending their session. + After logging out, it redirects the user to the home page, with the URL + being fetched from the environment variable `WEB_URL`. If `WEB_URL` is not + set, it defaults to 'http://localhost:3000'. - This view expects a POST request, logs out the user by ending the session, and - redirects to the home page. + Args: + request (HttpRequest): The HTTP request object. Returns: HttpResponseRedirect: A response that redirects to the home page after successful logout. From 03fb7d7646ef4f7caef444f51c46044c7bca1943 Mon Sep 17 00:00:00 2001 From: bluesimp1102 Date: Sun, 9 Jun 2024 13:27:02 -0500 Subject: [PATCH 2/8] build: add react-toastify for notifications --- meshapp/package-lock.json | 21 +++++++++++++++++++++ meshapp/package.json | 1 + 2 files changed, 22 insertions(+) diff --git a/meshapp/package-lock.json b/meshapp/package-lock.json index e33db3a1..f81bc2f8 100644 --- a/meshapp/package-lock.json +++ b/meshapp/package-lock.json @@ -30,6 +30,7 @@ "react-router-dom": "^6.14.2", "react-scripts": "5.0.1", "react-swipeable": "^7.0.1", + "react-toastify": "^10.0.5", "styled-components": "^5.3.11", "sweetalert2": "^11.7.12", "typescript": "^5.0.4", @@ -15094,6 +15095,26 @@ "react": "^16.8.3 || ^17 || ^18" } }, + "node_modules/react-toastify": { + "version": "10.0.5", + "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-10.0.5.tgz", + "integrity": "sha512-mNKt2jBXJg4O7pSdbNUfDdTsK9FIdikfsIE/yUCxbAEXl4HMyJaivrVFcn3Elvt5xvCQYhUZm+hqTIu1UXM3Pw==", + "dependencies": { + "clsx": "^2.1.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/react-toastify/node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "engines": { + "node": ">=6" + } + }, "node_modules/react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", diff --git a/meshapp/package.json b/meshapp/package.json index b2337d65..4d35b471 100644 --- a/meshapp/package.json +++ b/meshapp/package.json @@ -25,6 +25,7 @@ "react-router-dom": "^6.14.2", "react-scripts": "5.0.1", "react-swipeable": "^7.0.1", + "react-toastify": "^10.0.5", "styled-components": "^5.3.11", "sweetalert2": "^11.7.12", "typescript": "^5.0.4", From c8562a58a11e0cd56b0396c1756f2e7c927a930f Mon Sep 17 00:00:00 2001 From: bluesimp1102 Date: Sun, 9 Jun 2024 13:27:25 -0500 Subject: [PATCH 3/8] refactor: render App.tsx --- meshapp/src/App.tsx | 34 +++++++++++++++++----------------- meshapp/src/index.tsx | 21 ++------------------- 2 files changed, 19 insertions(+), 36 deletions(-) diff --git a/meshapp/src/App.tsx b/meshapp/src/App.tsx index e4378c60..2d34cb73 100644 --- a/meshapp/src/App.tsx +++ b/meshapp/src/App.tsx @@ -1,25 +1,25 @@ -import logo from "./logo.svg"; import "./App.css"; + +import { AccountProvider } from "./contexts/UserContext"; import React from "react"; +import { ToastContainer } from "react-toastify"; +import "react-toastify/dist/ReactToastify.css"; +import { ThemeContextProvider } from "./themes/ThemeContextProvider"; +import { MainThemeProvider } from "./themes/MainThemeProvider"; +import AppRoutes from "./routes/AppRoutes"; function App() { return ( -
-
- logo -

- Edit src/App.js and save to reload. -

- - Learn React - -
-
+ + + + + + + + + + ); } diff --git a/meshapp/src/index.tsx b/meshapp/src/index.tsx index 1e4631e1..c7e44e9c 100644 --- a/meshapp/src/index.tsx +++ b/meshapp/src/index.tsx @@ -1,26 +1,9 @@ -import React from "react"; import ReactDOM from "react-dom/client"; import "./index.css"; -import { ThemeContextProvider } from "src/themes/ThemeContextProvider"; -import { MainThemeProvider } from "./themes/MainThemeProvider"; -import TwoFactorAuthReminders from "./components/TwoFactorAuth/TwoFactorReminder"; -import ProfilePage from "./pages/Profiles/ProfilePage"; -import AppRoutes from "./routes/AppRoutes"; -import { exampleProfile } from "./pages/Profiles/tests/profile-examples"; - +import App from "./App"; const root = ReactDOM.createRoot(document.getElementById("root")!); -root.render( - - - - - - - - - -); +root.render(); // If you want to start measuring performance in your app, pass a function // to log results (for example: reportWebVitals(console.log)) // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals From 72569e574fe56aec081a3e54a3fb7ffaa38a5a14 Mon Sep 17 00:00:00 2001 From: bluesimp1102 Date: Sun, 9 Jun 2024 13:28:05 -0500 Subject: [PATCH 4/8] feat: user context to use across the site --- meshapp/src/contexts/UserContext.tsx | 66 ++++++++++++++++++++++++++++ meshapp/src/hooks/use-login.tsx | 28 ++++++++++++ meshapp/src/types/account.ts | 15 +++++++ meshapp/src/types/loading.ts | 1 + 4 files changed, 110 insertions(+) create mode 100644 meshapp/src/contexts/UserContext.tsx create mode 100644 meshapp/src/hooks/use-login.tsx create mode 100644 meshapp/src/types/account.ts create mode 100644 meshapp/src/types/loading.ts diff --git a/meshapp/src/contexts/UserContext.tsx b/meshapp/src/contexts/UserContext.tsx new file mode 100644 index 00000000..4fb29ab3 --- /dev/null +++ b/meshapp/src/contexts/UserContext.tsx @@ -0,0 +1,66 @@ +import React, { + createContext, + useState, + useContext, + useEffect, + ReactNode, +} from "react"; +import { axiosInstance } from "../config/axios-config"; +import { Account } from "../types/account"; +import { LoadingState } from "../types/loading"; + +interface AccountContextType { + account: Account | null; + updateAccount: React.Dispatch>; + loadingState: LoadingState; +} + +const AccountContext = createContext(undefined); + +interface AccountProviderProps { + children: React.ReactNode; +} + +export const AccountProvider: React.FC = ({ + children, +}) => { + const [account, setAccount] = useState(null); + const [loadingState, setLoadingState] = + useState("initializing"); + + useEffect(() => { + setLoadingState("loading"); + const checkSession = async () => { + try { + const response = await axiosInstance.get("/auth/session/"); + if (response.data.is_logged_in) { + setAccount(response.data.account); + } else { + setAccount(null); + } + } catch (error) { + console.error("Session check failed", error); + } finally { + setLoadingState("completed"); + } + }; + + checkSession(); + }, []); + + return ( + + {children} + + ); +}; + +export const useAccountContext = (): AccountContextType => { + const context = useContext(AccountContext); + if (context === undefined) { + throw new Error("useAccountContext must be used within an AccountProvider"); + } + return context; +}; diff --git a/meshapp/src/hooks/use-login.tsx b/meshapp/src/hooks/use-login.tsx new file mode 100644 index 00000000..0be13c11 --- /dev/null +++ b/meshapp/src/hooks/use-login.tsx @@ -0,0 +1,28 @@ +// hooks/useLogin.ts +import { useState } from "react"; +import { axiosInstance } from "src/config/axios-config"; + +export const useLogin = () => { + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + const login = async (email: String, password: String) => { + setIsLoading(true); + setError(null); + try { + const response = await axiosInstance.post("/auth/login/", { + email: email, + password: password, + }); + + setIsLoading(false); + return response; + } catch (err: any) { + setError(err.response?.data?.error || "An error occurred"); + setIsLoading(false); + return null; + } + }; + + return { login, isLoading, error }; +}; diff --git a/meshapp/src/types/account.ts b/meshapp/src/types/account.ts new file mode 100644 index 00000000..9ec9ab11 --- /dev/null +++ b/meshapp/src/types/account.ts @@ -0,0 +1,15 @@ +export interface AccountSettings { + isVerified: boolean; + hasContentFilterEnabled: boolean; + displayTheme: string; + is2FAEnabled: boolean; +} + +export interface Account { + accountID: number; + email: string; + phoneNum: string; + isMentor: boolean; + isMentee: boolean; + settings: AccountSettings; +} diff --git a/meshapp/src/types/loading.ts b/meshapp/src/types/loading.ts new file mode 100644 index 00000000..5c73df05 --- /dev/null +++ b/meshapp/src/types/loading.ts @@ -0,0 +1 @@ +export type LoadingState = "initializing" | "loading" | "completed"; From f1bf2ce42e693a435855bba14de5da6034790c51 Mon Sep 17 00:00:00 2001 From: bluesimp1102 Date: Sun, 9 Jun 2024 13:29:00 -0500 Subject: [PATCH 5/8] feat: login with new home routing & user context --- .../src/components/AuthForms/LoginForm.tsx | 260 ++++++++++-------- meshapp/src/components/svgs/discord.tsx | 28 ++ meshapp/src/components/svgs/google.tsx | 61 ++++ meshapp/src/pages/Login/index.tsx | 145 ++++++---- 4 files changed, 322 insertions(+), 172 deletions(-) create mode 100644 meshapp/src/components/svgs/discord.tsx create mode 100644 meshapp/src/components/svgs/google.tsx diff --git a/meshapp/src/components/AuthForms/LoginForm.tsx b/meshapp/src/components/AuthForms/LoginForm.tsx index 8d16c98c..b51908fe 100644 --- a/meshapp/src/components/AuthForms/LoginForm.tsx +++ b/meshapp/src/components/AuthForms/LoginForm.tsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; import { Button, Grid, @@ -6,21 +6,50 @@ import { Stack, Typography, TextField, + Divider, } from "@mui/material"; import { useForm } from "react-hook-form"; - -import { useNavigate } from "react-router-dom"; import { useCookies } from "react-cookie"; +import { useNavigate } from "react-router-dom"; +import { toast } from "react-toastify"; +import { GridContainer, GridItem } from "../ui/Grid"; +import LoadingProgress from "../ui/LoadingSpinner"; +import GoogleSvg from "../svgs/google"; +import DiscordSvg from "../svgs/discord"; import { axiosInstance } from "src/config/axios-config"; +import { useLogin } from "src/hooks/use-login"; +import { useAccountContext } from "src/contexts/UserContext"; import isError from "src/errors/error-checker"; import axiosErrorHandler from "src/errors/axios-error-handler"; +const LoginWindow = () => { + const [showForgotPassword, setShowForgotPassword] = useState(false); + + const updateShowForgotPasswordState = () => { + setShowForgotPassword((prevState) => !prevState); + }; + return showForgotPassword ? ( + + ) : ( + + ); +}; + +export default LoginWindow; + interface ComponentProps { updateShowForgotPasswordState: () => void; } const LoginScreen = (props: ComponentProps) => { const [formData, setFormData] = useState({ user: null, pass: null }); + const { login, isLoading, error } = useLogin(); + const { updateAccount: setAccount } = useAccountContext(); + const handleChange = (e: React.ChangeEvent) => { setFormData({ ...formData, [e.target.id]: e.target.value }); console.log(formData); @@ -48,84 +77,99 @@ const LoginScreen = (props: ComponentProps) => { ); } }; - const handleSubmit = async (event: React.FormEvent) => { - event.preventDefault(); - try { - const response = await axiosInstance.post("/auth/login/", { - email: formData.user, - password: formData.pass, - }); + + const handleLogin = async () => { + if (formData.user !== null && formData.pass !== null) { + const response = await login(formData.user, formData.pass); + if (!response || response.status !== 200) { + // Handle login error + toast.error("Invalid credentials"); + return; + } + if (response.data.account?.settings?.is2FAEnabled) { await handleTwoFactorAuth(response.data.user_id); } else { - navigate("/logged_in_home"); + navigate("/"); } - } catch (err) { - const error = isError(err); - axiosErrorHandler( - new Error("A login request attempt failed", { cause: error }) - ); + + // Handle response here. For example, store the user data in the state or context. + setAccount(response.data.account); + } else { + toast.error("Invalid credentials"); } }; + useEffect(() => {}, [isLoading]); + return ( - - - + + Login - - + + + {isLoading ? ( + + ) : ( + + )} - - -
- -
-
+ + - - props.updateShowForgotPasswordState()} - href="#" - sx={{ - textDecoration: "underline", - fontSize: "1.5em", - color: "text.main", - }} - > - Forgot Password - - - OR - - + props.updateShowForgotPasswordState()} + href="#" + sx={{ + fontWeight: "500", + textDecoration: "underline", + color: "text.primary", + }} + > + Forgotten password? + + + + or + + -
+ ); }; @@ -191,73 +241,47 @@ const ForgotPasswordScreen = (props: ComponentProps) => { }; return ( - - - - + + + Find Your Account + + + + + + - Forgot Password - - - - - - Return to Login - - - - - - ); -}; - -const LoginWindow = () => { - const [showForgotPassword, setShowForgotPassword] = useState(false); - - const updateShowForgotPasswordState = () => { - setShowForgotPassword((prevState) => !prevState); - }; - return showForgotPassword ? ( - - ) : ( - + + + + + ); }; - -export default LoginWindow; diff --git a/meshapp/src/components/svgs/discord.tsx b/meshapp/src/components/svgs/discord.tsx new file mode 100644 index 00000000..fff63085 --- /dev/null +++ b/meshapp/src/components/svgs/discord.tsx @@ -0,0 +1,28 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; +import React from "react"; + +const DiscordSvg: React.FC = (props: SvgIconProps) => { + return ( + + + + + + discord + + + + + ); +}; + +export default DiscordSvg; diff --git a/meshapp/src/components/svgs/google.tsx b/meshapp/src/components/svgs/google.tsx new file mode 100644 index 00000000..f2aac772 --- /dev/null +++ b/meshapp/src/components/svgs/google.tsx @@ -0,0 +1,61 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; +import React from "react"; + +const GoogleSvg: React.FC = (props: SvgIconProps) => { + return ( + + + + + + Google-color Created with Sketch. + + + + + + + + + + + + + + + ); +}; + +export default GoogleSvg; diff --git a/meshapp/src/pages/Login/index.tsx b/meshapp/src/pages/Login/index.tsx index 0b91bc73..3cfa111e 100644 --- a/meshapp/src/pages/Login/index.tsx +++ b/meshapp/src/pages/Login/index.tsx @@ -1,10 +1,9 @@ -import React from "react"; import { Button, Divider, Grid, + Link, Skeleton, - Theme, Typography, } from "@mui/material"; import { ThemeProvider, createTheme } from "@mui/material"; @@ -12,23 +11,11 @@ import { deepmerge } from "@mui/utils"; import { Link as RouterLink } from "react-router-dom"; import { paths } from "src/routes/route-paths"; import LoginWindow from "src/components/AuthForms/LoginForm"; - -const buttonTheme = createTheme({ - components: { - MuiButton: { - styleOverrides: { - root: { - borderRadius: 25, - width: "75%", - height: "4.5rem", - fontSize: "1.5rem", - }, - }, - }, - }, -}); +import { useNavigate } from "react-router-dom"; +import { GridContainer, GridItem } from "../../components/ui/Grid"; const SignUp = () => { + const navigate = useNavigate(); return ( { spacing={5} sx={{ textAlign: "center", alignItems: "center" }} > - + Don't have an account with us yet? @@ -44,55 +31,105 @@ const SignUp = () => { - - - + + + ); }; const Login = () => { + const navigate = useNavigate(); return ( - { - return createTheme(deepmerge(buttonTheme, theme)); - }} - > - - + + + + + - - - - - - - - - - - - + + + + + + + + + + + Don't have an account with us yet?{" "} + navigate(paths.sign_up)} + sx={{ + fontWeight: "700", + cursor: "pointer", + textDecoration: "underline", + color: "text.primary", + }} + > + {/* In this code, {'\u00A0'} is used to insert a non-breaking space between "Sign" and "up". + This will ensure that the entire "Sign up" text stays on the same line and wraps together when necessary. */} + Sign{"\u00A0"}up + + + + + + + + ); }; From 04f720b761679574cb88c70acdccc8363ef1b866 Mon Sep 17 00:00:00 2001 From: bluesimp1102 Date: Sun, 9 Jun 2024 13:29:23 -0500 Subject: [PATCH 6/8] feat: unified home routing with user context & layout --- meshapp/src/components/BrandingLogo.tsx | 30 ++ .../src/components/Footer/LoggedInMessage.tsx | 21 -- .../src/components/Header/LoggedInHeader.tsx | 34 -- .../src/components/Header/LoggedOutHeader.tsx | 52 --- .../components/{Footer => Layout}/Footer.tsx | 4 +- meshapp/src/components/Layout/Header.tsx | 125 +++++++ meshapp/src/components/Layout/SideMenu.tsx | 311 ++++++++++++++++++ meshapp/src/components/Layout/index.tsx | 51 +++ meshapp/src/components/SideMenu.tsx | 125 ------- meshapp/src/components/ThemeSwitch.tsx | 22 ++ .../TwoFactorAuth/TwoFactorBanner.tsx | 89 +++-- .../TwoFactorAuth/TwoFactorReminder.tsx | 32 +- .../TwoFactorAuth/styling/TwoFactorBanner.css | 23 +- .../TwoFactorAuth/styling/TwoFactorPopup.css | 2 +- meshapp/src/components/svgs/logo.tsx | 111 +++++++ meshapp/src/components/svgs/mesh-pfp.tsx | 38 +++ meshapp/src/components/ui/FlexBetween.tsx | 9 + meshapp/src/components/ui/Grid.tsx | 40 +++ meshapp/src/components/ui/LoadingSpinner.tsx | 11 + meshapp/src/config/axios-config.ts | 5 +- .../src/pages/Home/LoggedIn/LoggedInHome.tsx | 205 +++++++----- .../pages/Home/LoggedOut/LoggedOutHome.tsx | 224 +++---------- .../LoggedOut/components/AdvertSection.tsx | 4 +- .../LoggedOut/components/LoggedOutNavBar.tsx | 77 ----- .../LoggedOut/components/LoggedOutWelcome.tsx | 53 ++- .../LoggedOut/components/ReviewSection.tsx | 2 +- meshapp/src/pages/Home/Page.tsx | 28 ++ meshapp/src/routes/AppRoutes.tsx | 8 +- meshapp/src/routes/FooterRoutes.tsx | 28 -- meshapp/src/routes/HeaderRoutes.tsx | 30 -- meshapp/src/routes/MainPageRoutes.tsx | 94 +++--- meshapp/src/routes/route-paths.ts | 24 +- 32 files changed, 1124 insertions(+), 788 deletions(-) create mode 100644 meshapp/src/components/BrandingLogo.tsx delete mode 100644 meshapp/src/components/Footer/LoggedInMessage.tsx delete mode 100644 meshapp/src/components/Header/LoggedInHeader.tsx delete mode 100644 meshapp/src/components/Header/LoggedOutHeader.tsx rename meshapp/src/components/{Footer => Layout}/Footer.tsx (98%) create mode 100644 meshapp/src/components/Layout/Header.tsx create mode 100644 meshapp/src/components/Layout/SideMenu.tsx create mode 100644 meshapp/src/components/Layout/index.tsx delete mode 100644 meshapp/src/components/SideMenu.tsx create mode 100644 meshapp/src/components/ThemeSwitch.tsx create mode 100644 meshapp/src/components/svgs/logo.tsx create mode 100644 meshapp/src/components/svgs/mesh-pfp.tsx create mode 100644 meshapp/src/components/ui/FlexBetween.tsx create mode 100644 meshapp/src/components/ui/Grid.tsx create mode 100644 meshapp/src/components/ui/LoadingSpinner.tsx delete mode 100644 meshapp/src/pages/Home/LoggedOut/components/LoggedOutNavBar.tsx create mode 100644 meshapp/src/pages/Home/Page.tsx delete mode 100644 meshapp/src/routes/FooterRoutes.tsx delete mode 100644 meshapp/src/routes/HeaderRoutes.tsx diff --git a/meshapp/src/components/BrandingLogo.tsx b/meshapp/src/components/BrandingLogo.tsx new file mode 100644 index 00000000..94a3d46c --- /dev/null +++ b/meshapp/src/components/BrandingLogo.tsx @@ -0,0 +1,30 @@ +import { Box, Typography } from "@mui/material"; +import MeshLogo from "./svgs/logo"; + +export const BrandingLogo = () => { + return ( + + + mesh + + + + ); +}; diff --git a/meshapp/src/components/Footer/LoggedInMessage.tsx b/meshapp/src/components/Footer/LoggedInMessage.tsx deleted file mode 100644 index 841f0310..00000000 --- a/meshapp/src/components/Footer/LoggedInMessage.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { AppBar, Grid, IconButton, Toolbar } from "@mui/material"; -import EmailIcon from "@mui/icons-material/Email"; -import React from "react"; - -export default function LoggedInFooter() { - return ( - - - - - - - - - - ); -} diff --git a/meshapp/src/components/Header/LoggedInHeader.tsx b/meshapp/src/components/Header/LoggedInHeader.tsx deleted file mode 100644 index 0f478189..00000000 --- a/meshapp/src/components/Header/LoggedInHeader.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import React from "react"; -import { AppBar, Grid, Toolbar, Typography } from "@mui/material"; -import SideMenu from "../SideMenu"; - -export default function LoggedInHeader() { - return ( - - - {/*---Left Side Menu Elements (Menu/Notification Icon)---*/} - - - {/*---Logo---*/} - - - mesh - - - {/*---Icon goes here---*/} - - - - ); -} diff --git a/meshapp/src/components/Header/LoggedOutHeader.tsx b/meshapp/src/components/Header/LoggedOutHeader.tsx deleted file mode 100644 index 032f405a..00000000 --- a/meshapp/src/components/Header/LoggedOutHeader.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { Grid, Link, Typography } from "@mui/material"; -import { paths } from "src/routes/route-paths"; -import { Link as RouterLink } from "react-router-dom"; -export default function LoggedOutHeader() { - return ( - - {/*contains the nav links*/} - - - - - - home - - - - - - - - about us - - - - {/*For Icon*/} - - - ); -} diff --git a/meshapp/src/components/Footer/Footer.tsx b/meshapp/src/components/Layout/Footer.tsx similarity index 98% rename from meshapp/src/components/Footer/Footer.tsx rename to meshapp/src/components/Layout/Footer.tsx index 99f31cb0..7edc7406 100644 --- a/meshapp/src/components/Footer/Footer.tsx +++ b/meshapp/src/components/Layout/Footer.tsx @@ -107,7 +107,7 @@ const Footer = () => { > General - + home about @@ -158,7 +158,7 @@ const Footer = () => { justifyContent="space-between" alignItems="center" p={2} - color="text.main" + color="text.primary" > diff --git a/meshapp/src/components/Layout/Header.tsx b/meshapp/src/components/Layout/Header.tsx new file mode 100644 index 00000000..05502003 --- /dev/null +++ b/meshapp/src/components/Layout/Header.tsx @@ -0,0 +1,125 @@ +import React from "react"; +import { + AppBar, + AppBarProps, + Toolbar, + Typography, + Link, + IconButton, + Button, +} from "@mui/material"; +import MenuIcon from "@mui/icons-material/Menu"; +import { Notifications as NotificationsIcon } from "@mui/icons-material"; +import { useNavigate } from "react-router-dom"; // Hook for navigation +import { useAccountContext } from "src/contexts/UserContext"; // Context for account information +import { FlexBetween } from "src/components/ui/FlexBetween"; +import { ThemeSwitch } from "src/components/ThemeSwitch"; + +import { BrandingLogo } from "src/components/BrandingLogo"; +import SideMenuDrawer from "./SideMenu"; + +const Header: React.FC = () => { + const navigate = useNavigate(); + const { account } = useAccountContext(); // Get the current account context + + // State for managing the side menu drawer's open/close state + const [open, setOpen] = React.useState(false); + const handleDrawerOpen = () => setOpen(true); + const handleDrawerClose = () => setOpen(false); + + return ( + <> + + ({ + p: "4px 24px", + justifyContent: "space-between", + zIndex: 2, + bgcolor: theme.palette.primary.main, + })} + > + <> + + + + + {account && ( + + + + )} + + + + + + + + + + home + + + + + about us + + + + + {account === null && ( + + )} + + + + + + + ); +}; + +export default Header; diff --git a/meshapp/src/components/Layout/SideMenu.tsx b/meshapp/src/components/Layout/SideMenu.tsx new file mode 100644 index 00000000..a5d8e1a0 --- /dev/null +++ b/meshapp/src/components/Layout/SideMenu.tsx @@ -0,0 +1,311 @@ +import React from "react"; +import { + Box, + Button, + Collapse, + IconButton, + List, + ListItem, + ListItemButton, + ListItemIcon, + ListItemText, + SwipeableDrawer, +} from "@mui/material"; + +import { + Notifications as NotificationsIcon, + Home as HomeIcon, + Receipt as ProfileIcon, + Inbox as MessagingIcon, + People as SwipingIcon, + Settings as SettingsIcon, + Logout as LogoutIcon, + ChevronLeft as ChevronLeftIcon, + ContactSupport, + ExpandLess, + ExpandMore, + Info as InfoIcon, +} from "@mui/icons-material"; +import { Link as RouterLink, useLocation, useNavigate } from "react-router-dom"; +import { useAccountContext } from "src/contexts/UserContext"; +import { FlexBetween } from "src/components/ui/FlexBetween"; +import { ThemeSwitch } from "src/components/ThemeSwitch"; +import { paths } from "src/routes/route-paths"; +import { BASE_URL } from "src/config/axios-config"; + +interface MenuDrawerProps { + open: boolean; + handleDrawerClose: () => void; + handleDrawerOpen: () => void; +} + +const drawerWidth = 250; + +type ListItemTypes = { + text: String; + Icon: React.ElementType; + handleDrawerClose: () => void; + path?: String; + isExpand?: boolean; + onExpandClick?: () => void; +}; + +// TODO: (#258) Add routes to each menu item when routing is implemented +const ListItemComponent = ({ + text, + Icon, + path, + isExpand, + onExpandClick, + handleDrawerClose, +}: ListItemTypes) => { + const location = useLocation(); // Get current location + const isActive = path && path === location.pathname; // Check if current path matches + + return ( + ( + + )} + > + + + + + + {onExpandClick && + (isExpand !== null && isExpand == true ? ( + + ) : ( + + ))} + + + ); +}; + +const SideMenuDrawer: React.FC = ({ + open, + handleDrawerClose, + handleDrawerOpen, +}) => { + const { account } = useAccountContext(); + + const [homeOpen, setHomeOpen] = React.useState(false); + const toggleHome = () => { + setHomeOpen(!homeOpen); + }; + + const navigate = useNavigate(); + + return ( +
+ + +
+
+ {/* Top Content (Notifications Button, Theme Switch, and Close Button) */} +
+ +
+ {account && ( + + + + )} +
+ +
+ +
+ {/* Navigation tabs + - Home (includes a drop down select for `About Us` and `Contact Us` pages) + - Profile + - Messaging + - Swiping + */} +
+ + {/* Home navigation (can be expanded to more navigations links) */} + + + + + + + + {/* Display the `Sign In` button if the user is not logged in, otherwise, display the nagivation components */} + {account ? ( + <> + + {/* */} + + + ) : ( + + + + )} + +
+ {/* Bottom Content + - Settings page navigation + - Logout button + */} + {account && ( +
+ + {/* setting navigation */} + + {/* logout button */} + + + (window.location.href = `${BASE_URL}/auth/logout/`) + } + > + + + + + + + +
+ )} +
+
+
+
+ ); +}; + +export default SideMenuDrawer; diff --git a/meshapp/src/components/Layout/index.tsx b/meshapp/src/components/Layout/index.tsx new file mode 100644 index 00000000..c3dca237 --- /dev/null +++ b/meshapp/src/components/Layout/index.tsx @@ -0,0 +1,51 @@ +// src/components/Layout/index.tsx + +import { Box } from "@mui/material"; +import { Outlet, useLocation, useNavigate } from "react-router-dom"; +import { useAccountContext } from "../../contexts/UserContext"; +import { useEffect } from "react"; +import { + logged_in_paths, + logged_out_paths, + paths, +} from "src/routes/route-paths"; +import LoadingProgress from "../ui/LoadingSpinner"; +import TwoFactorAuthReminders from "src/components/TwoFactorAuth/TwoFactorReminder"; + +import Footer from "./Footer"; +import Header from "./Header"; + +export const PageLayout = () => { + const { account, loadingState } = useAccountContext(); + const navigate = useNavigate(); + const { pathname } = useLocation(); + + useEffect(() => { + if (loadingState === "completed") { + if (logged_in_paths.includes(pathname) && !account) { + navigate(paths.home); + } else if (logged_out_paths.includes(pathname) && account) { + navigate(paths.home); + } + } + }, [account, loadingState, pathname, navigate]); + + if (loadingState !== "completed") { + // Render only loading progress until the account context is loaded + return ; + } + + // Render the rest of the page layout once the account context is fully loaded + return ( + + +
+ + + + +