diff --git a/src/app/(app)/(home)/[category]/[subcategory]/page.tsx b/src/app/(app)/(home)/[category]/[subcategory]/page.tsx
new file mode 100644
index 0000000..d9b637f
--- /dev/null
+++ b/src/app/(app)/(home)/[category]/[subcategory]/page.tsx
@@ -0,0 +1,14 @@
+interface Props {
+ params: Promise<{
+ category: string;
+ subcategory: string;
+ }>;
+}
+
+const Page = async ({ params }: Props) => {
+ const { category, subcategory } = await params;
+
+ return
Subcategory Page: {subcategory} in Category: {category}
;
+};
+
+export default Page;
diff --git a/src/app/(app)/(home)/[category]/page.tsx b/src/app/(app)/(home)/[category]/page.tsx
new file mode 100644
index 0000000..ce6694a
--- /dev/null
+++ b/src/app/(app)/(home)/[category]/page.tsx
@@ -0,0 +1,13 @@
+interface Props {
+ params: Promise<{
+ category: string;
+ }>;
+}
+
+const Page = async ({ params }: Props) => {
+ const { category } = await params;
+
+ return Category Page: {category}
;
+};
+
+export default Page;
diff --git a/src/app/(app)/(home)/layout.tsx b/src/app/(app)/(home)/layout.tsx
index 4f7f1c6..6f37dda 100644
--- a/src/app/(app)/(home)/layout.tsx
+++ b/src/app/(app)/(home)/layout.tsx
@@ -3,9 +3,9 @@ import { dehydrate, HydrationBoundary } from '@tanstack/react-query';
import { getQueryClient, trpc } from '@/trpc/server';
-import Navbar from './navbar';
-import Footer from './footer';
-import SearchFilters, { SearchFiltersSkeleton } from './search-filters';
+import Navbar from '@/modules/home/ui/components/navbar';
+import Footer from '@/modules/home/ui/components/footer';
+import SearchFilters, { SearchFiltersSkeleton } from '@/modules/home/ui/components/search-filters';
interface LayoutProps {
children: ReactNode;
diff --git a/src/app/(app)/(home)/search-filters/index.tsx b/src/app/(app)/(home)/search-filters/index.tsx
deleted file mode 100644
index 7a52ba1..0000000
--- a/src/app/(app)/(home)/search-filters/index.tsx
+++ /dev/null
@@ -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 (
-
- );
-};
-
-export const SearchFiltersSkeleton = () => {
- return (
-
- );
-};
-
-export default SearchFilters;
diff --git a/src/modules/home/ui/components/constants.ts b/src/modules/home/ui/components/constants.ts
new file mode 100644
index 0000000..bf990ce
--- /dev/null
+++ b/src/modules/home/ui/components/constants.ts
@@ -0,0 +1 @@
+export const DEFAULT_BG_COLOR = '#F5F5F5';
\ No newline at end of file
diff --git a/src/app/(app)/(home)/footer.tsx b/src/modules/home/ui/components/footer.tsx
similarity index 95%
rename from src/app/(app)/(home)/footer.tsx
rename to src/modules/home/ui/components/footer.tsx
index 28c4202..ac38fe8 100644
--- a/src/app/(app)/(home)/footer.tsx
+++ b/src/modules/home/ui/components/footer.tsx
@@ -1,11 +1,11 @@
-const Footer = () => {
- return (
-
- )
-}
-
+const Footer = () => {
+ return (
+
+ )
+}
+
export default Footer
\ No newline at end of file
diff --git a/src/app/(app)/(home)/navbar-sidebar.tsx b/src/modules/home/ui/components/navbar-sidebar.tsx
similarity index 96%
rename from src/app/(app)/(home)/navbar-sidebar.tsx
rename to src/modules/home/ui/components/navbar-sidebar.tsx
index 2654b22..b6e07de 100644
--- a/src/app/(app)/(home)/navbar-sidebar.tsx
+++ b/src/modules/home/ui/components/navbar-sidebar.tsx
@@ -1,65 +1,65 @@
-import { ScrollArea } from '@/components/ui/scroll-area';
-import {
- Sheet,
- SheetContent,
- SheetHeader,
- SheetTitle,
-} from '@/components/ui/sheet';
-import Link from 'next/link';
-import { ReactNode } from 'react';
-
-interface NavbarItem {
- href: string;
- children: ReactNode;
-}
-
-interface Props {
- items: NavbarItem[];
- open: boolean;
- onOpenChange: (open: boolean) => void;
-}
-
-const NavbarSidebar = ({ items, open, onOpenChange }: Props) => {
- return (
-
-
-
- Menu
-
-
-
- {items.map((item) => (
- onOpenChange(false)}
- >
- {item.children}
-
- ))}
-
-
- onOpenChange(false)}
- >
- Log in
-
-
- onOpenChange(false)}
- >
- Start Selling
-
-
-
-
-
- );
-};
-
-export default NavbarSidebar;
+import { ScrollArea } from '@/components/ui/scroll-area';
+import {
+ Sheet,
+ SheetContent,
+ SheetHeader,
+ SheetTitle,
+} from '@/components/ui/sheet';
+import Link from 'next/link';
+import { ReactNode } from 'react';
+
+interface NavbarItem {
+ href: string;
+ children: ReactNode;
+}
+
+interface Props {
+ items: NavbarItem[];
+ open: boolean;
+ onOpenChange: (open: boolean) => void;
+}
+
+const NavbarSidebar = ({ items, open, onOpenChange }: Props) => {
+ return (
+
+
+
+ Menu
+
+
+
+ {items.map((item) => (
+ onOpenChange(false)}
+ >
+ {item.children}
+
+ ))}
+
+
+ onOpenChange(false)}
+ >
+ Log in
+
+
+ onOpenChange(false)}
+ >
+ Start Selling
+
+
+
+
+
+ );
+};
+
+export default NavbarSidebar;
diff --git a/src/app/(app)/(home)/navbar.tsx b/src/modules/home/ui/components/navbar.tsx
similarity index 96%
rename from src/app/(app)/(home)/navbar.tsx
rename to src/modules/home/ui/components/navbar.tsx
index 0ac73eb..77eb820 100644
--- a/src/app/(app)/(home)/navbar.tsx
+++ b/src/modules/home/ui/components/navbar.tsx
@@ -1,147 +1,147 @@
-'use client';
-
-import { ReactNode, useState } from 'react';
-import Link from 'next/link';
-import { Poppins } from 'next/font/google';
-import { MenuIcon } from 'lucide-react';
-import { usePathname } from 'next/navigation';
-import { useQuery } from '@tanstack/react-query';
-
-import { cn } from '@/lib/utils';
-import { Button } from '@/components/ui/button';
-import { useTRPC } from '@/trpc/client';
-
-import NavbarSidebar from './navbar-sidebar';
-
-const poppins = Poppins({
- subsets: ['latin'],
- weight: ['700'],
-});
-
-interface NavbarItemProps {
- href: string;
- children: ReactNode;
- isActive?: boolean;
-}
-
-const navbarItems = [
- {
- href: '/',
- children: 'Home',
- },
- {
- href: '/about',
- children: 'About',
- },
- {
- href: '/features',
- children: 'Features',
- },
- {
- href: '/pricing',
- children: 'Pricing',
- },
- {
- href: '/contact',
- children: 'Contact',
- },
-];
-
-const NavbarItem = ({ href, children, isActive }: NavbarItemProps) => {
- return (
-
- );
-};
-
-const Navbar = () => {
- const pathname = usePathname();
- const [isSidebarOpen, setIsSidebarOpen] = useState(false);
-
- const trpc = useTRPC();
- const session = useQuery(trpc.auth.session.queryOptions());
-
- return (
-
- );
-};
-
-export default Navbar;
+'use client';
+
+import { ReactNode, useState } from 'react';
+import Link from 'next/link';
+import { Poppins } from 'next/font/google';
+import { MenuIcon } from 'lucide-react';
+import { usePathname } from 'next/navigation';
+import { useQuery } from '@tanstack/react-query';
+
+import { cn } from '@/lib/utils';
+import { Button } from '@/components/ui/button';
+import { useTRPC } from '@/trpc/client';
+
+import NavbarSidebar from './navbar-sidebar';
+
+const poppins = Poppins({
+ subsets: ['latin'],
+ weight: ['700'],
+});
+
+interface NavbarItemProps {
+ href: string;
+ children: ReactNode;
+ isActive?: boolean;
+}
+
+const navbarItems = [
+ {
+ href: '/',
+ children: 'Home',
+ },
+ {
+ href: '/about',
+ children: 'About',
+ },
+ {
+ href: '/features',
+ children: 'Features',
+ },
+ {
+ href: '/pricing',
+ children: 'Pricing',
+ },
+ {
+ href: '/contact',
+ children: 'Contact',
+ },
+];
+
+const NavbarItem = ({ href, children, isActive }: NavbarItemProps) => {
+ return (
+
+ );
+};
+
+const Navbar = () => {
+ const pathname = usePathname();
+ const [isSidebarOpen, setIsSidebarOpen] = useState(false);
+
+ const trpc = useTRPC();
+ const session = useQuery(trpc.auth.session.queryOptions());
+
+ return (
+
+ );
+};
+
+export default Navbar;
diff --git a/src/modules/home/ui/components/search-filters/breadcrumb-navigation.tsx b/src/modules/home/ui/components/search-filters/breadcrumb-navigation.tsx
new file mode 100644
index 0000000..e2ec71e
--- /dev/null
+++ b/src/modules/home/ui/components/search-filters/breadcrumb-navigation.tsx
@@ -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 (
+
+
+ {activeSubCategoryName ? (
+ <>
+
+
+ {activeCategoryName}
+
+
+
+ /
+
+
+
+ {activeSubCategoryName}
+
+
+ >
+ ) : (
+
+
+ {activeCategoryName}
+
+
+ )}
+
+
+ );
+};
diff --git a/src/app/(app)/(home)/search-filters/categories-sidebar.tsx b/src/modules/home/ui/components/search-filters/categories-sidebar.tsx
similarity index 96%
rename from src/app/(app)/(home)/search-filters/categories-sidebar.tsx
rename to src/modules/home/ui/components/search-filters/categories-sidebar.tsx
index 408d013..70a7d57 100644
--- a/src/app/(app)/(home)/search-filters/categories-sidebar.tsx
+++ b/src/modules/home/ui/components/search-filters/categories-sidebar.tsx
@@ -1,113 +1,113 @@
-import { useState } from 'react';
-import { useRouter } from 'next/navigation';
-import { useSuspenseQuery } from '@tanstack/react-query';
-import { ChevronLeftIcon, ChevronRightIcon } from 'lucide-react';
-
-import {
- Sheet,
- SheetContent,
- SheetHeader,
- SheetTitle,
-} from '@/components/ui/sheet';
-import { ScrollArea } from '@/components/ui/scroll-area';
-import { useTRPC } from '@/trpc/client';
-import { CategoriesGetManyOutput } from '@/modules/categories/type';
-
-interface CategoriesSidebarProps {
- open: boolean;
- onOpenChange: (open: boolean) => void;
-}
-
-const CategoriesSidebar = ({
- open,
- onOpenChange,
-}: CategoriesSidebarProps) => {
- const trpc = useTRPC();
- const { data } = useSuspenseQuery(trpc.categories.getMany.queryOptions());
-
- const router = useRouter();
- const [parentCategories, setParentCategories] = useState<
- CategoriesGetManyOutput | null
- >(null);
- const [selectedCategory, setSelectedCategory] =
- useState(null);
-
- // If we have parent categories, show those, otherwise show root categories
- const currentCategories = parentCategories ?? data ?? [];
-
- const handleOpenChange = (open: boolean) => {
- setSelectedCategory(null);
- setParentCategories(null);
- onOpenChange(open);
- };
-
- const handleCategoryClick = (category: CategoriesGetManyOutput[1]) => {
- if (category.subcategories && category.subcategories.length > 0) {
- setParentCategories(category.subcategories as CategoriesGetManyOutput);
- setSelectedCategory(category);
- } else {
- // This is a leaf category (no subcategories)
- if (parentCategories && selectedCategory) {
- router.push(`/${selectedCategory.slug}/${category.slug}`);
- } else {
- if (category.slug === 'all') {
- router.push('/');
- } else {
- router.push(`/${category.slug}`);
- }
- }
-
- handleOpenChange(false);
- }
- };
-
- const handleBackClick = () => {
- if (parentCategories) {
- setParentCategories(null);
- setSelectedCategory(null);
- }
- };
-
- const backgroundColor = selectedCategory?.color || 'white';
-
- return (
-
-
-
- Categories
-
-
-
- {parentCategories && (
-
- )}
-
- {currentCategories.map((category) => (
-
- ))}
-
-
-
- );
-};
-
-export default CategoriesSidebar;
+import { useState } from 'react';
+import { useRouter } from 'next/navigation';
+import { useSuspenseQuery } from '@tanstack/react-query';
+import { ChevronLeftIcon, ChevronRightIcon } from 'lucide-react';
+
+import {
+ Sheet,
+ SheetContent,
+ SheetHeader,
+ SheetTitle,
+} from '@/components/ui/sheet';
+import { ScrollArea } from '@/components/ui/scroll-area';
+import { useTRPC } from '@/trpc/client';
+import { CategoriesGetManyOutput } from '@/modules/categories/type';
+
+interface CategoriesSidebarProps {
+ open: boolean;
+ onOpenChange: (open: boolean) => void;
+}
+
+const CategoriesSidebar = ({
+ open,
+ onOpenChange,
+}: CategoriesSidebarProps) => {
+ const trpc = useTRPC();
+ const { data } = useSuspenseQuery(trpc.categories.getMany.queryOptions());
+
+ const router = useRouter();
+ const [parentCategories, setParentCategories] = useState<
+ CategoriesGetManyOutput | null
+ >(null);
+ const [selectedCategory, setSelectedCategory] =
+ useState(null);
+
+ // If we have parent categories, show those, otherwise show root categories
+ const currentCategories = parentCategories ?? data ?? [];
+
+ const handleOpenChange = (open: boolean) => {
+ setSelectedCategory(null);
+ setParentCategories(null);
+ onOpenChange(open);
+ };
+
+ const handleCategoryClick = (category: CategoriesGetManyOutput[1]) => {
+ if (category.subcategories && category.subcategories.length > 0) {
+ setParentCategories(category.subcategories as CategoriesGetManyOutput);
+ setSelectedCategory(category);
+ } else {
+ // This is a leaf category (no subcategories)
+ if (parentCategories && selectedCategory) {
+ router.push(`/${selectedCategory.slug}/${category.slug}`);
+ } else {
+ if (category.slug === 'all') {
+ router.push('/');
+ } else {
+ router.push(`/${category.slug}`);
+ }
+ }
+
+ handleOpenChange(false);
+ }
+ };
+
+ const handleBackClick = () => {
+ if (parentCategories) {
+ setParentCategories(null);
+ setSelectedCategory(null);
+ }
+ };
+
+ const backgroundColor = selectedCategory?.color || 'white';
+
+ return (
+
+
+
+ Categories
+
+
+
+ {parentCategories && (
+
+ )}
+
+ {currentCategories.map((category) => (
+
+ ))}
+
+
+
+ );
+};
+
+export default CategoriesSidebar;
diff --git a/src/app/(app)/(home)/search-filters/categories.tsx b/src/modules/home/ui/components/search-filters/categories.tsx
similarity index 94%
rename from src/app/(app)/(home)/search-filters/categories.tsx
rename to src/modules/home/ui/components/search-filters/categories.tsx
index a2277a7..90c6d3a 100644
--- a/src/app/(app)/(home)/search-filters/categories.tsx
+++ b/src/modules/home/ui/components/search-filters/categories.tsx
@@ -1,127 +1,132 @@
-'use client';
-
-import { ListFilterIcon } from 'lucide-react';
-import { useEffect, useRef, useState } from 'react';
-import { Button } from '@/components/ui/button';
-import { cn } from '@/lib/utils';
-
-import CategoryDropdown from './category-dropdown';
-
-import CategoriesSidebar from './categories-sidebar';
-import { CategoriesGetManyOutput } from '@/modules/categories/type';
-
-interface CategoriesProps {
- data: CategoriesGetManyOutput;
-}
-
-const Categories = ({ data }: CategoriesProps) => {
- const containerRef = useRef(null);
- const measureRef = useRef(null);
- const viewAllRef = useRef(null);
-
- const [visibleCount, setVisibleCount] = useState(data.length);
- const [isAnyHovered, setIsAnyHovered] = useState(false);
- const [isSidebarOpen, setIsSidebarOpen] = useState(false);
-
- const activeCategory = 'all';
-
- const activeCategoryIndex = data.findIndex(
- (cat) => cat.slug === activeCategory
- );
- const isActiveCategoryHidden =
- activeCategoryIndex >= visibleCount && activeCategoryIndex !== -1;
-
- useEffect(() => {
- const calculateVisible = () => {
- if (!containerRef.current || !measureRef.current || !viewAllRef.current)
- return;
-
- const containerWidth = containerRef.current.offsetWidth;
- const viewAllWidth = viewAllRef.current.offsetWidth;
- const availableWidth = containerWidth - viewAllWidth;
-
- const items = Array.from(measureRef.current.children);
- let totalWidth = 0;
- let visible = 0;
-
- for (const item of items) {
- const width = item.getBoundingClientRect().width;
-
- if (totalWidth + width > availableWidth) break;
-
- totalWidth += width;
- visible++;
- }
-
- setVisibleCount(visible);
- };
-
- const resizeObserver = new ResizeObserver(calculateVisible);
-
- resizeObserver.observe(containerRef.current!);
-
- return () => resizeObserver.disconnect();
- }, [data.length]);
-
- return (
-
- {/* Categories sidebar */}
-
-
- {/* Hidden div to measure all items */}
-
- {data.map((category) => (
-
-
-
- ))}
-
-
- {/* Visible items */}
-
setIsAnyHovered(true)}
- onMouseLeave={() => setIsAnyHovered(false)}
- >
- {data.slice(0, visibleCount).map((category) => (
-
-
-
- ))}
-
-
-
-
-
-
- );
-};
-
-export default Categories;
+'use client';
+
+import { useParams } from 'next/navigation';
+import { ListFilterIcon } from 'lucide-react';
+import { useEffect, useRef, useState } from 'react';
+
+import { CategoriesGetManyOutput } from '@/modules/categories/type';
+import { Button } from '@/components/ui/button';
+import { cn } from '@/lib/utils';
+
+import CategoryDropdown from './category-dropdown';
+import CategoriesSidebar from './categories-sidebar';
+
+interface CategoriesProps {
+ data: CategoriesGetManyOutput;
+}
+
+const Categories = ({ data }: CategoriesProps) => {
+ const params = useParams();
+
+ const containerRef = useRef(null);
+ const measureRef = useRef(null);
+ const viewAllRef = useRef(null);
+
+ const [visibleCount, setVisibleCount] = useState(data.length);
+ const [isAnyHovered, setIsAnyHovered] = useState(false);
+ const [isSidebarOpen, setIsSidebarOpen] = useState(false);
+
+ const categoryParam = params.category as string | undefined;
+ const activeCategory = categoryParam || 'all';
+
+ const activeCategoryIndex = data.findIndex(
+ (cat) => cat.slug === activeCategory
+ );
+ const isActiveCategoryHidden =
+ activeCategoryIndex >= visibleCount && activeCategoryIndex !== -1;
+
+ useEffect(() => {
+ const calculateVisible = () => {
+ if (!containerRef.current || !measureRef.current || !viewAllRef.current)
+ return;
+
+ const containerWidth = containerRef.current.offsetWidth;
+ const viewAllWidth = viewAllRef.current.offsetWidth;
+ const availableWidth = containerWidth - viewAllWidth;
+
+ const items = Array.from(measureRef.current.children);
+ let totalWidth = 0;
+ let visible = 0;
+
+ for (const item of items) {
+ const width = item.getBoundingClientRect().width;
+
+ if (totalWidth + width > availableWidth) break;
+
+ totalWidth += width;
+ visible++;
+ }
+
+ setVisibleCount(visible);
+ };
+
+ const resizeObserver = new ResizeObserver(calculateVisible);
+
+ resizeObserver.observe(containerRef.current!);
+
+ return () => resizeObserver.disconnect();
+ }, [data.length]);
+
+ return (
+
+ {/* Categories sidebar */}
+
+
+ {/* Hidden div to measure all items */}
+
+ {data.map((category) => (
+
+
+
+ ))}
+
+
+ {/* Visible items */}
+
setIsAnyHovered(true)}
+ onMouseLeave={() => setIsAnyHovered(false)}
+ >
+ {data.slice(0, visibleCount).map((category) => (
+
+
+
+ ))}
+
+
+
+
+
+
+ );
+};
+
+export default Categories;
diff --git a/src/app/(app)/(home)/search-filters/category-dropdown.tsx b/src/modules/home/ui/components/search-filters/category-dropdown.tsx
similarity index 96%
rename from src/app/(app)/(home)/search-filters/category-dropdown.tsx
rename to src/modules/home/ui/components/search-filters/category-dropdown.tsx
index edbf25b..0c4477a 100644
--- a/src/app/(app)/(home)/search-filters/category-dropdown.tsx
+++ b/src/modules/home/ui/components/search-filters/category-dropdown.tsx
@@ -1,86 +1,86 @@
-'use client';
-
-import { useRef, useState } from 'react';
-
-import { Button } from '@/components/ui/button';
-import { cn } from '@/lib/utils';
-import useDropdownPosition from './use-dropdown-position';
-import SubCategoryMenu from './subcategory-menu';
-
-import Link from 'next/link';
-import { CategoriesGetManyOutput } from '@/modules/categories/type';
-
-interface CategoryDropdownProps {
- category: CategoriesGetManyOutput[1];
- isActive?: boolean;
- isNavigationHovered?: boolean;
-}
-
-const CategoryDropdown = ({
- category,
- isActive,
- isNavigationHovered,
-}: CategoryDropdownProps) => {
- const [isOpen, setIsOpen] = useState(false);
- const dropdownRef = useRef(null);
- const { getDropdownPosition } = useDropdownPosition(dropdownRef);
- const dropdownPosition = getDropdownPosition();
-
- // TODO: Will improve in mobile screen
- // const toggleDropdown = () => {
- // if (category.subcategories?.docs?.length) {
- // setIsOpen(!isOpen);
- // }
- // };
-
- const handleMouseEnter = () => {
- if (category.subcategories) {
- setIsOpen(true);
- }
- };
-
- const handleMouseLeave = () => setIsOpen(false);
-
- return (
-
-
-
-
- {category.subcategories && category.subcategories.length > 0 && (
-
- )}
-
-
-
-
- );
-};
-
-export default CategoryDropdown;
+'use client';
+
+import { useRef, useState } from 'react';
+
+import { Button } from '@/components/ui/button';
+import { cn } from '@/lib/utils';
+import useDropdownPosition from './use-dropdown-position';
+import SubCategoryMenu from './subcategory-menu';
+
+import Link from 'next/link';
+import { CategoriesGetManyOutput } from '@/modules/categories/type';
+
+interface CategoryDropdownProps {
+ category: CategoriesGetManyOutput[1];
+ isActive?: boolean;
+ isNavigationHovered?: boolean;
+}
+
+const CategoryDropdown = ({
+ category,
+ isActive,
+ isNavigationHovered,
+}: CategoryDropdownProps) => {
+ const [isOpen, setIsOpen] = useState(false);
+ const dropdownRef = useRef(null);
+ const { getDropdownPosition } = useDropdownPosition(dropdownRef);
+ const dropdownPosition = getDropdownPosition();
+
+ // TODO: Will improve in mobile screen
+ // const toggleDropdown = () => {
+ // if (category.subcategories?.docs?.length) {
+ // setIsOpen(!isOpen);
+ // }
+ // };
+
+ const handleMouseEnter = () => {
+ if (category.subcategories) {
+ setIsOpen(true);
+ }
+ };
+
+ const handleMouseLeave = () => setIsOpen(false);
+
+ return (
+
+
+
+
+ {category.subcategories && category.subcategories.length > 0 && (
+
+ )}
+
+
+
+
+ );
+};
+
+export default CategoryDropdown;
diff --git a/src/modules/home/ui/components/search-filters/index.tsx b/src/modules/home/ui/components/search-filters/index.tsx
new file mode 100644
index 0000000..3dabc9c
--- /dev/null
+++ b/src/modules/home/ui/components/search-filters/index.tsx
@@ -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 (
+
+ );
+};
+
+export const SearchFiltersSkeleton = () => {
+ return (
+
+ );
+};
+
+export default SearchFilters;
diff --git a/src/app/(app)/(home)/search-filters/search-input.tsx b/src/modules/home/ui/components/search-filters/search-input.tsx
similarity index 95%
rename from src/app/(app)/(home)/search-filters/search-input.tsx
rename to src/modules/home/ui/components/search-filters/search-input.tsx
index 11f6c33..e22dc93 100644
--- a/src/app/(app)/(home)/search-filters/search-input.tsx
+++ b/src/modules/home/ui/components/search-filters/search-input.tsx
@@ -1,63 +1,63 @@
-'use client';
-
-import { useState } from 'react';
-import Link from 'next/link';
-import { useQuery } from '@tanstack/react-query';
-import { BookmarkCheckIcon, ListFilterIcon, SearchIcon } from 'lucide-react';
-
-import { Input } from '@/components/ui/input';
-import { Button } from '@/components/ui/button';
-import { useTRPC } from '@/trpc/client';
-
-import CategoriesSidebar from './categories-sidebar';
-
-interface SearchInputProps {
- disabled?: boolean;
-}
-
-const SearchInput = ({ disabled }: SearchInputProps) => {
- const [isSidebarOpen, setIsSidebarOpen] = useState(false);
-
- const trpc = useTRPC();
- const session = useQuery(trpc.auth.session.queryOptions());
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
- {session.data?.user && (
-
- )}
-
-
- );
-};
-
-export default SearchInput;
+'use client';
+
+import { useState } from 'react';
+import Link from 'next/link';
+import { useQuery } from '@tanstack/react-query';
+import { BookmarkCheckIcon, ListFilterIcon, SearchIcon } from 'lucide-react';
+
+import { Input } from '@/components/ui/input';
+import { Button } from '@/components/ui/button';
+import { useTRPC } from '@/trpc/client';
+
+import CategoriesSidebar from './categories-sidebar';
+
+interface SearchInputProps {
+ disabled?: boolean;
+}
+
+const SearchInput = ({ disabled }: SearchInputProps) => {
+ const [isSidebarOpen, setIsSidebarOpen] = useState(false);
+
+ const trpc = useTRPC();
+ const session = useQuery(trpc.auth.session.queryOptions());
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ {session.data?.user && (
+
+ )}
+
+
+ );
+};
+
+export default SearchInput;
diff --git a/src/app/(app)/(home)/search-filters/subcategory-menu.tsx b/src/modules/home/ui/components/search-filters/subcategory-menu.tsx
similarity index 95%
rename from src/app/(app)/(home)/search-filters/subcategory-menu.tsx
rename to src/modules/home/ui/components/search-filters/subcategory-menu.tsx
index 4c9cc76..e62d47c 100644
--- a/src/app/(app)/(home)/search-filters/subcategory-menu.tsx
+++ b/src/modules/home/ui/components/search-filters/subcategory-menu.tsx
@@ -1,60 +1,60 @@
-import Link from 'next/link';
-
-import { Category } from '@/payload-types';
-import { CategoriesGetManyOutput } from '@/modules/categories/type';
-
-interface SubCategoryMenuProps {
- category: CategoriesGetManyOutput[1];
- isOpen: boolean;
- position: {
- top: number;
- left: number;
- };
-}
-
-const SubCategoryMenu = ({
- category,
- isOpen,
- position,
-}: SubCategoryMenuProps) => {
- if (
- !isOpen ||
- !category.subcategories ||
- category.subcategories.length === 0
- ) {
- return null;
- }
-
- const backgroundColor = category.color || '#F5F5F5';
-
- return (
-
- {/* Invisible bridge to maintain hover */}
-
-
-
- {category.subcategories.map((subcategory: Category) => (
-
- {subcategory.name}
-
- ))}
-
-
-
- );
-};
-
-export default SubCategoryMenu;
+import Link from 'next/link';
+
+import { Category } from '@/payload-types';
+import { CategoriesGetManyOutput } from '@/modules/categories/type';
+
+interface SubCategoryMenuProps {
+ category: CategoriesGetManyOutput[1];
+ isOpen: boolean;
+ position: {
+ top: number;
+ left: number;
+ };
+}
+
+const SubCategoryMenu = ({
+ category,
+ isOpen,
+ position,
+}: SubCategoryMenuProps) => {
+ if (
+ !isOpen ||
+ !category.subcategories ||
+ category.subcategories.length === 0
+ ) {
+ return null;
+ }
+
+ const backgroundColor = category.color || '#F5F5F5';
+
+ return (
+
+ {/* Invisible bridge to maintain hover */}
+
+
+
+ {category.subcategories.map((subcategory: Category) => (
+
+ {subcategory.name}
+
+ ))}
+
+
+
+ );
+};
+
+export default SubCategoryMenu;
diff --git a/src/app/(app)/(home)/search-filters/use-dropdown-position.ts b/src/modules/home/ui/components/search-filters/use-dropdown-position.ts
similarity index 96%
rename from src/app/(app)/(home)/search-filters/use-dropdown-position.ts
rename to src/modules/home/ui/components/search-filters/use-dropdown-position.ts
index 006b1d6..0e90b4f 100644
--- a/src/app/(app)/(home)/search-filters/use-dropdown-position.ts
+++ b/src/modules/home/ui/components/search-filters/use-dropdown-position.ts
@@ -1,36 +1,36 @@
-import { RefObject } from "react";
-
-const useDropdownPosition = (ref: RefObject | RefObject) => {
- const getDropdownPosition = () => {
- if (!ref.current) return { top: 0, left: 0 };
-
- const rect = ref.current.getBoundingClientRect();
- const dropdownWidth = 240; // Width of dropdown (w-60 = 15rem = 240px)
-
- // Calculate the initial position
- let left = rect.left + window.scrollX;
- const top = rect.bottom + window.scrollY;
-
- // Check if dropdown would go off the right edge of the viewport
- if (left + dropdownWidth > window.innerWidth) {
- left = rect.right + window.scrollX - dropdownWidth;
-
- // If still off-screen, align to the left edge of the viewport with some padding
- if (left < 0) {
- left = window.innerWidth - dropdownWidth - 16; // 16px padding
- }
-
- if (left < 0) {
- left = 16
- }
-
- return { top, left };
- }
-
- return { top, left };
- }
-
- return { getDropdownPosition }
-}
-
+import { RefObject } from "react";
+
+const useDropdownPosition = (ref: RefObject | RefObject) => {
+ const getDropdownPosition = () => {
+ if (!ref.current) return { top: 0, left: 0 };
+
+ const rect = ref.current.getBoundingClientRect();
+ const dropdownWidth = 240; // Width of dropdown (w-60 = 15rem = 240px)
+
+ // Calculate the initial position
+ let left = rect.left + window.scrollX;
+ const top = rect.bottom + window.scrollY;
+
+ // Check if dropdown would go off the right edge of the viewport
+ if (left + dropdownWidth > window.innerWidth) {
+ left = rect.right + window.scrollX - dropdownWidth;
+
+ // If still off-screen, align to the left edge of the viewport with some padding
+ if (left < 0) {
+ left = window.innerWidth - dropdownWidth - 16; // 16px padding
+ }
+
+ if (left < 0) {
+ left = 16
+ }
+
+ return { top, left };
+ }
+
+ return { top, left };
+ }
+
+ return { getDropdownPosition }
+}
+
export default useDropdownPosition;
\ No newline at end of file