A project tracker for decentralized social media platforms, clients, and tools

feat: organize projects into sections by type (Platforms, Clients, Dev Tools)

Split the project display into separate sections based on project type to improve
organization and browsing experience. Each section shows a count badge and only
appears if it contains projects.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

Changed files
+68 -7
public
src
public/logos/blebbit.jpg

This is a binary file and will not be displayed.

public/logos/smoke-signal.jpg

This is a binary file and will not be displayed.

-1
src/App.tsx
···
<FilterToolbar
query={filters.query}
selectedTags={filters.tags}
-
selectedNetwork={filters.network}
availableTags={availableTags}
sort={filters.sort}
projectCount={filteredProjects.length}
-2
src/components/FilterToolbar.tsx
···
interface FilterToolbarProps {
query: string;
selectedTags: string[];
-
selectedNetwork: string;
availableTags: string[];
sort: FilterState['sort'];
projectCount: number;
···
export default function FilterToolbar({
query,
selectedTags,
-
selectedNetwork,
availableTags,
sort,
projectCount,
+68 -4
src/components/ProjectGrid.tsx
···
import type { Project } from '../types/project';
import ProjectCard from './ProjectCard';
+
import { useMemo } from 'react';
interface ProjectGridProps {
projects: Project[];
···
}
export default function ProjectGrid({ projects, loading }: ProjectGridProps) {
+
const projectsByType = useMemo(() => {
+
const grouped: Record<string, Project[]> = {
+
platform: [],
+
client: [],
+
'dev tool': [],
+
other: []
+
};
+
+
projects.forEach(project => {
+
const type = project.type.toLowerCase();
+
if (type === 'platform' || type === 'semi-platform') {
+
grouped.platform.push(project);
+
} else if (type === 'client') {
+
grouped.client.push(project);
+
} else if (type === 'dev tool') {
+
grouped['dev tool'].push(project);
+
} else {
+
grouped.other.push(project);
+
}
+
});
+
+
return grouped;
+
}, [projects]);
+
if (loading) {
return (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
···
);
}
+
const sections = [
+
{ title: 'Platforms', key: 'platform' },
+
{ title: 'Clients', key: 'client' },
+
{ title: 'Dev Tools', key: 'dev tool' }
+
];
+
return (
-
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
-
{projects.map(project => (
-
<ProjectCard key={project.id} project={project} />
-
))}
+
<div className="space-y-12">
+
{sections.map(section => {
+
const sectionProjects = projectsByType[section.key];
+
if (!sectionProjects || sectionProjects.length === 0) return null;
+
+
return (
+
<section key={section.key}>
+
<div className="flex items-center gap-3 mb-6">
+
<h2 className="text-2xl font-bold text-gray-100">{section.title}</h2>
+
<span className="text-sm text-gray-400 bg-gray-800 px-2 py-1 rounded-full">
+
{sectionProjects.length}
+
</span>
+
</div>
+
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
+
{sectionProjects.map(project => (
+
<ProjectCard key={project.id} project={project} />
+
))}
+
</div>
+
</section>
+
);
+
})}
+
+
{projectsByType.other.length > 0 && (
+
<section>
+
<div className="flex items-center gap-3 mb-6">
+
<h2 className="text-2xl font-bold text-gray-100">Other</h2>
+
<span className="text-sm text-gray-400 bg-gray-800 px-2 py-1 rounded-full">
+
{projectsByType.other.length}
+
</span>
+
</div>
+
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
+
{projectsByType.other.map(project => (
+
<ProjectCard key={project.id} project={project} />
+
))}
+
</div>
+
</section>
+
)}
</div>
);
}