/** * GitHub PRs View * * Displays pull requests using React Query for data fetching. */ import { useState, useCallback } from 'react'; import { GitPullRequest, RefreshCw, ExternalLink, GitMerge, X } from 'lucide-react'; import { Spinner } from '@/components/ui/spinner'; import { getElectronAPI, type GitHubPR } from '@/lib/electron'; import { useAppStore } from '@/store/app-store'; import { Button } from '@/components/ui/button'; import { Markdown } from '@/components/ui/markdown'; import { cn } from '@/lib/utils'; import { useGitHubPRs } from '@/hooks/queries'; export function GitHubPRsView() { const [selectedPR, setSelectedPR] = useState(null); const { currentProject } = useAppStore(); const { data, isLoading: loading, isFetching: refreshing, error, refetch, } = useGitHubPRs(currentProject?.path); const openPRs = data?.openPRs ?? []; const mergedPRs = data?.mergedPRs ?? []; const handleRefresh = useCallback(() => { refetch(); }, [refetch]); const handleOpenInGitHub = useCallback((url: string) => { const api = getElectronAPI(); api.openExternalLink(url); }, []); const formatDate = (dateString: string) => { const date = new Date(dateString); return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric', }); }; const getReviewStatus = (pr: GitHubPR) => { if (pr.isDraft) return { label: 'Draft', color: 'text-muted-foreground', bg: 'bg-muted' }; switch (pr.reviewDecision) { case 'APPROVED': return { label: 'Approved', color: 'text-green-500', bg: 'bg-green-500/10' }; case 'CHANGES_REQUESTED': return { label: 'Changes requested', color: 'text-orange-500', bg: 'bg-orange-500/10' }; case 'REVIEW_REQUIRED': return { label: 'Review required', color: 'text-yellow-500', bg: 'bg-yellow-500/10' }; default: return null; } }; if (loading) { return (
); } if (error) { return (

Failed to Load Pull Requests

{error instanceof Error ? error.message : 'Failed to fetch pull requests'}

); } const totalPRs = openPRs.length + mergedPRs.length; return (
{/* PR List */}
{/* Header */}

Pull Requests

{totalPRs === 0 ? 'No pull requests found' : `${openPRs.length} open, ${mergedPRs.length} merged`}

{/* PR List */}
{totalPRs === 0 ? (

No Pull Requests

This repository has no pull requests yet.

) : (
{/* Open PRs */} {openPRs.map((pr) => ( setSelectedPR(pr)} onOpenExternal={() => handleOpenInGitHub(pr.url)} formatDate={formatDate} getReviewStatus={getReviewStatus} /> ))} {/* Merged PRs Section */} {mergedPRs.length > 0 && ( <>
Merged ({mergedPRs.length})
{mergedPRs.map((pr) => ( setSelectedPR(pr)} onOpenExternal={() => handleOpenInGitHub(pr.url)} formatDate={formatDate} getReviewStatus={getReviewStatus} /> ))} )}
)}
{/* PR Detail Panel */} {selectedPR && (
{/* Detail Header */}
{selectedPR.state === 'MERGED' ? ( ) : ( )} #{selectedPR.number} {selectedPR.title} {selectedPR.isDraft && ( Draft )}
{/* PR Detail Content */}
{/* Title */}

{selectedPR.title}

{/* Meta info */}
{selectedPR.state === 'MERGED' ? 'Merged' : selectedPR.isDraft ? 'Draft' : 'Open'} {getReviewStatus(selectedPR) && ( {getReviewStatus(selectedPR)!.label} )} #{selectedPR.number} opened {formatDate(selectedPR.createdAt)} by{' '} {selectedPR.author.login}
{/* Branch info */} {selectedPR.headRefName && (
Branch: {selectedPR.headRefName}
)} {/* Labels */} {selectedPR.labels.length > 0 && (
{selectedPR.labels.map((label) => ( {label.name} ))}
)} {/* Body */} {selectedPR.body ? ( {selectedPR.body} ) : (

No description provided.

)} {/* Open in GitHub CTA */}

View code changes, comments, and reviews on GitHub.

)}
); } interface PRRowProps { pr: GitHubPR; isSelected: boolean; onClick: () => void; onOpenExternal: () => void; formatDate: (date: string) => string; getReviewStatus: (pr: GitHubPR) => { label: string; color: string; bg: string } | null; } function PRRow({ pr, isSelected, onClick, onOpenExternal, formatDate, getReviewStatus, }: PRRowProps) { const reviewStatus = getReviewStatus(pr); return (
{pr.state === 'MERGED' ? ( ) : ( )}
{pr.title} {pr.isDraft && ( Draft )}
#{pr.number} opened {formatDate(pr.createdAt)} by {pr.author.login} {pr.headRefName && ( {pr.headRefName} )}
{/* Review Status */} {reviewStatus && ( {reviewStatus.label} )} {/* Labels */} {pr.labels.map((label) => ( {label.name} ))}
); }