Update navbar, implement useNavbar hook, style updates

- Created a custom hook that returns an object with pages depending on user auth. This is a refactor of the logic found in the navbar component.
- Updated styles for card components
- Fix font size issues for mobile.
- Update theming to include a new pastel theme.
This commit is contained in:
Aaron William Po
2023-04-13 22:51:18 -04:00
parent 8867c0bc56
commit 07330beb9c
6 changed files with 147 additions and 109 deletions

View File

@@ -1,45 +1,63 @@
/* eslint-disable jsx-a11y/no-noninteractive-tabindex */ import useMediaQuery from '@/hooks/useMediaQuery';
/* eslint-disable jsx-a11y/label-has-associated-control */ import useNavbar from '@/hooks/useNavbar';
/* eslint-disable jsx-a11y/label-has-for */
import UserContext from '@/contexts/userContext';
import Link from 'next/link'; import Link from 'next/link';
import { useRouter } from 'next/router'; import { FC } from 'react';
import { useContext, useEffect, useState } from 'react';
import { GiHamburgerMenu } from 'react-icons/gi';
const DesktopLinks: FC = () => {
const { pages, currentURL } = useNavbar();
return (
<div className="block flex-none">
<ul className="menu menu-horizontal p-0">
{pages.map((page) => {
return (
<li key={page.slug}>
<Link tabIndex={0} href={page.slug}>
<span
className={`text-lg uppercase ${
currentURL === page.slug ? 'font-extrabold' : 'font-semibold'
} text-primary-content`}
>
{page.name}
</span>
</Link>
</li>
);
})}
</ul>
</div>
);
};
const MobileLinks: FC = () => {
const { pages } = useNavbar();
return (
<div className="flex-none lg:hidden">
<div className="dropdown-end dropdown">
<label tabIndex={0} className="btn-ghost btn-circle btn">
<GiHamburgerMenu />
</label>
<ul
tabIndex={0}
className="dropdown-content menu rounded-box menu-compact mt-3 w-48 bg-base-100 p-2 shadow"
>
{pages.map((page) => (
<li key={page.slug}>
<Link href={page.slug}>
<span className="select-none text-primary-content">{page.name}</span>
</Link>
</li>
))}
</ul>
</div>
</div>
);
};
interface Page {
slug: string;
name: string;
}
const Navbar = () => { const Navbar = () => {
const router = useRouter(); const isDesktopView = useMediaQuery('(min-width: 1024px)');
const [currentURL, setCurrentURL] = useState('/');
const { user } = useContext(UserContext);
useEffect(() => {
setCurrentURL(router.asPath);
}, [router.asPath]);
const authenticatedPages: readonly Page[] = [
{ slug: '/account', name: 'Account' },
{ slug: '/api/users/logout', name: 'Logout' },
];
const unauthenticatedPages: readonly Page[] = [
{ slug: '/login', name: 'Login' },
{ slug: '/register', name: 'Register' },
];
const otherPages: readonly Page[] = [
{ slug: '/beers', name: 'Beers' },
{ slug: '/breweries', name: 'Breweries' },
];
const pages: readonly Page[] = [
...otherPages,
...(user ? authenticatedPages : unauthenticatedPages),
];
return ( return (
<nav className="navbar sticky top-0 z-50 bg-primary text-primary-content"> <nav className="navbar sticky top-0 z-50 bg-primary text-primary-content">
@@ -48,58 +66,7 @@ const Navbar = () => {
<span className="cursor-pointer text-lg font-bold">The Biergarten App</span> <span className="cursor-pointer text-lg font-bold">The Biergarten App</span>
</Link> </Link>
</div> </div>
<div className="hidden flex-none lg:block"> <div>{isDesktopView ? <DesktopLinks /> : <MobileLinks />}</div>
<ul className="menu menu-horizontal p-0">
{pages.map((page) => {
return (
<li key={page.slug}>
<Link tabIndex={0} href={page.slug}>
<span
className={`text-lg uppercase ${
currentURL === page.slug ? 'font-extrabold' : 'font-semibold'
} text-primary-content`}
>
{page.name}
</span>
</Link>
</li>
);
})}
</ul>
</div>
<div className="flex-none lg:hidden">
<div className="dropdown dropdown-end">
<label tabIndex={0} className="btn-ghost btn-circle btn">
<span className="w-10 rounded-full">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
className="inline-block h-5 w-5 stroke-white"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M4 6h16M4 12h16M4 18h16"
/>
</svg>
</span>
</label>
<ul
tabIndex={0}
className="dropdown-content menu rounded-box menu-compact mt-3 w-48 bg-base-100 p-2 shadow"
>
{pages.map((page) => (
<li key={page.slug}>
<Link href={page.slug}>
<span className="select-none text-primary-content">{page.name}</span>
</Link>
</li>
))}
</ul>
</div>
</div>
</nav> </nav>
); );
}; };

64
src/hooks/useNavbar.ts Normal file
View File

@@ -0,0 +1,64 @@
import UserContext from '@/contexts/userContext';
import { useRouter } from 'next/router';
import { useState, useEffect, useContext } from 'react';
interface Page {
slug: string;
name: string;
}
/**
* A custom hook that returns the current URL and the pages to display in the navbar. It
* uses the user context to determine whether the user is authenticated or not.
*
* @returns An object containing the current URL and the pages to display in the navbar.
*/
const useNavbar = () => {
const router = useRouter();
const [currentURL, setCurrentURL] = useState('/');
const { user } = useContext(UserContext);
const authenticatedPages: readonly Page[] = [
{ slug: '/account', name: 'Account' },
{ slug: '/api/users/logout', name: 'Logout' },
];
const unauthenticatedPages: readonly Page[] = [
{ slug: '/login', name: 'Login' },
{ slug: '/register', name: 'Register' },
];
/** These pages are accessible to both authenticated and unauthenticated users. */
const otherPages: readonly Page[] = [
{ slug: '/beers', name: 'Beers' },
{ slug: '/breweries', name: 'Breweries' },
];
/**
* The pages to display in the navbar. If the user is authenticated, the authenticated
* pages are displayed. Otherwise, the unauthenticated pages are displayed. The other
* pages are always displayed.
*/
const pages: readonly Page[] = [
...otherPages,
...(user ? authenticatedPages : unauthenticatedPages),
];
/**
* Sets the current URL to the current URL when the router's asPath changes. This
* ensures that the current URL is always up to date. When the component unmounts, the
* current URL is set to '/'.
*/
useEffect(() => {
setCurrentURL(router.asPath);
return () => {
setCurrentURL('/');
};
}, [router.asPath]);
return { currentURL, pages };
};
export default useNavbar;

View File

@@ -29,7 +29,7 @@ interface BeerPageProps {
} }
const BeerByIdPage: NextPage<BeerPageProps> = ({ beerPost, beerRecommendations }) => { const BeerByIdPage: NextPage<BeerPageProps> = ({ beerPost, beerRecommendations }) => {
const isMd = useMediaQuery('(min-width: 768px)'); const isDesktop = useMediaQuery('(min-width: 1024px)');
return ( return (
<> <>
@@ -64,7 +64,7 @@ const BeerByIdPage: NextPage<BeerPageProps> = ({ beerPost, beerRecommendations }
<div className="w-11/12 space-y-3 xl:w-9/12"> <div className="w-11/12 space-y-3 xl:w-9/12">
<BeerInfoHeader beerPost={beerPost} /> <BeerInfoHeader beerPost={beerPost} />
{isMd ? ( {isDesktop ? (
<div className="mt-4 flex flex-row space-x-3 space-y-0"> <div className="mt-4 flex flex-row space-x-3 space-y-0">
<div className="w-[60%]"> <div className="w-[60%]">
<BeerPostCommentsSection beerPost={beerPost} /> <BeerPostCommentsSection beerPost={beerPost} />
@@ -75,12 +75,12 @@ const BeerByIdPage: NextPage<BeerPageProps> = ({ beerPost, beerRecommendations }
</div> </div>
) : ( ) : (
<Tab.Group> <Tab.Group>
<Tab.List className="card flex flex-row bg-base-300"> <Tab.List className="tabs tabs-boxed items-center justify-center rounded-2xl bg-base-300">
<Tab className="ui-selected:bg-gray w-1/2 p-3 uppercase"> <Tab className="tab tab-lg w-1/2 uppercase ui-selected:tab-active">
Comments Comments
</Tab> </Tab>
<Tab className="ui-selected:bg-gray w-1/2 p-3 uppercase"> <Tab className="tab tab-lg w-1/2 uppercase ui-selected:tab-active">
Recommendations Other Beers
</Tab> </Tab>
</Tab.List> </Tab.List>
<Tab.Panels className="mt-2"> <Tab.Panels className="mt-2">

View File

@@ -5,29 +5,33 @@ import UserContext from '@/contexts/userContext';
import { GetServerSideProps, NextPage } from 'next'; import { GetServerSideProps, NextPage } from 'next';
import { useContext } from 'react'; import { useContext } from 'react';
import useMediaQuery from '@/hooks/useMediaQuery';
const ProtectedPage: NextPage = () => { const ProtectedPage: NextPage = () => {
const { user, isLoading } = useContext(UserContext); const { user, isLoading } = useContext(UserContext);
const currentTime = new Date().getHours(); const currentTime = new Date().getHours();
const isMorning = currentTime > 4 && currentTime < 12; const isMorning = currentTime >= 3 && currentTime < 12;
const isAfternoon = currentTime > 12 && currentTime < 18; const isAfternoon = currentTime >= 12 && currentTime < 18;
const isEvening = (currentTime > 18 && currentTime < 24) || currentTime < 4; const isEvening = currentTime >= 18 || currentTime < 3;
const isDesktop = useMediaQuery('(min-width: 768px)');
return ( return (
<Layout> <Layout>
<div className="flex h-full flex-col items-center justify-center space-y-3"> <div className="flex h-full flex-col items-center justify-center space-y-3 text-center">
{isLoading && <Spinner size="xl" />} {isLoading && <Spinner size={isDesktop ? 'xl' : 'md'} />}
{user && ( {user && !isLoading && (
<> <>
<h1 className="text-7xl font-bold"> <h1 className="text-2xl font-bold lg:text-7xl">
Good {isMorning && 'morning'} Good {isMorning && 'morning'}
{isAfternoon && 'afternoon'} {isAfternoon && 'afternoon'}
{isEvening && 'evening'} {isEvening && 'evening'}
{`, ${user?.firstName}!`} {`, ${user?.firstName}!`}
</h1> </h1>
<h2 className="text-4xl font-bold">Welcome to the Biergarten App!</h2> <h2 className="text-xl font-bold lg:text-4xl">
Welcome to the Biergarten App!
</h2>
</> </>
)} )}
</div> </div>

View File

@@ -1,3 +1,7 @@
@tailwind base; @tailwind base;
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;
.card {
@apply shadow-md
}

View File

@@ -1,6 +1,6 @@
//themes //themes
const myTheme = { const darkTheme = {
default: { default: {
primary: 'hsl(227, 23%, 20%)', primary: 'hsl(227, 23%, 20%)',
secondary: 'hsl(255, 9%, 69%)', secondary: 'hsl(255, 9%, 69%)',
@@ -20,7 +20,7 @@ const myTheme = {
const pastelTheme = { const pastelTheme = {
default: { default: {
primary: 'hsl(180, 28%, 65%)', primary: 'hsl(180, 15%, 60%)',
secondary: 'hsl(21, 54%, 83%)', secondary: 'hsl(21, 54%, 83%)',
error: 'hsl(4, 87%, 74%)', error: 'hsl(4, 87%, 74%)',
accent: 'hsl(93, 27%, 73%)', accent: 'hsl(93, 27%, 73%)',
@@ -33,7 +33,6 @@ const pastelTheme = {
'base-100': 'hsl(0, 0%, 85%)', 'base-100': 'hsl(0, 0%, 85%)',
'base-200': 'hsl(0, 0%, 82%)', 'base-200': 'hsl(0, 0%, 82%)',
'base-300': 'hsl(0, 0%, 78%)', 'base-300': 'hsl(0, 0%, 78%)',
'base-400': 'hsl(0, 0%, 75%)',
}, },
}; };
@@ -55,6 +54,6 @@ module.exports = {
daisyui: { daisyui: {
logs: false, logs: false,
themes: [myTheme], themes: [darkTheme, pastelTheme],
}, },
}; };