Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 59 additions & 56 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,66 +1,71 @@
import { HelmetProvider } from "react-helmet-async";
import { RouterProvider, createBrowserRouter } from "react-router-dom";
import { Footer } from "src/components/Footer";
import { Navbar } from "src/components/Navbar";
import { PrivateRoute } from "src/components/PrivateRoute";
import { RootLayout } from "src/components/RootLayout";
import { Home } from "src/pages";
import { AddProduct } from "src/pages/AddProduct";
import { EditProduct } from "src/pages/EditProduct";
import { IndividualProductPage } from "src/pages/Individual-product-page";
import { Marketplace } from "src/pages/Marketplace";

import { PrivateRoute } from "../src/components/PrivateRoute";
import { AddProduct } from "../src/pages/AddProduct";
import { EditProduct } from "../src/pages/EditProduct";
import { IndividualProductPage } from "../src/pages/Individual-product-page";
import { PageNotFound } from "../src/pages/PageNotFound";
import FirebaseProvider from "../src/utils/FirebaseProvider";
import { SavedProducts } from "./pages/SavedProducts";
import { PageNotFound } from "src/pages/PageNotFound";
import { SavedProducts } from "src/pages/SavedProducts";
import FirebaseProvider from "src/utils/FirebaseProvider";

const router = createBrowserRouter([
{
path: "/",
element: <Home />,
},
{
path: "/products",
element: (
<PrivateRoute>
<Marketplace />
</PrivateRoute>
),
},
{
path: "/add-product",
element: (
<PrivateRoute>
<AddProduct />
</PrivateRoute>
),
},
{
path: "/edit-product/:id",
element: (
<PrivateRoute>
<EditProduct />
</PrivateRoute>
),
},
{
path: "/products/:id",
element: (
<PrivateRoute>
<IndividualProductPage />
</PrivateRoute>
),
},
{
path: "/saved-products",
element: (
<PrivateRoute>
<SavedProducts />
</PrivateRoute>
),
},
{
path: "*",
element: <PageNotFound />,
element: <RootLayout />,
children: [
{
path: "/",
element: <Home />,
},
{
path: "/products",
element: (
<PrivateRoute>
<Marketplace />
</PrivateRoute>
),
},
{
path: "/add-product",
element: (
<PrivateRoute>
<AddProduct />
</PrivateRoute>
),
},
{
path: "/edit-product/:id",
element: (
<PrivateRoute>
<EditProduct />
</PrivateRoute>
),
},
{
path: "/products/:id",
element: (
<PrivateRoute>
<IndividualProductPage />
</PrivateRoute>
),
},
{
path: "/saved-products",
element: (
<PrivateRoute>
<SavedProducts />
</PrivateRoute>
),
},
{
path: "*",
element: <PageNotFound />,
},
],
},
]);

