Pipeline/frontend/src/components/git/EditForgejoIssueDialog.tsx

144 lines
4.2 KiB
TypeScript

"use client";
import { useEffect, useState } from "react";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import type { ForgejoIssue } from "@/lib/api-forgejo";
import { editForgejoIssue } from "@/lib/api-forgejo";
type EditForgejoIssueDialogProps = {
issue: ForgejoIssue | null;
repositoryName: string;
open: boolean;
onOpenChange: (open: boolean) => void;
onSuccess: (updated: { title: string; body: string | null; state: string }) => void;
};
export function EditForgejoIssueDialog({
issue,
repositoryName,
open,
onOpenChange,
onSuccess,
}: EditForgejoIssueDialogProps) {
const [title, setTitle] = useState("");
const [body, setBody] = useState("");
const [isSubmitting, setIsSubmitting] = useState(false);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
if (open && issue) {
setTitle(issue.title);
setBody(issue.body ?? issue.body_preview ?? "");
setError(null);
}
}, [open, issue]);
if (!issue) return null;
const isDirty =
title.trim() !== issue.title || body !== (issue.body ?? issue.body_preview ?? "");
const handleSubmit = async () => {
if (!title.trim()) return;
setIsSubmitting(true);
setError(null);
try {
const patch: { title?: string; body?: string } = {};
if (title.trim() !== issue.title) patch.title = title.trim();
if (body !== (issue.body ?? issue.body_preview ?? "")) patch.body = body;
const result = await editForgejoIssue(issue.id, patch);
onSuccess({ title: result.title, body: result.body, state: result.state });
onOpenChange(false);
} catch (err) {
setError(err instanceof Error ? err.message : "Failed to edit issue");
} finally {
setIsSubmitting(false);
}
};
return (
<Dialog
open={open}
onOpenChange={(next) => {
if (!isSubmitting) {
setError(null);
onOpenChange(next);
}
}}
>
<DialogContent className="max-w-2xl">
<DialogHeader>
<DialogTitle>Edit issue</DialogTitle>
<DialogDescription>
Editing{" "}
<span className="font-mono font-semibold text-strong">
{repositoryName}#{issue.forgejo_issue_number}
</span>
. Changes will be saved to the connected Git provider.
</DialogDescription>
</DialogHeader>
<div className="space-y-4">
<div className="space-y-1.5">
<label htmlFor="issue-title" className="text-sm font-medium text-strong">Title</label>
<Input
id="issue-title"
value={title}
onChange={(e) => setTitle(e.target.value)}
disabled={isSubmitting}
placeholder="Issue title"
/>
</div>
<div className="space-y-1.5">
<label htmlFor="issue-body" className="text-sm font-medium text-strong">Body</label>
<Textarea
id="issue-body"
value={body}
onChange={(e) => setBody(e.target.value)}
rows={10}
disabled={isSubmitting}
className="resize-none font-mono text-sm"
placeholder="Issue body (Markdown supported)"
/>
</div>
</div>
{error ? (
<div className="rounded-lg border border-[color:rgba(248,113,113,0.35)] bg-[color:rgba(248,113,113,0.08)] p-3 text-xs text-[color:var(--danger)]">
{error}
</div>
) : null}
<DialogFooter>
<Button
variant="outline"
onClick={() => onOpenChange(false)}
disabled={isSubmitting}
>
Cancel
</Button>
<Button
onClick={handleSubmit}
disabled={isSubmitting || !title.trim() || !isDirty}
>
{isSubmitting ? "Saving…" : "Save Changes"}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}