diff --git a/components/pr-check-status.tsx b/components/pr-check-status.tsx new file mode 100644 index 00000000..ba15d791 --- /dev/null +++ b/components/pr-check-status.tsx @@ -0,0 +1,104 @@ +'use client' + +import { useEffect, useState } from 'react' +import { Check, Loader2, X } from 'lucide-react' + +interface CheckRun { + id: number + name: string + status: string + conclusion: string | null + html_url: string + started_at: string | null + completed_at: string | null +} + +interface PRCheckStatusProps { + taskId: string + prStatus: 'open' | 'closed' | 'merged' + isActive?: boolean + className?: string +} + +export function PRCheckStatus({ taskId, prStatus, isActive = false, className = '' }: PRCheckStatusProps) { + const [checkRuns, setCheckRuns] = useState([]) + const [isLoading, setIsLoading] = useState(true) + + useEffect(() => { + const fetchCheckRuns = async () => { + try { + const response = await fetch(`/api/tasks/${taskId}/check-runs`) + if (response.ok) { + const data = await response.json() + if (data.success && data.checkRuns) { + setCheckRuns(data.checkRuns) + } + } + } catch (error) { + console.error('Error fetching check runs:', error) + } finally { + setIsLoading(false) + } + } + + fetchCheckRuns() + // Refresh every 30 seconds for in-progress checks + const interval = setInterval(fetchCheckRuns, 30000) + return () => clearInterval(interval) + }, [taskId]) + + // Only show indicator for open PRs + if (prStatus !== 'open') { + return null + } + + // Don't render anything if loading or no check runs + if (isLoading || checkRuns.length === 0) { + return null + } + + // Determine overall status + const hasInProgress = checkRuns.some((run) => run.status === 'in_progress' || run.status === 'queued') + const hasFailed = checkRuns.some((run) => run.conclusion === 'failure' || run.conclusion === 'cancelled') + const hasNeutral = checkRuns.some((run) => run.conclusion === 'neutral') + const allPassed = checkRuns.every((run) => run.status === 'completed' && run.conclusion === 'success') + + // Determine background color based on active state + const bgColor = isActive ? 'bg-accent' : 'bg-card' + + // Render the appropriate indicator + // Note: Check failed first to ensure failures are always visible, even if other checks are in progress + if (hasFailed) { + return ( +
+
+
+ ) + } + + if (hasInProgress) { + return ( +
+
+
+ ) + } + + if (hasNeutral) { + return ( +
+
+
+ ) + } + + if (allPassed) { + return ( +
+ +
+ ) + } + + return null +} diff --git a/components/task-sidebar.tsx b/components/task-sidebar.tsx index 896a5311..18211fc7 100644 --- a/components/task-sidebar.tsx +++ b/components/task-sidebar.tsx @@ -25,6 +25,7 @@ import { useTasks } from '@/components/app-layout' import { useAtomValue } from 'jotai' import { sessionAtom } from '@/lib/atoms/session' import { PRStatusIcon } from '@/components/pr-status-icon' +import { PRCheckStatus } from '@/components/pr-check-status' // Model mappings for human-friendly names const AGENT_MODELS = { @@ -397,7 +398,12 @@ export function TaskSidebar({ tasks, onTaskSelect, width = 288 }: TaskSidebarPro
{task.repoUrl && (
- {task.prStatus && } + {task.prStatus && ( +
+ + +
+ )} {(() => { try { diff --git a/components/tasks-list-client.tsx b/components/tasks-list-client.tsx index 71a3b8dc..a840b45b 100644 --- a/components/tasks-list-client.tsx +++ b/components/tasks-list-client.tsx @@ -28,6 +28,7 @@ import type { Session } from '@/lib/session/types' import { VERCEL_DEPLOY_URL } from '@/lib/constants' import { Claude, Codex, Copilot, Cursor, Gemini, OpenCode } from '@/components/logos' import { PRStatusIcon } from '@/components/pr-status-icon' +import { PRCheckStatus } from '@/components/pr-check-status' interface TasksListClientProps { user: Session['user'] | null @@ -420,7 +421,12 @@ export function TasksListClient({ user, authProvider, initialStars = 1056 }: Tas
{task.repoUrl && (
- {task.prStatus && } + {task.prStatus && ( +
+ + +
+ )} {(() => { try {