Expand All @@ -69,11 +74,9 @@ export default function App() {
<HelmetProvider>
<FirebaseProvider>
<div className="flex flex-col min-h-screen">
<Navbar />
<div className="flex-grow">
<RouterProvider router={router} />
</div>
<Footer />
</div>
</FirebaseProvider>
</HelmetProvider>
Expand Down
8 changes: 8 additions & 0 deletions frontend/src/components/Header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export function Header() {
return (
<>
<div className="bg-[url('./ucsd-pricecenter.png')] bg-[center_25%] bg-cover w-full h-[60%] shadow-[8px_8px_0px_rgba(246,174,45,0.6)] fixed -z-50"/>
<div className="h-[50vh]"/>
</>
);
}
172 changes: 124 additions & 48 deletions frontend/src/components/Navbar.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,79 @@
import { faBars, faCartShopping, faUser, faXmark, faHeart } from "@fortawesome/free-solid-svg-icons";
import { faHeart } from "@fortawesome/free-regular-svg-icons";
import {
faBars,
faCartShopping,
faMagnifyingGlass,
faUser,
faXmark,
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useContext, useEffect, useRef, useState } from "react";
import { HTMLAttributes, forwardRef, useContext, useEffect, useRef, useState } from "react";
import { useNavigate } from "react-router-dom";
import { FirebaseContext } from "src/utils/FirebaseProvider";

interface MiniSearchbarProps extends HTMLAttributes<HTMLDivElement> {
open: boolean;
onSubmit: React.FormEventHandler;
}

const MiniSearchbar = forwardRef<HTMLFormElement, MiniSearchbarProps>(
({ open, onSubmit, ...props }, ref) => {
return (
<div
className={`absolute mt-8 -translate-x-1/2 left-1/2 w-[40vh] p-4 bg-white shadow-ucsd-blue shadow-md
flex flex-col gap-4
rounded-lg transform transition-all duration-150 ease-out
${open ? "opacity-100 " : "opacity-0 pointer-events-none"}
`}
{...props}
>
<p className="text-md">Search the marketplace</p>
<form onSubmit={onSubmit} ref={ref}>
<div className={`flex flex-row w-full rounded-full border-2 p-2 text-[16px]`}>
<div className="items-center px-2 pointer-events-none">
<FontAwesomeIcon
icon={faMagnifyingGlass}
aria-label="faMagnifyingGlass"
className="text-gray-400"
/>
</div>
<input
name="query"
className={`w-full focus:outline-none text-[16px]`}
placeholder="Search UCSD"
/>
</div>
</form>
</div>
);
},
);

MiniSearchbar.displayName = "MiniSearchbar";

export function Navbar() {
const { user, signOutFromFirebase, openGoogleAuthentication } = useContext(FirebaseContext);
const [isMobileMenuOpen, setMobileMenuOpen] = useState(false);
const [isSearchBarOpen, setSearchbarOpen] = useState<boolean>(false);
const menuRef = useRef<HTMLUListElement>(null);
const buttonRef = useRef<HTMLButtonElement>(null);
const searchRef = useRef<HTMLFormElement>(null);
const navigate = useNavigate();

const toggleMobileMenu = () => {
setMobileMenuOpen(!isMobileMenuOpen);
};

const handleSearch = (e: React.FormEvent<HTMLFormElement>) => {
if (!searchRef.current) return;
e.preventDefault();
const formData = new FormData(searchRef.current);
const url = new URL("/products", window.location.origin);
url.searchParams.set("query", formData.get("query") as string);
navigate(url.pathname + url.search);
return;
};

useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (
Expand All @@ -23,6 +84,13 @@ export function Navbar() {
) {
setMobileMenuOpen(false);
}

if (searchRef.current && !searchRef.current.contains(event.target as Node)) {
setSearchbarOpen(false);
setTimeout(() => {
if (searchRef.current) searchRef.current.reset();
}, 100);
}
};

const handleResize = () => {
Expand All @@ -40,65 +108,73 @@ export function Navbar() {
};
}, []);

const tabStyling = "text-gray-400 hover:text-gray-800";
const selectedTabStyling = "text-ucsd-blue";

return (
<>
<nav className="bg-ucsd-blue text-white w-full h-12 max-h-12 p-2 flex items-center justify-between sticky top-0 z-50">
<nav className="font-rubik shadow-md shadow-ucsd-blue bg-white c left-0 right-0 min-w-0 mx-12 rounded-b-lg h-12 max-h-12 px-6 py-10 flex items-center justify-between fixed top-0 z-50">
{/* Desktop View */}
<button
className="font-jetbrains text-xl pl-2"
className="text-lg font-semibold"
onClick={() => (window.location.href = "/products")}
>
Low-Price Center
<span className="text-3xl text-ucsd-blue">Low </span>
<span className="text-3xl text-ucsd-gold">Price Center</span>
</button>
<ul className="hidden md:flex items-center space-x-4 text-xl">
<li>
<div
className={`hidden ${!user && "opacity-0"} md:flex flex-row gap-3 items-center justify-center`}
>
{[
{ label: "Shop", path: "/products" },
{ label: "Sell", path: "/sell" },
{ label: "Student Organizations", path: "/organizations" },
].map((val) => (
<button
hidden={user === null}
onClick={() => (window.location.href = "/products")}
className="font-inter px-4 py-1 bg-transparent border-transparent rounded hover:bg-ucsd-darkblue transition-colors"
key={val.label}
className={`${window.location.pathname === val.path ? selectedTabStyling : tabStyling}`}
onClick={() => {
window.location.href = val.path;
}}
>
<FontAwesomeIcon
className="text-lg pr-2"
icon={faCartShopping}
aria-label="Shopping Cart"
/>
Products
{val.label}
</button>
</li>
<li>
))}
</div>
<div className="hidden md:flex items-center text-2xl space-x-4">
<button
onClick={() => (window.location.href = "/saved-products")}
className={`${!user && "opacity-0"} w-12 h-12 text-xl flex items-center justify-center border-2 hover:bg-gray-300 rounded-full transition-colors`}
>
<FontAwesomeIcon icon={faHeart} aria-label="Heart Icon" />
</button>

<div className="relative">
<button
hidden={user === null}
onClick={() => (window.location.href = "/saved-products")}
className="font-inter px-4 py-1 bg-transparent border-transparent rounded hover:bg-ucsd-darkblue transition-colors"
onClick={() => {
setSearchbarOpen(true);
}}
className={`${!user && "opacity-0"} w-12 h-12 text-xl flex items-center justify-center border-2 hover:bg-gray-300 rounded-full transition-colors`}
>
<FontAwesomeIcon
className="text-lg pr-2"
icon={faHeart}
aria-label="Heart Icon"
/>
Saved
<FontAwesomeIcon icon={faMagnifyingGlass} aria-label="faMagnifyingGlass" />
</button>
</li>
<li>
{user ? (
<button
onClick={signOutFromFirebase}
className="font-inter px-4 py-1 bg-transparent border-transparent rounded hover:bg-ucsd-darkblue transition-colors"
>
<FontAwesomeIcon className="text-lg pr-2" icon={faUser} aria-label="User Icon" />
Sign Out
</button>
) : (
<button
onClick={openGoogleAuthentication}
className="font-inter px-4 py-1 bg-transparent border-transparent rounded hover:bg-ucsd-darkblue transition-colors"
>
<FontAwesomeIcon className="text-lg pr-2" icon={faUser} aria-label="User Icon" />
Sign In
</button>
)}
</li>
</ul>
<MiniSearchbar open={isSearchBarOpen} ref={searchRef} onSubmit={handleSearch} />
</div>

<button
onClick={() => (window.location.href = "/products")}
className={`${!user && "opacity-0"} w-12 h-12 text-xl flex items-center justify-center border-2 hover:bg-gray-300 rounded-full transition-colors`}
>
<FontAwesomeIcon icon={faCartShopping} aria-label="Shopping Cart" />
</button>

<button
onClick={user ? signOutFromFirebase : openGoogleAuthentication}
className="px-4 py-1 bg-transparent border-transparent rounded transition-colors"
>
{user ? "Log Out" : "Log In"}
</button>
</div>

{/* Mobile View */}
<div className="md:hidden relative">
Expand Down
13 changes: 13 additions & 0 deletions frontend/src/components/RootLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Outlet } from "react-router-dom";
import { Navbar } from "src/components/Navbar";

export const RootLayout = () => {
return (
<>
<Navbar />
<div className="flex-grow">
<Outlet />
</div>
</>
);
};
Loading