A project tracker for decentralized social media platforms, clients, and tools
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}