import { useState } from "react"; import { requestReviewers } from "~/fns/reviews"; import { dismissReview, rerequestPullReview } from "~/fns/pull-review"; import { usePull } from "~/hooks/use-pull"; import { useAction } from "~/hooks/use-action"; import { useViewer } from "~/hooks/use-viewer"; import { Avatar } from "~/ui/Avatar"; import { Badge } from "~/ui/Badge"; import { Button } from "~/ui/Button"; import { inputClass } from "~/ui/Field"; import { ErrorBanner } from "~/ui/Callout"; import { cn } from "~/utils/cn"; import { SidebarSection } from "~/ui/Heading"; const REVIEW_TONE = { approved: "success", changes_requested: "danger", dismissed: "neutral" } as const; function toneOf(state: string) { return state in REVIEW_TONE ? REVIEW_TONE[state as keyof typeof REVIEW_TONE] : "neutral"; } function DismissForm({ onDismiss, pending }: { onDismiss: (reason: string) => void; pending: boolean }) { const [reason, setReason] = useState(""); return (
{ event.preventDefault(); if (reason.trim()) onDismiss(reason); }} > setReason(event.target.value)} placeholder="Reason for dismissal" className={cn(inputClass, "h-7 text-xs")} />
); } export function PullReviewers({ owner, repo, number }: { owner: string; repo: string; number: number }) { const data = usePull(); const viewer = useViewer(); const request = useAction(requestReviewers); const rerequest = useAction(rerequestPullReview); const dismiss = useAction(dismissReview); const [dismissing, setDismissing] = useState(null); const canRequest = data.canMerge; const isAuthor = viewer?.name === data.conversation.issue.author.name; const open = data.conversation.issue.state === "open"; return ( {data.reviews.map((review) => (
{review.reviewer.name} {open && (canRequest || isAuthor) && ( )} {open && canRequest && (review.state === "approved" || review.state === "changes_requested") && ( )} {review.state.replace("_", " ")}
{review.stale && review.state !== "dismissed" && (

Reviewed an earlier commit

)} {review.dismissReason &&

Dismissed: {review.dismissReason}

} {dismissing === review.id && ( { if (await dismiss.run({ data: { owner, repo, number, reviewId: review.id, reason } })) { setDismissing(null); } }} /> )}
))} {data.requestedReviewers.map((reviewer) => (
{reviewer.name} {reviewer.codeOwner && code owner} pending
))} {canRequest && (
{ event.preventDefault(); const value = String(new FormData(event.currentTarget).get("reviewers") ?? ""); const usernames = value .split(",") .map((name) => name.trim()) .filter(Boolean); request.run({ data: { owner, repo, number, usernames: [...new Set([...data.requestedReviewers.map((reviewer) => reviewer.name), ...usernames])], }, }); }} > {data.assignable.map((name) => (
)}
); }