import React, { useEffect, useCallback } from 'react' import { createFileRoute, useNavigate } from '@tanstack/react-router' import { SubBar } from '../shell/SubBar' import { Sub } from '../primitives' import { ProjectsTable } from '../features/projects/ProjectsTable' import { useProjects } from '../features/projects/useProjects' import { useProjectsCursor } from '../features/projects/useProjectsCursor' import { useKeyboardCursor } from './__root' import { AuthError, APIError } from '../api/client' import type { ProjectFilters } from '../features/projects/useProjects' import type { Project } from '../api/adapters' import '../styles/projects.css' type ProjectsSearch = { since?: '7d' | '30d' | '90d' | 'all' } const VALID_SINCE = new Set(['7d', '30d', '90d', 'all']) export const Route = createFileRoute('/projects')({ validateSearch: (search: Record): ProjectsSearch => { const since = typeof search['since'] === 'string' && VALID_SINCE.has(search['since']) ? (search['since'] as ProjectsSearch['since']) : undefined return { since } }, component: ProjectsRoute, }) function SinceButtonGroup({ value, onChange, }: { value: string onChange: (v: ProjectsSearch['since']) => void }): React.JSX.Element { const options = ['7d', '30d', '90d', 'all'] as const return (
{options.map(o => ( ))}
) } function ProjectsRoute(): React.JSX.Element { const navigate = useNavigate() const search = Route.useSearch() const since = search.since ?? '30d' const filters: ProjectFilters = { since } const { data: projects, isLoading, error } = useProjects(filters) const handleOpen = useCallback( (p: Project) => { // Route /project/$ is added in Phase 4; cast needed until routeTree is regenerated. // eslint-disable-next-line @typescript-eslint/no-explicit-any void (navigate as any)({ to: '/project/$', params: { _splat: encodeURIComponent(p.cwd) } }) }, [navigate], ) const { cursor, move, activate, jumpTo } = useProjectsCursor( projects?.length ?? 0, useCallback( (idx: number) => { if (projects && projects[idx]) handleOpen(projects[idx]) }, [projects, handleOpen], ), ) const cursorRef = useKeyboardCursor() useEffect(() => { cursorRef.current = { move, activate } return () => { cursorRef.current = { move: (_d: 1 | -1) => { /* no-op */ }, activate: () => { /* no-op */ }, } } }, [cursorRef, move, activate]) function handleSinceChange(next: ProjectsSearch['since']) { void navigate({ to: '/projects', search: { since: next } }) } const subBar = ( {projects != null ? `${projects.length} projects` : '…'} · ranked by recent activity ) if (isLoading) { return ( <> {subBar}
loading…
) } if (error != null) { if (error instanceof AuthError) { return (
not authenticated
Sign in to view your projects.
) } const detail = error instanceof APIError ? error.message : String(error) return (
error
{detail}
) } return ( <> {subBar} ) }