Skip to content

Commit

Permalink
Some UI updates.
Browse files Browse the repository at this point in the history
  • Loading branch information
david emioma committed Mar 24, 2024
1 parent a2f5800 commit fb09b1c
Show file tree
Hide file tree
Showing 29 changed files with 1,318 additions and 380 deletions.
8 changes: 7 additions & 1 deletion __tests__/UserRole.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ import { render, screen, fireEvent, waitFor } from "@testing-library/react";

jest.mock("next-auth/react");

jest.mock("next/navigation", () => ({
useRouter: jest.fn(),
useParams: jest.fn(),
usePathname: jest.fn(),
}));

jest.mock("@tanstack/react-query", () => ({
useQuery: jest.fn(),
}));
Expand Down Expand Up @@ -194,7 +200,7 @@ describe("Checking user roles (No User)", () => {
render(<NavBar />);

waitFor(() => {
expect(screen.getByTestId("nav-sign-in-btn")).not.toBeVisible();
expect(screen.getByTestId("nav-sign-in-btn")).toBeVisible();
});
});

Expand Down
25 changes: 25 additions & 0 deletions app/(create-store)/_components/NavBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"use client";

import React from "react";
import Link from "next/link";
import Logo from "@/components/Logo";
import { ArrowLeft } from "lucide-react";
import Container from "@/components/Container";

const NavBar = () => {
return (
<nav className="fixed top-0 inset-x-0 z-40 bg-white h-16 flex items-center border-b border-gray-200 shadow-sm">
<Container>
<div className="flex items-center gap-5">
<Link href="/">
<ArrowLeft className="w-5 h-5" />
</Link>

<Logo />
</div>
</Container>
</nav>
);
};

export default NavBar;
7 changes: 6 additions & 1 deletion app/(create-store)/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { redirect } from "next/navigation";
import { getFirstStoreByUserId } from "@/data/store";
import { currentRole, currentUser } from "@/lib/auth";
import NavBar from "./_components/NavBar";

