import { useState, useEffect, useMemo } from 'react'; import type { Network, Project, FilterState } from './types/project'; import TopBar from './components/TopBar'; import FilterToolbar from './components/FilterToolbar'; import ProjectGrid from './components/ProjectGrid'; import { filterAndSortProjects, getTagsByNetwork } from './utils/projectUtils'; function App() { const [projects, setProjects] = useState([]); const [loading, setLoading] = useState(true); const [filters, setFilters] = useState({ network: 'atproto', query: '', tags: [], sort: 'stars' }); useEffect(() => { fetch('/data/projects.json') .then(res => res.json()) .then(data => { setProjects(data); setLoading(false); }) .catch(err => { console.error('Failed to load projects:', err); setLoading(false); }); }, []); useEffect(() => { const params = new URLSearchParams({ network: filters.network, q: filters.query, tags: filters.tags.join(','), sort: filters.sort }); const newUrl = `${window.location.pathname}?${params.toString()}`; window.history.replaceState({}, '', newUrl); }, [filters]); // Calculate available tags from actual project data const tagsByNetwork = useMemo(() => getTagsByNetwork(projects), [projects]); const availableTags = tagsByNetwork[filters.network] || []; const handleNetworkChange = (network: Network) => { setFilters(prev => { // Clear tags that aren't available in the new network const newNetworkTags = tagsByNetwork[network] || []; const filteredTags = prev.tags.filter(tag => newNetworkTags.includes(tag) ); return { ...prev, network, tags: filteredTags }; }); }; const handleSearchChange = (query: string) => { setFilters(prev => ({ ...prev, query })); }; const handleTagsChange = (tags: string[]) => { setFilters(prev => ({ ...prev, tags })); }; const handleSortChange = (sort: FilterState['sort']) => { setFilters(prev => ({ ...prev, sort })); }; const filteredProjects = filterAndSortProjects(projects, filters); return (
); } export default App;