import React, { useEffect, useCallback } from 'react'
import { createFileRoute, useNavigate } from '@tanstack/react-router'
import { SubBar } from '../shell/SubBar'
import { Sub, EmptyState } from '../primitives'
import { ProjectHeader } from '../features/projects/ProjectHeader'
import { SessionsTable } from '../features/home/SessionsTable'
import { useSessions } from '../features/home/useSessions'
import { useProjects } from '../features/projects/useProjects'
import { useHomeCursor } from '../features/home/useHomeCursor'
import { useKeyboardCursor } from './__root'
import { AuthError, APIError } from '../api/client'
import type { Session } from '../api/adapters'
import '../styles/projects.css'
export const Route = createFileRoute('/project/$')({
component: ProjectRoute,
})
function ProjectRoute(): React.JSX.Element {
const navigate = useNavigate()
const { _splat } = Route.useParams()
const cwd = decodeURIComponent(_splat ?? '')
const { data: sessions, isLoading: sessionsLoading, error: sessionsError } = useSessions({ cwd, since: 'all' })
const { data: projects, isLoading: projectsLoading } = useProjects({ since: 'all' })
const project = projects?.find(p => p.cwd === cwd)
const isLoading = sessionsLoading || projectsLoading
const error = sessionsError
const handleOpen = useCallback((s: Session) => {
void navigate({ to: '/session/$tool/$host/$id', params: { tool: s.tool, host: s.host, id: s.sessionId } })
}, [navigate])
const { cursor, move, activate, jumpTo } = useHomeCursor(
sessions?.length ?? 0,
useCallback((idx: number) => {
if (sessions && sessions[idx]) handleOpen(sessions[idx])
}, [sessions, handleOpen]),
)
const cursorRef = useKeyboardCursor()
useEffect(() => {
cursorRef.current = { move, activate }
return () => {
cursorRef.current = {
move: (_d: 1 | -1) => { /* no-op */ },
activate: () => { /* no-op */ },
}
}
}, [cursorRef, move, activate])
// Collect unique hosts from the loaded sessions
const hosts = sessions != null
? Array.from(new Set(sessions.map(s => s.host))).sort()
: (project?.hosts ?? [])
const sessionCount = sessions?.length ?? project?.sessions ?? 0
if (isLoading) {
return (
<>
<SubBar>
<span className="mono muted truncate">{cwd}</span>
</SubBar>
<div className="body body-pad">
<Sub>loading…</Sub>
</div>
</>
)
}
if (error != null) {
if (error instanceof AuthError) {
return (
<div className="body body-pad" style={{ display: 'flex', justifyContent: 'center', paddingTop: 60 }}>
<div className="card" style={{ padding: '24px 32px', textAlign: 'center' }}>
<div className="uppercase-mono" style={{ marginBottom: 8 }}>not authenticated</div>
<div className="muted">Sign in to view this project.</div>
</div>
</div>
)
}
const detail = error instanceof APIError ? error.message : String(error)
return (
<div className="body body-pad" style={{ display: 'flex', justifyContent: 'center', paddingTop: 60 }}>
<div className="card" style={{ padding: '24px 32px', textAlign: 'center' }}>
<div className="uppercase-mono" style={{ marginBottom: 8 }}>error</div>
<div className="muted">{detail}</div>
</div>
</div>
)
}
// If the project was not found in the index and sessions came back empty,
// render an EmptyState rather than a blank page.
if (project === undefined && (sessions == null || sessions.length === 0)) {
return (
<>
<ProjectHeader cwd={cwd} project={undefined} sessionCount={0} hosts={[]} />
<div className="body body-pad">
<EmptyState glyph="∅" copy="no sessions found for this project" />
</div>
</>
)
}
return (
<>
<ProjectHeader cwd={cwd} project={project} sessionCount={sessionCount} hosts={hosts} />
<SubBar>
<span className="mono muted">{sessionCount} sessions</span>
</SubBar>
<SessionsTable
sessions={sessions ?? []}
cursor={cursor}
onCursor={jumpTo}
onOpen={handleOpen}
/>
</>
)
}