"use client"; import { useEffect, useMemo, useState } from "react"; import { Button } from "@kandev/ui/button"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@kandev/ui/dialog"; import { Input } from "@kandev/ui/input"; import { Label } from "@kandev/ui/label"; import { useToast } from "@/components/toast-provider"; import { linkTaskIssue, unlinkTaskIssue } from "@/lib/api/domains/github-api"; import type { Repository } from "@/lib/types/http"; type TaskIssue = { id: string; title: string; repositoryId?: string; issueUrl?: string; issueNumber?: number; repositories?: Array<{ repository_id: string }>; }; type GitHubRepoRef = { owner: string; repo: string; }; type TaskGitHubIssueDialogProps = { open: boolean; onOpenChange: (open: boolean) => void; task: TaskIssue; repositories: Repository[]; }; type DialogFieldsProps = { input: string; setInput: (input: string) => void; inferredRepo: GitHubRepoRef | null; submitting: boolean; error: string | null; }; type DialogActionsProps = { currentLabel: string | null; submitting: boolean; onCancel: () => void; onSubmit: () => void; onUnlink: () => void; }; function githubReposForTask(task: TaskIssue, repositories: Repository[]): GitHubRepoRef[] { const ids = new Set((task.repositories ?? []).map((repo) => repo.repository_id)); if (task.repositoryId) ids.add(task.repositoryId); return repositories .filter((repo) => ids.has(repo.id) && repo.provider === "github") .map((repo) => ({ owner: repo.provider_owner, repo: repo.provider_name })) .filter((repo) => repo.owner && repo.repo); } function issuePayload(input: string, inferredRepo: GitHubRepoRef | null) { const trimmed = input.trim(); if (/^#?\d+$/.test(trimmed) && inferredRepo) { return { issue: trimmed, owner: inferredRepo.owner, repo: inferredRepo.repo, number: Number(trimmed.replace(/^#/, "")), }; } return { issue: trimmed }; } function TaskGitHubIssueFields({ input, setInput, inferredRepo, submitting, error, }: DialogFieldsProps) { const placeholder = inferredRepo ? "#1470 or github.com/owner/repo/issues/1470" : "github.com/owner/repo/issues/1470"; return (
setInput(event.target.value)} placeholder={placeholder} disabled={submitting} /> {error && (

{error}

)}
); } function TaskGitHubIssueActions({ currentLabel, submitting, onCancel, onSubmit, onUnlink, }: DialogActionsProps) { return ( {currentLabel ? ( ) : ( )}
); } export function TaskGitHubIssueDialog({ open, onOpenChange, task, repositories, }: TaskGitHubIssueDialogProps) { const { toast } = useToast(); const [input, setInput] = useState(""); const [submitting, setSubmitting] = useState(false); const [error, setError] = useState(null); let currentLabel: string | null = null; if (task.issueNumber) { currentLabel = `#${task.issueNumber}`; } else if (task.issueUrl) { currentLabel = "Linked issue"; } const githubRepos = useMemo(() => githubReposForTask(task, repositories), [task, repositories]); const inferredRepo = githubRepos.length === 1 ? githubRepos[0] : null; useEffect(() => { if (open) { setInput(task.issueUrl ?? ""); setError(null); } }, [open, task.issueUrl]); const submit = async () => { if (!input.trim()) { setError("Enter a GitHub issue URL or number."); return; } setSubmitting(true); setError(null); try { await linkTaskIssue(task.id, issuePayload(input, inferredRepo)); toast({ description: "GitHub issue linked", variant: "success" }); onOpenChange(false); } catch (err) { setError(err instanceof Error ? err.message : "Failed to link GitHub issue."); } finally { setSubmitting(false); } }; const unlink = async () => { setSubmitting(true); setError(null); try { await unlinkTaskIssue(task.id); toast({ description: "GitHub issue unlinked", variant: "success" }); onOpenChange(false); } catch (err) { setError(err instanceof Error ? err.message : "Failed to unlink GitHub issue."); } finally { setSubmitting(false); } }; return ( {currentLabel ? "Change GitHub issue" : "Link GitHub issue"} {inferredRepo ? `Use a full issue URL or number for ${inferredRepo.owner}/${inferredRepo.repo}.` : "Use a full GitHub issue URL for this task."} onOpenChange(false)} onSubmit={submit} onUnlink={unlink} /> ); }