"use client"; import { IconExternalLink, IconPlus, IconRefresh } from "@tabler/icons-react"; import { Badge } from "@kandev/ui/badge"; import { Button } from "@kandev/ui/button"; import { Dialog, DialogContent, DialogTitle } from "@kandev/ui/dialog"; import type { LinearIssue, LinearWorkflowState } from "@/lib/types/linear"; import { LinearErrorMessage, PersonCell, formatRelative, priorityClass, stateBadgeClass, useIssueState, type IssueState, } from "./linear-issue-common"; type LinearIssueDialogProps = { open: boolean; onOpenChange: (open: boolean) => void; workspaceId: string | null | undefined; identifier: string | null | undefined; /** Shown while the full issue is loading so the dialog doesn't flash empty. */ initialIssue?: LinearIssue | null; /** When set, the dialog footer shows a Start task button that calls this. */ onStartTask?: (issue: LinearIssue) => void; }; function dialogTitle(issue: LinearIssue | null, identifier: string | null | undefined): string { return issue?.title ?? identifier ?? "Linear issue"; } function effectiveIdentifier( issue: LinearIssue | null, identifier: string | null | undefined, ): string { return identifier ?? issue?.identifier ?? ""; } export function LinearIssueDialog({ open, onOpenChange, workspaceId, identifier, initialIssue, onStartTask, }: LinearIssueDialogProps) { const enabled = open && !!workspaceId && !!identifier; const state = useIssueState(workspaceId ?? "", identifier ?? "", enabled); const issue = state.issue ?? initialIssue ?? null; const errorOnly = state.error !== null && issue === null; const showFooter = issue !== null && onStartTask !== undefined; // The "Start task" button hands the dialog's issue back to the caller and // closes — the caller is responsible for opening the task creation flow. const handleStart = () => { if (issue && onStartTask) { onStartTask(issue); onOpenChange(false); } }; return ( {dialogTitle(issue, identifier)} {errorOnly ? (
) : ( <> void state.load()} /> {showFooter && } )}
); } function IssueFooter({ onStart }: { onStart: () => void }) { return (
); } function DialogBody({ issue, state, identifier, }: { issue: LinearIssue | null; state: IssueState; identifier: string; }) { return (
{state.error && issue && (
)} {!issue && state.loading && (
Loading issue…
)} {issue && }
); } type TopBarProps = { issue: LinearIssue | null; loading: boolean; onRefresh: () => void; }; function IssueTopBar({ issue, loading, onRefresh }: TopBarProps) { return (
{issue?.url && ( )}
); } function IssueBody({ issue, state, identifier, }: { issue: LinearIssue; state: IssueState; identifier: string; }) { return (
); } function IssueHeading({ issue, identifier }: { issue: LinearIssue; identifier: string }) { return (
{identifier} {issue.teamKey && ( <> · {issue.teamKey} )}

{issue.title}

); } function SectionHeading({ children }: { children: React.ReactNode }) { return
{children}
; } function DescriptionSection({ description }: { description: string }) { return (
Description
{description ? (
{description}
) : (
No description.
)}
); } type StateBlockProps = { issue: LinearIssue; states: LinearWorkflowState[] | null | undefined; pending: string | null; onChange: (stateId: string) => void; }; // Hide the issue's current state from the available transitions list — moving // to the same state would be a no-op and clutters the UI. function visibleStates(issue: LinearIssue, list: LinearWorkflowState[]): LinearWorkflowState[] { return list.filter((s) => s.id !== issue.stateId); } function StateBlock({ issue, states, pending, onChange }: StateBlockProps) { const list = states ?? []; const next = visibleStates(issue, list); return (
{issue.stateName && (
{issue.stateName}
)} {next.length > 0 && (
{next.map((s) => ( ))}
)}
); } function DetailsCard({ issue }: { issue: LinearIssue }) { return (
Details
{issue.priorityLabel || "—"} {issue.teamKey}
); } function DetailRow({ label, children }: { label: string; children: React.ReactNode }) { return (
{label}
{children}
); } function MetaFooter({ issue }: { issue: LinearIssue }) { const updated = formatRelative(issue.updated); if (!updated) return null; return (
Updated {updated}
); }