import { useState, useEffect, useCallback } from 'react'; import { Link, useSearchParams, useNavigate } from 'react-router-dom'; import { Project, PaginatedResponse } from '../types'; import { listProjects, createProject } from '../api'; import { Badge } from '../components/Badge'; import { DataTable } from '../components/DataTable'; import { FilterDropdown, FilterOption } from '../components/FilterDropdown'; import { FilterChip, FilterChipGroup } from '../components/FilterChip'; import { Pagination } from '../components/Pagination'; import { useAuth } from '../contexts/AuthContext'; import './Home.css'; // Lock icon SVG component function LockIcon() { return ( ); } const VISIBILITY_OPTIONS: FilterOption[] = [ { value: '', label: 'All Projects' }, { value: 'public', label: 'Public Only' }, { value: 'private', label: 'Private Only' }, ]; function Home() { const [searchParams, setSearchParams] = useSearchParams(); const navigate = useNavigate(); const { user } = useAuth(); const [projectsData, setProjectsData] = useState | null>(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [showForm, setShowForm] = useState(false); const [newProject, setNewProject] = useState({ name: '', description: '', is_public: true }); const [creating, setCreating] = useState(false); // Get params from URL const page = parseInt(searchParams.get('page') || '1', 10); const sort = searchParams.get('sort') || 'name'; const order = (searchParams.get('order') || 'asc') as 'asc' | 'desc'; const visibility = searchParams.get('visibility') || ''; const updateParams = useCallback( (updates: Record) => { const newParams = new URLSearchParams(searchParams); Object.entries(updates).forEach(([key, value]) => { if (value === undefined || value === '' || (key === 'page' && value === '1')) { newParams.delete(key); } else { newParams.set(key, value); } }); setSearchParams(newParams); }, [searchParams, setSearchParams] ); const loadProjects = useCallback(async () => { try { setLoading(true); const data = await listProjects({ page, sort, order, visibility: visibility as 'public' | 'private' | undefined || undefined, }); setProjectsData(data); setError(null); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to load projects'); } finally { setLoading(false); } }, [page, sort, order, visibility]); useEffect(() => { loadProjects(); }, [loadProjects]); async function handleCreateProject(e: React.FormEvent) { e.preventDefault(); try { setCreating(true); await createProject(newProject); setNewProject({ name: '', description: '', is_public: true }); setShowForm(false); loadProjects(); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to create project'); } finally { setCreating(false); } } const handleSortChange = (columnKey: string) => { // Toggle order if clicking the same column, otherwise default to asc const newOrder = columnKey === sort ? (order === 'asc' ? 'desc' : 'asc') : 'asc'; updateParams({ sort: columnKey, order: newOrder, page: '1' }); }; const handleVisibilityChange = (value: string) => { updateParams({ visibility: value, page: '1' }); }; const handlePageChange = (newPage: number) => { updateParams({ page: String(newPage) }); }; const clearFilters = () => { setSearchParams({}); }; const hasActiveFilters = visibility !== ''; const projects = projectsData?.items || []; const pagination = projectsData?.pagination; if (loading && !projectsData) { return
Loading projects...
; } return (

Projects

{user ? ( ) : ( Login to create projects )}
{error &&
{error}
} {showForm && (

Create New Project

setNewProject({ ...newProject, name: e.target.value })} placeholder="my-project" required />
setNewProject({ ...newProject, description: e.target.value })} placeholder="Optional description" />
)} {user && (
)} {user && hasActiveFilters && ( {visibility && ( handleVisibilityChange('')} /> )} )}
project.id} onRowClick={(project) => navigate(`/project/${project.name}`)} onSort={handleSortChange} sortKey={sort} sortOrder={order} emptyMessage={ hasActiveFilters ? 'No projects match your filters. Try adjusting your search.' : 'No projects yet. Create your first project to get started!' } columns={[ { key: 'name', header: 'Name', sortable: true, render: (project) => ( {!project.is_public && } {project.name} ), }, { key: 'description', header: 'Description', className: 'cell-description', render: (project) => project.description || '—', }, { key: 'visibility', header: 'Visibility', render: (project) => ( {project.is_public ? 'Public' : 'Private'} ), }, { key: 'created_by', header: 'Owner', className: 'cell-owner', render: (project) => project.created_by, }, ...(user ? [ { key: 'access_level', header: 'Access', render: (project: Project) => project.access_level ? ( {project.is_owner ? 'Owner' : project.access_level.charAt(0).toUpperCase() + project.access_level.slice(1)} ) : ( '—' ), }, ] : []), { key: 'created_at', header: 'Created', sortable: true, className: 'cell-date', render: (project) => new Date(project.created_at).toLocaleDateString(), }, { key: 'updated_at', header: 'Updated', sortable: true, className: 'cell-date', render: (project) => new Date(project.updated_at).toLocaleDateString(), }, ]} />
{pagination && pagination.total_pages > 1 && ( )}
); } export default Home;