diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index c31269ec..78047e3f 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -2,6 +2,7 @@ import React, { lazy, Suspense } from 'react' import { BrowserRouter, Route, Routes } from 'react-router-dom' import env from '@/config/env.config' import { GlobalProvider } from '@/context/GlobalContext' +import { UserProvider } from '@/context/UserContext' import { RecaptchaProvider } from '@/context/RecaptchaContext' import { init as initGA } from '@/common/ga4' import ScrollToTop from '@/components/ScrollToTop' @@ -10,6 +11,7 @@ if (env.GOOGLE_ANALYTICS_ENABLED) { initGA() } +const Header = lazy(() => import('@/components/Header')) const SignIn = lazy(() => import('@/pages/SignIn')) const SignUp = lazy(() => import('@/pages/SignUp')) const Activate = lazy(() => import('@/pages/Activate')) @@ -35,38 +37,42 @@ const Locations = lazy(() => import('@/pages/Locations')) const App = () => ( - - + + + -
- }> - - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> +
+ }> +
- } /> - - -
- + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + } /> + +
+
+
+
) diff --git a/frontend/src/assets/css/contact.css b/frontend/src/assets/css/contact.css index 544c1dad..75827a97 100644 --- a/frontend/src/assets/css/contact.css +++ b/frontend/src/assets/css/contact.css @@ -4,6 +4,7 @@ div.contact { flex: 1 0 auto; align-items: center; transform: translate3d(0, 0, 0); + min-height: 100vh; } div.contact .form { diff --git a/frontend/src/assets/css/header.css b/frontend/src/assets/css/header.css index 2d052727..d2ab0de1 100644 --- a/frontend/src/assets/css/header.css +++ b/frontend/src/assets/css/header.css @@ -20,6 +20,14 @@ margin-right: 10px; } +.side-menu li { + cursor: pointer; +} + +.side-menu li:hover { + background-color: #f1f1f1; +} + .header-action { margin-right: 20px; } @@ -53,11 +61,12 @@ .header .logo { text-decoration: none; + text-transform: none; + background: transparent; font-size: 18px; font-weight: 500; color: #121212; } - /* Device width is greater than or equal to 960px */ @media only screen and (width >=960px) { diff --git a/frontend/src/components/Header.tsx b/frontend/src/components/Header.tsx index d291fdf4..e74b5c67 100644 --- a/frontend/src/components/Header.tsx +++ b/frontend/src/components/Header.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react' +import React, { useState, useEffect, memo } from 'react' import { useNavigate } from 'react-router-dom' import { AppBar, @@ -11,10 +11,9 @@ import { Button, Drawer, List, - ListItemButton, ListItemIcon, ListItemText, - Link + ListItem } from '@mui/material' import { Menu as MenuIcon, @@ -47,27 +46,29 @@ import Avatar from './Avatar' import * as langHelper from '@/common/langHelper' import * as helper from '@/common/helper' import { useGlobalContext, GlobalContextType } from '@/context/GlobalContext' +import { useUserContext, UserContextType } from '@/context/UserContext' +import { useInit } from '@/common/customHooks' import '@/assets/css/header.css' const flagHeight = 28 interface HeaderProps { - user?: movininTypes.User hidden?: boolean hideSignin?: boolean } -const ListItemLink = (props: any) => - const Header = ({ - user, hidden, hideSignin, }: HeaderProps) => { const navigate = useNavigate() + + const { user, setUser, setUserLoaded } = useUserContext() as UserContextType const { notificationCount, setNotificationCount } = useGlobalContext() as GlobalContextType + const [currentUser, setCurrentUser] = useState() + const [lang, setLang] = useState(helper.getLanguage(env.DEFAULT_LANGUAGE)) const [anchorEl, setAnchorEl] = useState(null) const [langAnchorEl, setLangAnchorEl] = useState(null) @@ -75,7 +76,7 @@ const Header = ({ const [mobileMoreAnchorEl, setMobileMoreAnchorEl] = useState(null) const [sideAnchorEl, setSideAnchorEl] = useState(null) const [isSignedIn, setIsSignedIn] = useState(false) - const [loading, setIsLoading] = useState(true) + const [loading, setLoading] = useState(true) const [isLoaded, setIsLoaded] = useState(false) const isMenuOpen = Boolean(anchorEl) @@ -104,6 +105,76 @@ const Header = ({ }, } + const exit = async () => { + setLoading(false) + setUserLoaded(true) + + await UserService.signout(false, false) + } + + useInit(async () => { + const _currentUser = UserService.getCurrentUser() + + if (_currentUser) { + try { + const status = await UserService.validateAccessToken() + + if (status === 200) { + const _user = await UserService.getUser(_currentUser._id) + + if (_user) { + if (_user.blacklisted) { + await exit() + return + } + setUser(_user) + setCurrentUser(_user) + setIsSignedIn(true) + setLoading(false) + setUserLoaded(true) + } else { + await exit() + } + } else { + await exit() + } + } catch { + await exit() + } + } else { + await exit() + } + }, []) + + useEffect(() => { + const language = langHelper.getLanguage() + setLang(helper.getLanguage(language)) + langHelper.setLanguage(strings, language) + }, []) + + useEffect(() => { + if (user) { + setCurrentUser(user) + } + }, [user]) + + useEffect(() => { + const init = async () => { + if (!hidden) { + if (currentUser) { + const notificationCounter = await NotificationService.getNotificationCounter(currentUser._id as string) + setIsSignedIn(true) + setNotificationCount(notificationCounter.count) + setIsLoaded(true) + } else { + setIsLoaded(true) + } + } + } + + init() + }, [hidden, currentUser]) // eslint-disable-line react-hooks/exhaustive-deps + const handleAccountMenuOpen = (event: React.MouseEvent) => { setAnchorEl(event.currentTarget) } @@ -187,11 +258,13 @@ const Header = ({ } const handleSettingsClick = () => { + handleMenuClose() navigate('/settings') } const handleSignout = async () => { - await UserService.signout() + await UserService.signout(true, false) + handleMenuClose() } const handleMobileMenuOpen = (event: React.MouseEvent) => { @@ -210,28 +283,6 @@ const Header = ({ navigate('/notifications') } - useEffect(() => { - const language = langHelper.getLanguage() - setLang(helper.getLanguage(language)) - langHelper.setLanguage(strings, language) - }, []) - - useEffect(() => { - if (!hidden) { - if (user) { - NotificationService.getNotificationCounter(user._id as string).then((notificationCounter) => { - setIsSignedIn(true) - setNotificationCount(notificationCounter.count) - setIsLoading(false) - setIsLoaded(true) - }) - } else { - setIsLoading(false) - setIsLoaded(true) - } - } - }, [hidden, user, setNotificationCount]) - const menuId = 'primary-account-menu' const renderMenu = ( - Movin' In + + )} <> - + - + { + navigate('/') + handleSideMenuClose() + }} + > - + {isSignedIn && ( - + { + navigate('/bookings') + handleSideMenuClose() + }} + > - + )} - + { + navigate('/agencies') + handleSideMenuClose() + }} + > - - + + { + navigate('/destinations') + handleSideMenuClose() + }} + > - - + + { + navigate('/about') + handleSideMenuClose() + }} + > - - + + { + navigate('/tos') + handleSideMenuClose() + }} + > - - + + { + navigate('/contact') + handleSideMenuClose() + }} + > - + {env.isMobile && !hideSignin && !isSignedIn && isLoaded && !loading && ( - - - - + <> + { + navigate('/sign-in') + handleSideMenuClose() + }} + > + + + + { + navigate('/sign-up') + handleSideMenuClose() + }} + > + + + + )} @@ -420,7 +523,7 @@ const Header = ({ )} {isSignedIn && ( - + )} @@ -461,4 +564,4 @@ const Header = ({ ) } -export default Header +export default memo(Header) diff --git a/frontend/src/components/Layout.tsx b/frontend/src/components/Layout.tsx index d7730364..40b27606 100644 --- a/frontend/src/components/Layout.tsx +++ b/frontend/src/components/Layout.tsx @@ -2,87 +2,39 @@ import React, { useState, useEffect, ReactNode } from 'react' import { Button } from '@mui/material' import * as movininTypes from ':movinin-types' import { strings } from '@/lang/master' -import Header from './Header' import * as UserService from '@/services/UserService' import * as helper from '@/common/helper' -import { useInit } from '@/common/customHooks' import { useAnalytics } from '@/common/useAnalytics' +import { useUserContext, UserContextType } from '@/context/UserContext' interface LayoutProps { - user?: movininTypes.User strict?: boolean - hideSignin?: boolean children: ReactNode onLoad?: (user?: movininTypes.User) => void } const Layout = ({ - user: masterUser, strict, - hideSignin, children, onLoad }: LayoutProps) => { useAnalytics() - const [user, setUser] = useState() + const { user, userLoaded } = useUserContext() as UserContextType const [loading, setLoading] = useState(true) useEffect(() => { - if (masterUser && user && user.avatar !== masterUser.avatar) { - setUser(masterUser) - } - }, [masterUser, user]) - - useInit(async () => { - const exit = async () => { - if (strict) { - await UserService.signout(false, true) - } else { - setLoading(false) - - await UserService.signout(false, false) - - if (onLoad) { - onLoad() - } - } - } - - const currentUser = UserService.getCurrentUser() - - if (currentUser) { - try { - const status = await UserService.validateAccessToken() - - if (status === 200) { - const _user = await UserService.getUser(currentUser._id) - - if (_user) { - if (_user.blacklisted) { - await exit() - return - } - - setUser(_user) - setLoading(false) + if (userLoaded && !user && strict) { + // UserService.signout(false, true) + UserService.signout(true, false) + } else { + setLoading(false) - if (onLoad) { - onLoad(_user) - } - } else { - await exit() - } - } else { - await exit() - } - } catch { - await exit() + if (onLoad) { + onLoad(user || undefined) } - } else { - await exit() } - }, []) + }, [user, userLoaded, strict]) // eslint-disable-line react-hooks/exhaustive-deps const handleResend = async (e: React.MouseEvent) => { e.preventDefault() @@ -105,14 +57,13 @@ const Layout = ({ return ( <> -