diff --git a/apps/web/src/components/dashboard/ProjectsContainer.tsx b/apps/web/src/components/dashboard/ProjectsContainer.tsx index be004035..26c90fbc 100644 --- a/apps/web/src/components/dashboard/ProjectsContainer.tsx +++ b/apps/web/src/components/dashboard/ProjectsContainer.tsx @@ -15,7 +15,8 @@ import { DashboardProjectsProps } from "@/types"; import Image from "next/image"; import { useFilterStore } from "@/store/useFilterStore"; import { usePathname } from "next/navigation"; -import { MagnifyingGlassIcon } from "@heroicons/react/24/outline"; +import { MagnifyingGlassIcon, FunnelIcon } from "@heroicons/react/24/outline"; +import { useState, useMemo } from "react"; type ProjectsContainerProps = { projects: DashboardProjectsProps[] }; @@ -58,6 +59,53 @@ export default function ProjectsContainer({ const { projectTitle } = useProjectTitleStore(); const { setShowFilters } = useFilterStore(); const isProjectsPage = pathname === "/dashboard/projects"; + const [searchQuery, setSearchQuery] = useState(""); + + // Client-side filtering of projects based on search query + // Memoized for performance optimization + const filteredProjects = useMemo(() => { + // Return all projects if no search query or empty projects array + if (!searchQuery.trim() || !projects || projects.length === 0) { + return projects || []; + } + + const query = searchQuery.toLowerCase().trim(); + + // Filter projects by checking all searchable fields + return projects.filter((project) => { + // Search in project name + const nameMatch = project.name?.toLowerCase().includes(query) ?? false; + + // Search in description + const descriptionMatch = project.description?.toLowerCase().includes(query) ?? false; + + // Search in primary language + const languageMatch = project.primaryLanguage?.toLowerCase().includes(query) ?? false; + + // Search in stage + const stageMatch = project.stage?.toLowerCase().includes(query) ?? false; + + // Search in popularity + const popularityMatch = project.popularity?.toLowerCase().includes(query) ?? false; + + // Search in competition level + const competitionMatch = project.competition?.toLowerCase().includes(query) ?? false; + + // Search in activity level + const activityMatch = project.activity?.toLowerCase().includes(query) ?? false; + + // Return true if any field matches the query + return ( + nameMatch || + descriptionMatch || + languageMatch || + stageMatch || + popularityMatch || + competitionMatch || + activityMatch + ); + }); + }, [projects, searchQuery]); return (
@@ -67,14 +115,61 @@ export default function ProjectsContainer({ {isProjectsPage && ( )}
+ {/* Search Input for Quick Filtering */} + {isProjectsPage && projects && projects.length > 0 && ( +
+
+ + setSearchQuery(e.target.value)} + className="w-full pl-10 pr-4 py-2.5 bg-[#15161a] border border-[#1a1a1d] rounded-md text-white text-sm placeholder:text-zinc-500 focus:outline-none focus:ring-2 focus:ring-ox-purple focus:border-transparent transition-all" + aria-label="Search projects" + /> + {searchQuery && ( + + )} +
+ {searchQuery && filteredProjects !== undefined && ( +

+ Showing {filteredProjects.length} of {projects.length} project{projects.length !== 1 ? 's' : ''} +

+ )} +
+ )} + {projects && projects.length > 0 ? (
- ))} + )) + ) : ( + + +
+ +

No projects found

+

+ Try adjusting your search query or{" "} + +

+
+
+
+ )}
@@ -169,7 +289,7 @@ export default function ProjectsContainer({

Find Your Next Project

- Click the 'Find projects' button above to discover open + Click the 'Filter Projects' button above to discover open source projects that match your interests