A project tracker for decentralized social media platforms, clients, and tools
at main 4.1 kB view raw
1import type { Project } from '../types/project'; 2import ProjectCard from './ProjectCard'; 3import { useMemo } from 'react'; 4 5interface ProjectGridProps { 6 projects: Project[]; 7 loading: boolean; 8} 9 10export default function ProjectGrid({ projects, loading }: ProjectGridProps) { 11 const projectsByType = useMemo(() => { 12 const grouped: Record<string, Project[]> = { 13 platform: [], 14 client: [], 15 'dev tool': [], 16 other: [] 17 }; 18 19 projects.forEach(project => { 20 const type = project.type.toLowerCase(); 21 if (type === 'platform' || type === 'semi-platform') { 22 grouped.platform.push(project); 23 } else if (type === 'client') { 24 grouped.client.push(project); 25 } else if (type === 'dev tool') { 26 grouped['dev tool'].push(project); 27 } else { 28 grouped.other.push(project); 29 } 30 }); 31 32 return grouped; 33 }, [projects]); 34 35 if (loading) { 36 return ( 37 <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4"> 38 {[...Array(12)].map((_, i) => ( 39 <div key={i} className="bg-gray-800 rounded-lg h-64 animate-pulse"> 40 <div className="p-4 space-y-3"> 41 <div className="flex items-center space-x-3"> 42 <div className="w-10 h-10 bg-gray-700 rounded-lg" /> 43 <div className="space-y-2"> 44 <div className="h-4 w-24 bg-gray-700 rounded" /> 45 <div className="h-3 w-32 bg-gray-700 rounded" /> 46 </div> 47 </div> 48 <div className="h-20 bg-gray-700 rounded" /> 49 <div className="flex gap-2"> 50 <div className="h-6 w-16 bg-gray-700 rounded-full" /> 51 <div className="h-6 w-20 bg-gray-700 rounded-full" /> 52 </div> 53 </div> 54 </div> 55 ))} 56 </div> 57 ); 58 } 59 60 if (projects.length === 0) { 61 return ( 62 <div className="text-center py-12"> 63 <svg className="w-16 h-16 mx-auto text-gray-600 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> 64 <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9.172 16.172a4 4 0 015.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /> 65 </svg> 66 <h3 className="text-xl font-medium text-gray-300 mb-2">No projects found</h3> 67 <p className="text-gray-500">Try adjusting your filters or search query</p> 68 </div> 69 ); 70 } 71 72 const sections = [ 73 { title: 'Platforms', key: 'platform' }, 74 { title: 'Clients', key: 'client' }, 75 { title: 'Dev Tools', key: 'dev tool' } 76 ]; 77 78 return ( 79 <div className="space-y-12"> 80 {sections.map(section => { 81 const sectionProjects = projectsByType[section.key]; 82 if (!sectionProjects || sectionProjects.length === 0) return null; 83 84 return ( 85 <section key={section.key}> 86 <div className="flex items-center gap-3 mb-6"> 87 <h2 className="text-2xl font-bold text-gray-100">{section.title}</h2> 88 <span className="text-sm text-gray-400 bg-gray-800 px-2 py-1 rounded-full"> 89 {sectionProjects.length} 90 </span> 91 </div> 92 <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4"> 93 {sectionProjects.map(project => ( 94 <ProjectCard key={project.id} project={project} /> 95 ))} 96 </div> 97 </section> 98 ); 99 })} 100 101 {projectsByType.other.length > 0 && ( 102 <section> 103 <div className="flex items-center gap-3 mb-6"> 104 <h2 className="text-2xl font-bold text-gray-100">Other</h2> 105 <span className="text-sm text-gray-400 bg-gray-800 px-2 py-1 rounded-full"> 106 {projectsByType.other.length} 107 </span> 108 </div> 109 <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4"> 110 {projectsByType.other.map(project => ( 111 <ProjectCard key={project.id} project={project} /> 112 ))} 113 </div> 114 </section> 115 )} 116 </div> 117 ); 118}