mirror of
https://github.com/Nezumi-2711/multitenant-ecommerce.git
synced 2026-09-22 13:38:34 +00:00
feat: add category pages
This commit is contained in:
@@ -0,0 +1,14 @@
|
|||||||
|
interface Props {
|
||||||
|
params: Promise<{
|
||||||
|
category: string;
|
||||||
|
subcategory: string;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Page = async ({ params }: Props) => {
|
||||||
|
const { category, subcategory } = await params;
|
||||||
|
|
||||||
|
return <div>Subcategory Page: {subcategory} in Category: {category}</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Page;
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
interface Props {
|
||||||
|
params: Promise<{
|
||||||
|
category: string;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Page = async ({ params }: Props) => {
|
||||||
|
const { category } = await params;
|
||||||
|
|
||||||
|
return <div>Category Page: {category}</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Page;
|
||||||
@@ -3,9 +3,9 @@ import { dehydrate, HydrationBoundary } from '@tanstack/react-query';
|
|||||||
|
|
||||||
import { getQueryClient, trpc } from '@/trpc/server';
|
import { getQueryClient, trpc } from '@/trpc/server';
|
||||||
|
|
||||||
import Navbar from './navbar';
|
import Navbar from '@/modules/home/ui/components/navbar';
|
||||||
import Footer from './footer';
|
import Footer from '@/modules/home/ui/components/footer';
|
||||||
import SearchFilters, { SearchFiltersSkeleton } from './search-filters';
|
import SearchFilters, { SearchFiltersSkeleton } from '@/modules/home/ui/components/search-filters';
|
||||||
|
|
||||||
interface LayoutProps {
|
interface LayoutProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
'use client';
|
|
||||||
|
|
||||||
import { useSuspenseQuery } from '@tanstack/react-query';
|
|
||||||
|
|
||||||
import { useTRPC } from '@/trpc/client';
|
|
||||||
|
|
||||||
import Categories from './categories';
|
|
||||||
import SearchInput from './search-input';
|
|
||||||
|
|
||||||
const SearchFilters = () => {
|
|
||||||
const trpc = useTRPC();
|
|
||||||
const { data } = useSuspenseQuery(trpc.categories.getMany.queryOptions());
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="px-4 lg:px-12 py-8 border-b flex flex-col gap-4 w-full bg-[#F5F5F5]">
|
|
||||||
<SearchInput />
|
|
||||||
|
|
||||||
<div className="hidden lg:block">
|
|
||||||
<Categories data={data} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const SearchFiltersSkeleton = () => {
|
|
||||||
return (
|
|
||||||
<div className="px-4 lg:px-12 py-8 border-b flex flex-col gap-4 w-full bg-[#F5F5F5]">
|
|
||||||
<SearchInput disabled />
|
|
||||||
|
|
||||||
<div className="hidden lg:block">
|
|
||||||
<div className="h-11" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default SearchFilters;
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export const DEFAULT_BG_COLOR = '#F5F5F5';
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import {
|
||||||
|
Breadcrumb,
|
||||||
|
BreadcrumbItem,
|
||||||
|
BreadcrumbLink,
|
||||||
|
BreadcrumbList,
|
||||||
|
BreadcrumbPage,
|
||||||
|
BreadcrumbSeparator,
|
||||||
|
} from '@/components/ui/breadcrumb';
|
||||||
|
import Link from 'next/link';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
activeCategoryName?: string | null;
|
||||||
|
activeCategory?: string | null;
|
||||||
|
activeSubCategoryName?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const BreadcrumbNavigation = ({
|
||||||
|
activeCategory,
|
||||||
|
activeCategoryName,
|
||||||
|
activeSubCategoryName,
|
||||||
|
}: Props) => {
|
||||||
|
if (!activeCategoryName || activeCategory === 'all') return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Breadcrumb>
|
||||||
|
<BreadcrumbList>
|
||||||
|
{activeSubCategoryName ? (
|
||||||
|
<>
|
||||||
|
<BreadcrumbItem>
|
||||||
|
<BreadcrumbLink
|
||||||
|
asChild
|
||||||
|
className="text-xl font-medium underline text-primary"
|
||||||
|
>
|
||||||
|
<Link href={`/${activeCategory}`}>{activeCategoryName}</Link>
|
||||||
|
</BreadcrumbLink>
|
||||||
|
</BreadcrumbItem>
|
||||||
|
<BreadcrumbSeparator className="text-primary font-medium text-lg">
|
||||||
|
/
|
||||||
|
</BreadcrumbSeparator>
|
||||||
|
<BreadcrumbItem>
|
||||||
|
<BreadcrumbPage className="text-xl font-medium">
|
||||||
|
{activeSubCategoryName}
|
||||||
|
</BreadcrumbPage>
|
||||||
|
</BreadcrumbItem>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<BreadcrumbItem>
|
||||||
|
<BreadcrumbPage className="text-xl font-medium">
|
||||||
|
{activeCategoryName}
|
||||||
|
</BreadcrumbPage>
|
||||||
|
</BreadcrumbItem>
|
||||||
|
)}
|
||||||
|
</BreadcrumbList>
|
||||||
|
</Breadcrumb>
|
||||||
|
);
|
||||||
|
};
|
||||||
+8
-3
@@ -1,20 +1,23 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
import { useParams } from 'next/navigation';
|
||||||
import { ListFilterIcon } from 'lucide-react';
|
import { ListFilterIcon } from 'lucide-react';
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
|
import { CategoriesGetManyOutput } from '@/modules/categories/type';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
import CategoryDropdown from './category-dropdown';
|
import CategoryDropdown from './category-dropdown';
|
||||||
|
|
||||||
import CategoriesSidebar from './categories-sidebar';
|
import CategoriesSidebar from './categories-sidebar';
|
||||||
import { CategoriesGetManyOutput } from '@/modules/categories/type';
|
|
||||||
|
|
||||||
interface CategoriesProps {
|
interface CategoriesProps {
|
||||||
data: CategoriesGetManyOutput;
|
data: CategoriesGetManyOutput;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Categories = ({ data }: CategoriesProps) => {
|
const Categories = ({ data }: CategoriesProps) => {
|
||||||
|
const params = useParams();
|
||||||
|
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const measureRef = useRef<HTMLDivElement>(null);
|
const measureRef = useRef<HTMLDivElement>(null);
|
||||||
const viewAllRef = useRef<HTMLDivElement>(null);
|
const viewAllRef = useRef<HTMLDivElement>(null);
|
||||||
@@ -23,7 +26,8 @@ const Categories = ({ data }: CategoriesProps) => {
|
|||||||
const [isAnyHovered, setIsAnyHovered] = useState(false);
|
const [isAnyHovered, setIsAnyHovered] = useState(false);
|
||||||
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
|
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
|
||||||
|
|
||||||
const activeCategory = 'all';
|
const categoryParam = params.category as string | undefined;
|
||||||
|
const activeCategory = categoryParam || 'all';
|
||||||
|
|
||||||
const activeCategoryIndex = data.findIndex(
|
const activeCategoryIndex = data.findIndex(
|
||||||
(cat) => cat.slug === activeCategory
|
(cat) => cat.slug === activeCategory
|
||||||
@@ -107,6 +111,7 @@ const Categories = ({ data }: CategoriesProps) => {
|
|||||||
|
|
||||||
<div ref={viewAllRef} className="shrink-0">
|
<div ref={viewAllRef} className="shrink-0">
|
||||||
<Button
|
<Button
|
||||||
|
variant="elevated"
|
||||||
className={cn(
|
className={cn(
|
||||||
'h-11 px-4 bg-transparent border-transparent rounded-full hover:bg-white hover:border-primary text-black',
|
'h-11 px-4 bg-transparent border-transparent rounded-full hover:bg-white hover:border-primary text-black',
|
||||||
isActiveCategoryHidden &&
|
isActiveCategoryHidden &&
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useSuspenseQuery } from '@tanstack/react-query';
|
||||||
|
import { useParams } from 'next/navigation';
|
||||||
|
|
||||||
|
import { useTRPC } from '@/trpc/client';
|
||||||
|
|
||||||
|
import Categories from './categories';
|
||||||
|
import SearchInput from './search-input';
|
||||||
|
import { DEFAULT_BG_COLOR } from '../constants';
|
||||||
|
import { BreadcrumbNavigation } from './breadcrumb-navigation';
|
||||||
|
|
||||||
|
const SearchFilters = () => {
|
||||||
|
const trpc = useTRPC();
|
||||||
|
const { data } = useSuspenseQuery(trpc.categories.getMany.queryOptions());
|
||||||
|
const params = useParams();
|
||||||
|
const categoryParam = params.category as string | undefined;
|
||||||
|
const activeCategory = categoryParam || 'all';
|
||||||
|
const activeCategoryData = data.find(
|
||||||
|
(category) => category.slug === activeCategory
|
||||||
|
);
|
||||||
|
const activeCategoryColor = activeCategoryData?.color || DEFAULT_BG_COLOR;
|
||||||
|
const activeCategoryName = activeCategoryData?.name || null;
|
||||||
|
const activeSubCategory = params.subcategory as string | undefined;
|
||||||
|
const activeSubCategoryName =
|
||||||
|
activeCategoryData?.subcategories.find(
|
||||||
|
(sub) => sub.slug === activeSubCategory
|
||||||
|
)?.name || null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="px-4 lg:px-12 py-8 border-b flex flex-col gap-4 w-full bg-[#F5F5F5]"
|
||||||
|
style={{
|
||||||
|
backgroundColor: activeCategoryColor,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SearchInput />
|
||||||
|
|
||||||
|
<div className="hidden lg:block">
|
||||||
|
<Categories data={data} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<BreadcrumbNavigation
|
||||||
|
activeCategory={activeCategory}
|
||||||
|
activeCategoryName={activeCategoryName}
|
||||||
|
activeSubCategoryName={activeSubCategoryName}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SearchFiltersSkeleton = () => {
|
||||||
|
return (
|
||||||
|
<div className="px-4 lg:px-12 py-8 border-b flex flex-col gap-4 w-full bg-[#F5F5F5]">
|
||||||
|
<SearchInput disabled />
|
||||||
|
|
||||||
|
<div className="hidden lg:block">
|
||||||
|
<div className="h-11" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SearchFilters;
|
||||||
Reference in New Issue
Block a user