Skip to content

Commit

Permalink
feat(navbar): implement landing page navbar
Browse files Browse the repository at this point in the history
-implement landing page navbar reusable component for both desktop and mobile screens

[Delivers #21]
  • Loading branch information
jkarenzi committed Jun 21, 2024
1 parent 9339be3 commit e8b9589
Show file tree
Hide file tree
Showing 22 changed files with 3,559 additions and 1,651 deletions.
4,900 changes: 3,253 additions & 1,647 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@
"@types/react-router-dom": "^5.3.3",
"axios": "^1.7.2",
"dotenv": "^16.4.5",
"jest": "^29.7.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-icons": "^5.2.1",
"react-redux": "^9.1.2",
"react-router-dom": "^6.23.1",
"redux": "^5.0.1"
Expand Down
Binary file added public/avatar.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/cart.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/down.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/edit.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/hamburger.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/heart.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/help.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/moon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/settings.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/signout.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/user.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
123 changes: 123 additions & 0 deletions src/__test__/navbar.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import { render, screen, fireEvent } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import { describe, it, expect } from 'vitest';
import Navbar from '@/components/Navbar';

describe('Navbar Component', () => {
it('renders Navbar component', () => {
render(
<MemoryRouter>
<Navbar />
</MemoryRouter>
);

const logo = screen.getByAltText(/logo/i);
expect(logo).toBeInTheDocument();

const title = screen.getByText(/dynamites/i);
expect(title).toBeInTheDocument();

const homeLink = screen.getByText(/Home/i);
expect(homeLink).toBeInTheDocument();

const shopLink = screen.getByText(/Shop/i);
expect(shopLink).toBeInTheDocument();

const aboutLink = screen.getByText(/About us/i);
expect(aboutLink).toBeInTheDocument();

const contactLink = screen.getAllByText(/Contact/i)[0];
expect(contactLink).toBeInTheDocument();

const cartIcon = screen.getByTitle('cart');
expect(cartIcon).toBeInTheDocument();

const avatar = screen.getByAltText(/profile/i);
expect(avatar).toBeInTheDocument();

const username = screen.getByText(/amanda green/i);
expect(username).toBeInTheDocument();
});

it('highlights the correct navigation link based on the current route', () => {
render(
<MemoryRouter initialEntries={['/shop']}>
<Navbar />
</MemoryRouter>
);

expect(screen.getByText('Shop')).toHaveClass(
'border-b-[2px] border-primary text-primary'
);
});

it('toggles menu on hamburger icon click', () => {
render(
<MemoryRouter>
<Navbar />
</MemoryRouter>
);

const hamburgerIcon = screen.getByTitle('hamburger');
fireEvent.click(hamburgerIcon);

expect(screen.getAllByText(/home/i)[0]).toBeInTheDocument();
});

it('renders links with correct paths', () => {
render(
<MemoryRouter>
<Navbar />
</MemoryRouter>
);

const homeLink = screen.getByText(/home/i);
expect(homeLink.closest('a')).toHaveAttribute('href', '/');

const shopLink = screen.getByText(/shop/i);
expect(shopLink.closest('a')).toHaveAttribute('href', '/shop');

const aboutLink = screen.getByText(/about us/i);
expect(aboutLink.closest('a')).toHaveAttribute('href', '/about');

const contactLink = screen.getAllByText(/contact/i)[0];
expect(contactLink.closest('a')).toHaveAttribute('href', '/contact');
});

it('displays cart item count', () => {
render(
<MemoryRouter>
<Navbar />
</MemoryRouter>
);

const cartCount = screen.getByText(/5/i);
expect(cartCount).toBeInTheDocument();
});

it('renders profile options on avatar click', () => {
render(
<MemoryRouter>
<Navbar />
</MemoryRouter>
);

const profileIcon = screen.getByTitle('toggleProfile');
fireEvent.click(profileIcon);

const editProfileOption = screen.getByText(/edit profile/i);
expect(editProfileOption).toBeInTheDocument();

const preferencesOption = screen.getByText(/preferences/i);
expect(preferencesOption).toBeInTheDocument();

const nightModeOption = screen.getByText(/night mode/i);
expect(nightModeOption).toBeInTheDocument();

const helpCenterOption = screen.getByText(/help center/i);
expect(helpCenterOption).toBeInTheDocument();

const signOutOption = screen.getByText(/sign out/i);
expect(signOutOption).toBeInTheDocument();
});
});
153 changes: 153 additions & 0 deletions src/components/Navbar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import { Link, useLocation } from 'react-router-dom';
import { useState } from 'react';
import { LuShoppingCart } from 'react-icons/lu';
import { FiHeart } from 'react-icons/fi';
import { FaAngleDown } from 'react-icons/fa6';
import { RxHamburgerMenu } from 'react-icons/rx';

function Navbar() {
const location = useLocation();
const [toggleMenu, setToggleMenu] = useState(false);
const [toggleProfileMenu, setToggleProfileMenu] = useState(false);
return (
<div className="relative flex items-center justify-between w-full h-16 shadow-sm">
<RxHamburgerMenu
title="hamburger"
size="20"
color="#424856"
className="lg:hidden ml-8"
onClick={() => setToggleMenu(!toggleMenu)}
/>
<div className="flex items-center gap-2 md:ml-8">
<img src="/logo.png" alt="logo" />
<h2 className="text-textBlack font-bold">Dynamites</h2>
</div>
<nav className="xs:hidden lg:flex items-center h-full">
<Link
to="/"
className={`${location.pathname === '/' ? 'border-b-[2px] border-primary text-primary' : 'text-grey'} h-full flex items-center justify-center pl-4 pr-8`}
>
Home
</Link>
<Link
to="/shop"
className={`${location.pathname === '/shop' ? 'border-b-[2px] border-primary text-primary' : 'text-grey'} h-full flex items-center justify-center pl-4 pr-8`}
>
Shop
</Link>
<Link
to="/about"
className={`${location.pathname === '/about' ? 'border-b-[2px] border-primary text-primary' : 'text-grey'} h-full flex items-center justify-center pl-4 pr-8`}
>
About Us
</Link>
<div
className={`${location.pathname === '/contact' ? 'border-b-[2px] border-primary text-primary' : 'text-grey'} flex items-center gap-2 h-full pl-4 pr-8`}
>
<Link
to="/contact"
className="h-full flex items-center justify-center"
>
Contact
</Link>
<FaAngleDown size="15" color="#424856" title="contact" />
</div>
</nav>
<div className="flex items-center gap-8 mr-8">
<div className="flex items-center gap-4">
<div className="relative">
<LuShoppingCart size="20" color="#424856" title="cart" />
<div className="flex items-center justify-center w-4 h-4 rounded-full bg-red-700 text-white absolute right-[-0.3rem] top-[-0.2rem] text-sm">
5
</div>
</div>
<FiHeart color="#424856" size="20" title="wishlist" />
</div>
<div className="xs:hidden lg:flex items-center gap-2">
<div className="w-8 h-8 rounded-full overflow-hidden">
<img
src="/avatar.jpg"
className="w-full h-full object-cover"
alt="profile"
/>
</div>
<h2 className="text-textBlack">Amanda Green</h2>
<FaAngleDown
size="15"
color="#424856"
title="toggleProfile"
onClick={() => setToggleProfileMenu(!toggleProfileMenu)}
/>
</div>
</div>
{toggleMenu && (
<div className="bg-white absolute z-20 top-16 flex flex-col items-start p-4 w-full gap-4 text-grey shadow-md border-b border-lightGrey">
<Link to="/">Home</Link>
<Link to="/shop">Shop</Link>
<Link to="/about">About</Link>
<Link to="contact">Contact</Link>
<div className="flex flex-col w-full gap-2 border-b-[1.5px] border-lightGrey py-2">
<div className="flex gap-2 w-full items-center">
<img src="/edit.png" width="20" height="20" alt="edit" />
<h2>Edit profile</h2>
</div>
<div className="flex gap-2 w-full items-center">
<img src="/settings.png" width="20" height="20" alt="settings" />
<h2>Preferences</h2>
</div>
</div>
<div className="flex items-center w-full justify-between pr-2 py-2 border-b-[1.5px] border-lightGrey">
<div className="flex gap-2 items-center">
<img src="/moon.png" width="20" height="20" alt="moon" />
<h2>Night mode</h2>
</div>
<div className="rounded-xl w-9 h-5 bg-custom-purple flex items-center justify-end px-1">
<div className="bg-white rounded-full w-3 h-3" />
</div>
</div>
<div className="flex gap-2 w-full items-center pt-1">
<img src="/help.png" width="20" height="20" alt="help" />
<h2>Help center</h2>
</div>
<div className="flex gap-2 w-full items-center border-t-[1.5px] border-lightGrey pt-1 mt-8">
<img src="/signout.png" width="20" height="20" alt="signout" />
<h2>Sign out</h2>
</div>
</div>
)}
{toggleProfileMenu && (
<div className="bg-white absolute z-20 top-16 right-0 flex flex-col items-center w-52 shadow-sm py-2 text-grey rounded-b-md border-l border-b border-lightGrey">
<div className="flex flex-col w-full gap-2 border-b-[1.5px] border-lightGrey py-2">
<div className="flex gap-2 w-full items-center px-2">
<img src="/edit.png" width="20" height="20" alt="edit" />
<h2>Edit profile</h2>
</div>
<div className="flex gap-2 w-full items-center px-2">
<img src="/settings.png" width="20" height="20" alt="settings" />
<h2>Preferences</h2>
</div>
</div>
<div className="flex items-center w-full justify-between pr-2 py-2 border-b-[1.5px] border-lightGrey">
<div className="flex gap-2 items-center px-2">
<img src="/moon.png" width="20" height="20" alt="moon" />
<h2>Night mode</h2>
</div>
<div className="rounded-xl w-9 h-5 bg-custom-purple flex items-center justify-end px-1">
<div className="bg-white rounded-full w-3 h-3" />
</div>
</div>
<div className="flex gap-2 w-full items-center px-2 pt-1">
<img src="/help.png" width="20" height="20" alt="help" />
<h2>Help center</h2>
</div>
<div className="flex gap-2 w-full items-center px-2 border-t-[1.5px] border-lightGrey pt-1 mt-8">
<img src="/signout.png" width="20" height="20" alt="signout" />
<h2>Sign out</h2>
</div>
</div>
)}
</div>
);
}

export default Navbar;
5 changes: 3 additions & 2 deletions src/layout/HomeLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { Outlet } from 'react-router-dom';
import Navbar from '@/components/Navbar';

function HomeLayout() {
return (
<div>
<div>Nav</div>
<Navbar />
<div>
<Outlet />
</div>
<div>Fotter</div>
<div>Footer</div>
</div>
);
}
Expand Down
5 changes: 5 additions & 0 deletions src/pages/About.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
function About() {
return <p>About</p>;
}

export default About;
5 changes: 5 additions & 0 deletions src/pages/Contact.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
function Contact() {
return <p>Contact</p>;
}

export default Contact;
5 changes: 5 additions & 0 deletions src/pages/Shop.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
function Shop() {
return <p>Shop</p>;
}

export default Shop;
6 changes: 6 additions & 0 deletions src/routes/AppRoutes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,19 @@ import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
import HomeLayout from '@/layout/HomeLayout';
import Home from '@/pages/Home';
import ErrorPage from '@/pages/ErrorPage';
import Shop from '@/pages/Shop';
import About from '@/pages/About';
import Contact from '@/pages/Contact';

function AppRoutes() {
return (
<Router>
<Routes>
<Route element={<HomeLayout />}>
<Route index path="/" element={<Home />} />
<Route path="/shop" element={<Shop />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
</Route>
<Route path="*" element={<ErrorPage />} />
{/* Add many routes as you want */}
Expand Down
6 changes: 4 additions & 2 deletions tailwind.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export default {
xs: '320px',
sm: '640px',
md: '768px',
lg: '1440px',
lg: '1024px',
},
extend: {
colors: {
Expand All @@ -18,7 +18,9 @@ export default {
grayDark: '#CCD0D8',
grayLight: '#F3F4F6',
redBg: '#DC2627',
black: '#171A1F',
textBlack: '#171A1F',
lightGrey: '#DEE1E6',
grey: '#565D6D',
},
fontFamily: {
Lexend: ['Lexend'],
Expand Down

0 comments on commit e8b9589

Please sign in to comment.