"use client"; import { useEffect, useState, useMemo } from "react"; import { useSearchParams } from "next/navigation"; import Link from "next/link"; import Image from "next/image"; export default function ProjectsPage() { const searchParams = useSearchParams(); const initialQuery = searchParams.get("query") || ""; const [query, setQuery] = useState(initialQuery); const [debouncedQuery, setDebouncedQuery] = useState(initialQuery); const [projects, setProjects] = useState([]); const [loading, setLoading] = useState(true); const [categories] = useState([ "assets", "documents", "fixtures", "projects", "templates", "test files", "tools" ]); useEffect(() => { const timer = setTimeout(() => setDebouncedQuery(query), 300); return () => clearTimeout(timer); }, [query]); useEffect(() => { const url = new URL(`${process.env.NEXT_PUBLIC_API_BASE_URL}/items/projects`); url.searchParams.set("fields", "submission_id,title,uploader,category,tags,p_image.filename_disk,p_image.title"); url.searchParams.set("limit", "-1"); fetch(url.toString(), { cache: "no-store" }) .then((res) => res.json()) .then((data) => { setProjects(data.data || []); setLoading(false); }) .catch(() => setLoading(false)); }, []); const highlight = (text: string) => { if (!debouncedQuery) return text; const regex = new RegExp(`(${debouncedQuery})`, "gi"); return text?.replace(regex, '$1'); }; const normalize = (str: string) => str?.toLowerCase().replace(/[_\s]/g, ""); const filtered = useMemo(() => { const q = normalize(debouncedQuery); return projects.filter((entry) => { const fieldsToSearch = [ entry.title ?? "", entry.uploader ?? "", entry.category ?? "", Array.isArray(entry.tags) ? entry.tags.join(" ") : "", ]; return fieldsToSearch.filter(Boolean).some((field) => normalize(field).includes(q) ); }); }, [projects, debouncedQuery]); const tagCounts = useMemo(() => { const counts: Record = {}; projects.forEach((p) => { if (Array.isArray(p.tags)) { p.tags.forEach((tag) => { counts[tag] = (counts[tag] || 0) + 1; }); } }); return counts; }, [projects]); const popularTags = Object.entries(tagCounts) .sort((a, b) => (Number(b[1]) || 0) - (Number(a[1]) || 0)) .slice(0, 10) .map(([tag]) => tag); const recentTags = [...projects] .sort((a, b) => b.submission_id - a.submission_id) .slice(0, 10) .flatMap((p) => p.tags || []) .filter((tag, i, self) => self.indexOf(tag) === i) .slice(0, 10); const uniqueUploaders = new Set(projects.map((p) => p.uploader).filter(Boolean)).size; const totalTags = Object.keys(tagCounts).length; return (

Community Projects

setQuery(e.target.value)} placeholder="Search projects..." className="w-full dark:bg-background border border-border rounded-md p-2" /> ← Back to Main Menu

Popular Tags

{popularTags.length > 0 ? (
{popularTags.map((tag, i) => ( setQuery(tag)} title={tag}> {tag} ))}
) : (

No tags yet.

)}

Recent Tags

{recentTags.length > 0 ? (
{recentTags.map((tag, i) => ( setQuery(tag)} title={tag}> {tag} ))}
) : (

No recent tags.

)}

Project Stats

Total Projects: {projects.length}

Unique Uploaders: {uniqueUploaders}

Total Tags: {totalTags}

Browse by Category

{categories.map((cat, i) => ( setQuery(cat)}> {cat === "test_files" ? "test files" : cat} ))}

Submit a Project

Have a cool design, tool, or jig to share? Submit it to the community database.


{loading ? (

Loading projects...

) : filtered.length === 0 ? (

No projects found.

) : (
{filtered.map((project) => (
{project.p_image?.title
{project.title || "Untitled"}

Uploaded by: {project.uploader || "—"}

Category: {project.category || "—"}

{Array.isArray(project.tags) && project.tags.length > 0 ? project.tags.map((tag, i) => ( setQuery(tag)} title={tag} > {tag} )) : ""}
))}
)}
); }