Skip to content

Commit

Permalink
Feat/navbar mobile (#81)
Browse files Browse the repository at this point in the history
  • Loading branch information
Akzuu authored Oct 16, 2023
1 parent da83340 commit f8260e3
Show file tree
Hide file tree
Showing 18 changed files with 271 additions and 414 deletions.
109 changes: 34 additions & 75 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import Container from 'react-bootstrap/Container';
import { Routes, Route, Navigate } from 'react-router-dom';
import { Login } from './views/Login/Login';
import { Routes, Route, Navigate, Outlet } from 'react-router-dom';
import { Register } from './views/Register/Register';
import { Navbar } from './views/Navbar';
import {
QueryClient,
QueryClientProvider,
Expand All @@ -12,25 +9,29 @@ import { PageLoadingSpinner } from './components/Spinner';
import { DivingCylinderSetManagement } from './views/DivingCylinderSetSettings';
import { UserSettings } from './components/UserSettings/UserSettings';
import { BlenderLogbook } from './views/BlenderLogbook';
import { ProtectedRoute } from './components/common/Auth';
import { Logbook } from './views/Logbook';
import { FillEvents } from './views/FillEvents';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import React, { useEffect, useState } from 'react';
import { ToastContainer } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
import { Footer } from './components/Footer/Footer';
import { GDPR } from './views/GDPR';
import { FrontPage } from './views/FrontPage/FrontPage';
import { PasswordResetRequest } from './views/PasswordResetRequest/PasswordResetRequest';
import { ResetPassword } from './views/ResetPassword/ResetPassword';
import { Login } from './views/Login/Login';
import { PrivateContent } from './components/common/PrivateContent';
import { Footer } from './components/Footer/Footer';

const QUERY_CLIENT = new QueryClient();

type ContentProps = {
forceShowNavbar: () => void;
};
const BaseElement: React.FC = () => (
<div className="position-absolute w-100 h-100 d-flex flex-column justify-content-between">
<Outlet />
<Footer />
</div>
);

const Content: React.FC<ContentProps> = ({ forceShowNavbar }) => {
const Content: React.FC = () => {
const [showSpinner, setShowSpinner] = useState(false);
const isFetching = useIsFetching();

Expand All @@ -51,14 +52,11 @@ const Content: React.FC<ContentProps> = ({ forceShowNavbar }) => {
return (
<main>
{showSpinner ? <PageLoadingSpinner /> : null}
<Container className="pt-4 content">
<Routes>
<Routes>
<Route element={<BaseElement />}>
{/* Public routes */}
<Route element={<FrontPage />}>
<Route
path="/"
element={<Login onLoginSuccess={forceShowNavbar} />}
/>
<Route path="/" element={<Login />} />
<Route path="register" element={<Register />} />
<Route
path={'request-password-reset'}
Expand All @@ -69,74 +67,35 @@ const Content: React.FC<ContentProps> = ({ forceShowNavbar }) => {
<Route path="gdpr" element={<GDPR />} />

{/* Private routes */}
<Route
path="diving-cylinder-set"
element={
<ProtectedRoute>
<DivingCylinderSetManagement />
</ProtectedRoute>
}
/>
<Route
path="logbook"
element={
<ProtectedRoute>
<Logbook />
</ProtectedRoute>
}
/>
<Route
path="blender-logbook"
element={
<ProtectedRoute blenderOnly>
<BlenderLogbook />
</ProtectedRoute>
}
/>
<Route
path="fill-events"
element={
<ProtectedRoute>
<FillEvents />
</ProtectedRoute>
}
/>
<Route
path="user"
element={
<ProtectedRoute>
<UserSettings />
</ProtectedRoute>
}
/>
<Route element={<PrivateContent />}>
<Route
path="diving-cylinder-set"
element={<DivingCylinderSetManagement />}
/>
<Route path="logbook" element={<Logbook />} />

<Route path="fill-events" element={<FillEvents />} />
<Route path="user" element={<UserSettings />} />
</Route>

{/* Blender only views */}
<Route element={<PrivateContent blenderOnly />}>
<Route path="blender-logbook" element={<BlenderLogbook />} />
</Route>

{/* 404 */}
<Route path="*" element={<Navigate to="/" />} />
</Routes>
</Container>
</Route>
</Routes>
</main>
);
};

const App = (): JSX.Element => {
// If access token exist, user has authenticated
const authenticated = useMemo(
() => localStorage.getItem('accessToken') !== null,
[]
);
const [showNavbar, setShowNavbar] = useState(authenticated);

useEffect(() => {
setShowNavbar(authenticated);
}, [authenticated]);

const setNavbarVisible = useCallback(() => setShowNavbar(true), []);

return (
<QueryClientProvider client={QUERY_CLIENT}>
<ToastContainer className="toast-position" position={'top-right'} />
{showNavbar && <Navbar />}
<Content forceShowNavbar={setNavbarVisible} />
<Footer />
<Content />
</QueryClientProvider>
);
};
Expand Down
1 change: 1 addition & 0 deletions src/components/Footer/Footer.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
left: 0;
bottom: 0;
max-height: 120px;
margin-top: 2rem;
width: 100%;
}
.footer .row {
Expand Down
6 changes: 2 additions & 4 deletions src/components/Login/LoginForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import Form from 'react-bootstrap/Form';
import { Formik } from 'formik';
import { ButtonType, PrimaryButton } from '../common/Buttons';
import { useNavigate } from 'react-router-dom';
import { LoginProps } from '../../views/Login/Login';
import { TextInput } from '../common/Inputs';
import { LOGIN_VALIDATION_SCHEMA } from './validation';
import { useLoginMutation } from '../../lib/queries/loginMutation';
Expand All @@ -13,12 +12,11 @@ type LoginFormFields = {
password: string;
};

export const LoginForm: React.FC<LoginProps> = ({ onLoginSuccess }) => {
export const LoginForm: React.FC = () => {
const navigate = useNavigate();
const handleSuccessfulLogin = useCallback(() => {
onLoginSuccess();
navigate('/logbook');
}, [navigate, onLoginSuccess]);
}, [navigate]);

const { mutate } = useLoginMutation(handleSuccessfulLogin);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Link, useMatch, useResolvedPath } from 'react-router-dom';
import styles from './Navbar.module.scss';

export const CustomLink = ({
to,
Expand All @@ -12,7 +13,7 @@ export const CustomLink = ({
const isActive = useMatch({ path: resolvedPath.pathname, end: true });

return (
<li className={isActive !== null ? 'active' : ''}>
<li className={isActive !== null ? styles.active : ''}>
<Link to={to} {...props}>
{children}
</Link>
Expand Down
113 changes: 113 additions & 0 deletions src/components/Navbar/Navbar.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
@import '../../variables.scss';

nav {
background-color: $blue;
padding: 0 !important;

li {
align-items: center;
color: $white;
display: flex;
}

ul {
padding: 0 1rem;
list-style: none;
justify-content: space-between;
width: 100%;
}

p {
align-items: center;
justify-content: center;
padding: 0;
margin: 0;
list-style: none;
display: flex;
}

a {
align-items: center;
box-sizing: content-box;
color: inherit;
display: flex;
height: 42px;
justify-content: center;
padding: 0.625rem 1rem;
text-decoration: none;
}

li:hover:not(.active) {
background-color: $blue-hover;
color: $white;

a {
color: inherit;
}
}

span {
padding-left: 0.5rem;
}
}

.active {
background: $white-smoke;
color: $black;

:hover {
color: $black;
a {
color: inherit;
}
}
}

.user {
display: flex;
height: 64px;
white-space: nowrap;
}

.iconLink {
align-items: center;
display: flex;
}

.logout {
align-self: center;
height: 42px;
margin: 0rem 1rem;
}

@media (max-width: 780px) {
nav {
a {
justify-content: left;
padding: 0 1rem;
width: 100%;
}
}

.user {
height: 42px;

a {
padding: 0 1rem;

span {
padding: 0;
}
}
}

.iconLink {
svg {
display: none;
}
}

.logout {
margin: 1rem 0;
}
}
67 changes: 67 additions & 0 deletions src/components/Navbar/Navbar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { useQueryClient } from '@tanstack/react-query';
import { useCallback } from 'react';
import { BsPersonCircle } from 'react-icons/bs';
import { useNavigate } from 'react-router-dom';
import { TertiaryButton } from '../common/Buttons';
import { getUserRoles } from '../../lib/utils';
import styles from './Navbar.module.scss';
import { Navbar as BootNavbar, Container, Nav } from 'react-bootstrap';
import { CustomLink } from './CustomLink';

export const Navbar: React.FC = () => {
const navigate = useNavigate();
const queryClient = useQueryClient();

const handleLogoutButtonClick = useCallback(() => {
localStorage.removeItem('accessToken');
localStorage.removeItem('refreshToken');

// Invalidate React Query cache
queryClient.clear();

navigate('/');
}, [navigate, queryClient]);

const { isAdmin, isBlender } = getUserRoles();

return (
<BootNavbar expand="lg">
<Container>
<span className="navbar-brand d-block d-lg-none d-xl-none text-white">
Täyttöpaikka
</span>
<BootNavbar.Toggle aria-controls="basic-navbar-nav" />
<BootNavbar.Collapse id="basic-navbar-nav">
<div className="d-flex flex-row justify-content-between w-100">
<Nav>
<CustomLink to="/logbook">Paineilmatäyttö</CustomLink>

{(isAdmin || isBlender) && (
<CustomLink to="/blender-logbook">Happihäkki</CustomLink>
)}

<CustomLink to="/diving-cylinder-set">Omat pullot</CustomLink>

<CustomLink to="/fill-events">Täyttöhistoria</CustomLink>
</Nav>
<Nav>
<div className={styles.user}>
<CustomLink to="/user">
<div className={styles.iconLink}>
<BsPersonCircle size={35} />
<span>Omat tiedot</span>
</div>
</CustomLink>
</div>
<TertiaryButton
className={styles.logout}
onClick={handleLogoutButtonClick}
text="Kirjaudu ulos"
/>
</Nav>
</div>
</BootNavbar.Collapse>
</Container>
</BootNavbar>
);
};
Loading

0 comments on commit f8260e3

Please sign in to comment.