diff --git a/src/app/(app)/settings/pipelines/[pipelineId]/page.tsx b/src/app/(app)/settings/pipelines/[pipelineId]/page.tsx index ac8c00b..57a4989 100644 --- a/src/app/(app)/settings/pipelines/[pipelineId]/page.tsx +++ b/src/app/(app)/settings/pipelines/[pipelineId]/page.tsx @@ -2,7 +2,7 @@ import { use, useState } from "react"; import { useRouter } from "next/navigation"; -import { ArrowLeft, GitBranch, Plus, Star, Workflow, RotateCcw } from "lucide-react"; +import { ArrowLeft, Copy, GitBranch, Plus, Star, Workflow, RotateCcw } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Skeleton } from "@/components/ui/skeleton"; @@ -25,6 +25,7 @@ import { useRemoveDependency, useAddRework, useRemoveRework, + useDuplicatePipeline, usePipelineValidation, } from "@/hooks/use-pipelines"; import { usePipelineBuilderStore } from "@/stores/pipeline-builder-store"; @@ -51,10 +52,13 @@ export default function PipelineEditorPage({ params }: PageProps) { const removeDependency = useRemoveDependency(pipelineId); const addRework = useAddRework(pipelineId); const removeRework = useRemoveRework(pipelineId); + const duplicatePipeline = useDuplicatePipeline(); const { selectedStageId, selectStage } = usePipelineBuilderStore(); const [showAddStage, setShowAddStage] = useState(false); const [newStageName, setNewStageName] = useState(""); + const [showDuplicate, setShowDuplicate] = useState(false); + const [dupName, setDupName] = useState(""); // Which edge type dragging between stage handles will create. // Both edge types are ALWAYS rendered on the canvas so the full // workflow is visible; this just scopes the drag target. @@ -154,6 +158,24 @@ export default function PipelineEditorPage({ params }: PageProps) { } }; + const handleDuplicate = async () => { + if (!dupName.trim()) return; + try { + const result = (await duplicatePipeline.mutateAsync({ + pipelineId, + name: dupName.trim(), + })) as any; + setShowDuplicate(false); + setDupName(""); + toast.success("Pipeline duplicated"); + // Jump straight into the new clone so editing flows naturally + // from "clone this and tweak" without a trip back to the list. + if (result?.id) router.push(`/settings/pipelines/${result.id}`); + } catch (e: any) { + toast.error(e.message || "Failed to duplicate"); + } + }; + if (isLoading) { return (