const CreateStoreLayout = async ({
children,
Expand All @@ -26,7 +27,11 @@ const CreateStoreLayout = async ({
}

return (
<div className="relative min-h-full w-full bg-gray-100">{children}</div>
<div className="relative min-h-full w-full bg-gray-100">
<NavBar />

<main className="pt-20 pb-10">{children}</main>
</div>
);
};

Expand Down
31 changes: 22 additions & 9 deletions app/(create-store)/store/page.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,29 @@
"use client";

import { useEffect } from "react";
import useStoreModal from "@/hooks/use-store-modal";
import Container from "@/components/Container";
import StoreForm from "@/components/StoreForm";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";

export default function CreateStorePage() {
const storeModal = useStoreModal();
return (
<Container>
<Card className="w-full max-w-md mx-auto">
<CardHeader>
<CardTitle>Become a seller</CardTitle>

useEffect(() => {
if (!storeModal.isOpen) {
storeModal.onOpen();
}
}, [storeModal.isOpen, storeModal.onOpen]);
<CardDescription>Create your store.</CardDescription>
</CardHeader>

return null;
<CardContent>
<StoreForm />
</CardContent>
</Card>
</Container>
);
}
7 changes: 6 additions & 1 deletion app/(marketing)/_components/NavBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import React from "react";
import Link from "next/link";
import SearchBar from "./SearchBar";
import Logo from "@/components/Logo";
import Cart from "@/components/cart/Cart";
import Container from "@/components/Container";
Expand All @@ -15,9 +16,13 @@ const NavBar = () => {
return (
<nav className="fixed top-0 inset-x-0 z-40 bg-white h-16 flex items-center border-b border-gray-200 shadow-sm">
<Container>
<div className="flex items-center justify-between">
<div className="flex items-center justify-between gap-5">
<Logo />

<div className="hidden md:flex md:flex-1 max-w-lg">
<SearchBar />
</div>

{user ? (
<div className="flex items-center gap-2 sm:gap-4">
<UserAccount />
Expand Down
8 changes: 5 additions & 3 deletions app/(marketing)/_components/Product.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import React, { useEffect, useState } from "react";
import React from "react";
import ProductImg from "./ProductImg";
import { HomeProductType } from "@/types";
import { useRouter } from "next/navigation";
Expand All @@ -20,7 +20,7 @@ const Product = ({ product }: Props) => {

return (
<div
className="bg-white border border-gray-300 rounded-b-lg cursor-pointer shadow-sm hover:scale-105 transition-transform duration-300"
className="bg-white border border-gray-300 rounded-b-lg cursor-pointer shadow-sm transition"
onClick={onClick}
data-testid="product-item"
data-cy={`feed-product-${product.id}`}
Expand All @@ -39,11 +39,13 @@ const Product = ({ product }: Props) => {

<p className="text-sm text-gray-500">{product.category?.name}</p>

{product?.reviews?.length > 0 && (
{product?.reviews?.length > 0 ? (
<AverageRating
className="my-2"
ratings={product.reviews.map((review) => review.value)}
/>
) : (
<div className="py-3" />
)}

<div className="text-sm my-2">
Expand Down
49 changes: 49 additions & 0 deletions app/(marketing)/_components/SearchBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
"use client";

import React, { useState } from "react";
import { useRouter } from "next/navigation";
import { XIcon, SearchIcon } from "lucide-react";

const SearchBar = () => {
const router = useRouter();

const [value, setValue] = useState("");

const onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();

if (value.trim() === "") return;

router.push(`/products/search?query=${value}`);
};

return (
<form
onSubmit={onSubmit}
className="w-full h-10 bg-background flex gap-3 border border-input px-3 py-2 rounded-md text-sm ring-offset-background "
>
<input
className="w-full flex-1 outline-none"
placeholder="Search..."
value={value}
onChange={(e) => setValue(e.target.value)}
/>

{value.trim() && (
<button
type="button"
className="flex items-center justify-center"
onClick={() => setValue("")}
>
<XIcon className="w-4 h-4" />
</button>
)}

<button type="submit" className="flex items-center justify-center">
<SearchIcon className="w-4 h-4" />
</button>
</form>
);
};

export default SearchBar;
5 changes: 5 additions & 0 deletions app/(marketing)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { ArrowRight } from "lucide-react";
import Heading from "@/components/Heading";
import Container from "@/components/Container";
import { Button } from "@/components/ui/button";
import SearchBar from "./_components/SearchBar";
import { getHomePageProducts } from "@/data/product";

export default async function Home() {
Expand Down Expand Up @@ -86,6 +87,10 @@ export default async function Home() {
</div>
</div>

<div className="w-full max-w-lg px-4 md:hidden">
<SearchBar />
</div>

<Container>
<Heading title="Products" description="View all product" />

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
"use client";

import React, { useEffect, useState } from "react";
import qs from "query-string";
import { XIcon } from "lucide-react";
import { useRouter } from "next/navigation";
import { Input } from "@/components/ui/input";

type Props = {
storeId: string;
productId: string;
};

const ProductFilters = ({ storeId, productId }: Props) => {
const router = useRouter();

const [value, setValue] = useState("");

const [debounceValue, setDebounceValue] = useState("");

useEffect(() => {
setTimeout(() => {
setDebounceValue(value);
}, 1000);
}, [value]);

useEffect(() => {
const pushToUrl = () => {
const url = qs.stringifyUrl(
{
url: `/products/${productId}/stores/${storeId}`,
query: {
search: debounceValue,
},
},
{ skipNull: true }
);

router.push(url);
};

pushToUrl();
}, [debounceValue, productId, storeId]);

return (
<div className="relative w-full max-w-96">
<Input
className="w-full rounded-lg pr-6"
placeholder="Search Product"
value={value}
onChange={(e) => setValue(e.target.value)}
/>

{value.trim() && (
<button
className="absolute right-3 top-1/2 -translate-y-1/2"
onClick={() => setValue("")}
>
<XIcon className="w-4 h-4" />
</button>
)}
</div>
);
};

export default ProductFilters;
Original file line number Diff line number Diff line change
@@ -1,29 +1,42 @@
"use client";

import React, { useEffect } from "react";
import React, { useEffect, useRef } from "react";
import Empty from "@/components/Empty";
import { HomeProductType } from "@/types";
import Spinner from "@/components/Spinner";
import ProductSkeleton from "@/components/ProductSkeleton";
import Product from "@/app/(marketing)/_components/Product";
import { INFINITE_SCROLL_PAGINATION_RESULTS } from "@/lib/utils";
import useUnlimitedScrolling from "@/hooks/use-unlimited-scrolling";

type Props = {
storeId: string;
productId: string;
searchValue: string;
initialData: HomeProductType[];
};

const StoreFeed = ({ storeId, productId, initialData }: Props) => {
const { ref, entry, data, error, fetchNextPage, isFetchingNextPage } =
useUnlimitedScrolling({
key: "feed-products",
query: `/api/products/${productId}/stores/${storeId}?limit=${INFINITE_SCROLL_PAGINATION_RESULTS}`,
initialData,
});
const StoreFeed = ({ productId, storeId, searchValue, initialData }: Props) => {
const productRef = useRef<HTMLDivElement>(null);

const {
ref,
entry,
data,
error,
isLoading,
fetchNextPage,
isFetchingNextPage,
} = useUnlimitedScrolling({
key: ["search-store-products", searchValue],
query: `/api/products/${productId}/stores/${storeId}?q=${
searchValue || ""
}}&limit=${INFINITE_SCROLL_PAGINATION_RESULTS}`,
initialData,
});

//@ts-ignore
const products: ProductType[] =
const products: HomeProductType[] =
data?.pages?.flatMap((page) => page) ?? initialData;

//When you scroll to the bottom it triggers the fetchNextPage() to fetch more products
Expand All @@ -33,14 +46,31 @@ const StoreFeed = ({ storeId, productId, initialData }: Props) => {
}
}, [entry, fetchNextPage]);

if (products.length === 0) {
useEffect(() => {
productRef?.current?.scrollIntoView({ behavior: "smooth" });
}, [searchValue]);

if (isLoading) {
return (
<div className="grid sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-5">
{new Array(20).fill("").map((_, i) => (
<ProductSkeleton key={i} />
))}
</div>
);
}

if (products?.length === 0) {
return <Empty message="Sorry, no products found! Try again later." />;
}

return (
<>
<div className="grid sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-5">
{products.map((product, i) => {
<div
ref={productRef}
className="grid sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-5"
>
{products?.map((product, i) => {
if (i === products.length - 1) {
return (
<div key={product.id} ref={ref}>
Expand Down
Loading

0 comments on commit fb09b1c

Please sign in to comment.