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 20, 2024
1 parent e1066b5 commit ad4fb09
Show file tree
Hide file tree
Showing 24 changed files with 5,481 additions and 2,941 deletions.
8,050 changes: 5,123 additions & 2,927 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 @@ -17,8 +17,10 @@
},
"dependencies": {
"@reduxjs/toolkit": "^2.2.5",
"@types/jest": "^29.5.12",
"axios": "^1.7.2",
"dotenv": "^16.4.5",
"jest": "^29.7.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-redux": "^9.1.2",
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.
24 changes: 23 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,27 @@
import {
createBrowserRouter,
createRoutesFromElements,
Route,
RouterProvider,
} from 'react-router-dom';
import Home from './pages/Home';
import Shop from './pages/Shop';
import About from './pages/About';
import Contact from './pages/Contact';

function App() {
return <p className="text-blue-400">Welcome to Dynamites E-commerce</p>;
const router = createBrowserRouter(
createRoutesFromElements(
<Route path="/">
<Route index element={<Home />} />
<Route path="/shop" element={<Shop />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
</Route>
)
);

return <RouterProvider router={router} />;
}

export default App;
12 changes: 0 additions & 12 deletions src/__test__/App.test.tsx

This file was deleted.

138 changes: 138 additions & 0 deletions src/__test__/navbar.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
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.getByText(/Contact/i);
expect(contactLink).toBeInTheDocument();

const cartIcon = screen.getByAltText(/cart/i);
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-custom-purple text-custom-purple'
);
});

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

const hamburgerIcon = screen.getByRole('img', { name: /hamburger/i });
fireEvent.click(hamburgerIcon);

// expect(screen.getByAltText(/home/i)).toBeInTheDocument();
});

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

expect(screen.queryByText(/edit profile/i)).not.toBeInTheDocument();

const profileIcon = screen.getAllByRole('img', { name: /down/i })[0];
fireEvent.click(profileIcon);

expect(screen.getByText(/edit profile/i)).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.getByText(/contact/i);
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.getAllByRole('img', { name: /down/i })[0];
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();
});
});
151 changes: 151 additions & 0 deletions src/components/navbar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import { Link, useLocation } from 'react-router-dom';
import { useState } from 'react';

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">
<img
src="/hamburger.png"
alt="hamburger"
width="20"
height="20"
className="md: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-custom-black font-bold">Dynamites</h2>
</div>
<nav className="xs:hidden md:flex items-center h-full">
<Link
to="/"
className={`${location.pathname === '/' ? 'border-b-[2px] border-custom-purple text-custom-purple' : 'text-custom-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-custom-purple text-custom-purple' : 'text-custom-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-custom-purple text-custom-purple' : 'text-custom-grey'} h-full flex items-center justify-center pl-4 pr-8`}
>
About Us
</Link>
<div
className={`${location.pathname === '/contact' ? 'border-b-[2px] border-custom-purple text-custom-purple' : 'text-custom-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>
<img src="/down.png" width="15" height="15" alt="contact" />
</div>
</nav>
<div className="flex items-center gap-8 mr-8">
<div className="flex items-center gap-4">
<div className="relative">
<img src="/cart.png" alt="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>
<img src="/heart.png" alt="heart" />
</div>
<div className="xs:hidden md: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-custom-black">Amanda Green</h2>
<img
src="/down.png"
width="15"
height="15"
alt="down"
onClick={() => setToggleProfileMenu(!toggleProfileMenu)}
/>
</div>
</div>
{toggleMenu && (
<div className="absolute top-16 flex flex-col items-start p-4 w-full gap-4 text-custom-grey shadow-sm border-b border-custom-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-custom-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-custom-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-custom-lightGrey pt-1 mt-8">
<img src="/signout.png" width="20" height="20" alt="signout" />
<h2>Sign out</h2>
</div>
</div>
)}
{toggleProfileMenu && (
<div className="absolute top-16 right-0 flex flex-col items-center w-52 shadow-sm py-2 text-custom-grey rounded-b-md border-l border-b border-custom-lightGrey">
<div className="flex flex-col w-full gap-2 border-b-[1.5px] border-custom-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-custom-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-custom-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;
7 changes: 7 additions & 0 deletions src/pages/about.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import Navbar from '@/components/Navbar';

function About() {
return <Navbar />;
}

export default About;
7 changes: 7 additions & 0 deletions src/pages/contact.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import Navbar from '@/components/Navbar';

function Contact() {
return <Navbar />;
}

export default Contact;
Empty file removed src/pages/home
Empty file.
7 changes: 7 additions & 0 deletions src/pages/home.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import Navbar from '@/components/Navbar';

function Home() {
return <Navbar />;
}

export default Home;
7 changes: 7 additions & 0 deletions src/pages/shop.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import Navbar from '@/components/Navbar';

function Shop() {
return <Navbar />;
}

export default Shop;
17 changes: 16 additions & 1 deletion tailwind.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,22 @@
export default {
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
theme: {
extend: {},
screens: {
xs: '320px',
sm: '640px',
md: '768px',
lg: '1440px',
},
extend: {
colors: {
custom: {
purple: '#6D31ED',
black: '#171A1F',
grey: '#565D6D',
lightGrey: '#DEE1E6',
},
},
},
},
plugins: [],
};

0 comments on commit ad4fb09

Please sign in to comment.