From a9f4657d0365b2c788fd4b90e8b386987193d4d6 Mon Sep 17 00:00:00 2001 From: Fusion Date: Tue, 19 May 2026 15:05:55 +0200 Subject: [PATCH] feat: add MergeDialog component and integrate with GitToolbar - Create MergeDialog component for branch merging - Add merge button to GitToolbar - Show source branch selector and target branch display - Handle merge success and error states - Add modal CSS styles Completes Task 6.2 of git-control change. --- apps/web/src/components/git-toolbar.tsx | 23 ++++ apps/web/src/components/merge-dialog.tsx | 132 +++++++++++++++++++++++ apps/web/src/styles.css | 84 +++++++++++++++ 3 files changed, 239 insertions(+) create mode 100644 apps/web/src/components/merge-dialog.tsx diff --git a/apps/web/src/components/git-toolbar.tsx b/apps/web/src/components/git-toolbar.tsx index 4c92c59..37cdea2 100644 --- a/apps/web/src/components/git-toolbar.tsx +++ b/apps/web/src/components/git-toolbar.tsx @@ -9,6 +9,7 @@ import { pushRepository, type GitStatus, } from "../api/git_repositories"; +import { MergeDialog } from "./merge-dialog"; interface GitToolbarProps { projectId: string; @@ -33,6 +34,7 @@ export const GitToolbar = ({ const [showNewBranch, setShowNewBranch] = useState(false); const [newBranchName, setNewBranchName] = useState(""); const [newBranchBase, setNewBranchBase] = useState(""); + const [showMergeDialog, setShowMergeDialog] = useState(false); const loadStatus = useCallback(async () => { try { @@ -179,6 +181,14 @@ export const GitToolbar = ({ ⇡ Push {status?.ahead ? {status.ahead} : null} + @@ -227,6 +237,19 @@ export const GitToolbar = ({ {status.untracked.length > 0 && ❓ {status.untracked.length} untracked} )} + + setShowMergeDialog(false)} + onMerge={() => { + void loadStatus(); + onRefresh(); + }} + /> ); }; diff --git a/apps/web/src/components/merge-dialog.tsx b/apps/web/src/components/merge-dialog.tsx new file mode 100644 index 0000000..1bb6c28 --- /dev/null +++ b/apps/web/src/components/merge-dialog.tsx @@ -0,0 +1,132 @@ +import { useState } from "react"; + +import { mergeBranches } from "../api/git_repositories"; + +interface MergeDialogProps { + projectId: string; + repoId: string; + branches: string[]; + currentBranch: string; + isOpen: boolean; + onClose: () => void; + onMerge: () => void; +} + +export const MergeDialog = ({ + projectId, + repoId, + branches, + currentBranch, + isOpen, + onClose, + onMerge, +}: MergeDialogProps) => { + const [sourceBranch, setSourceBranch] = useState(""); + const [commitMessage, setCommitMessage] = useState(""); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [success, setSuccess] = useState(false); + + if (!isOpen) return null; + + const availableBranches = branches.filter((b) => b !== currentBranch); + + const handleMerge = async () => { + if (!sourceBranch) { + setError("Please select a source branch"); + return; + } + setLoading(true); + setError(null); + setSuccess(false); + try { + await mergeBranches( + projectId, + repoId, + sourceBranch, + currentBranch, + commitMessage || undefined + ); + setSuccess(true); + setTimeout(() => { + onMerge(); + onClose(); + }, 1500); + } catch { + setError("Merge failed. There may be conflicts to resolve."); + } finally { + setLoading(false); + } + }; + + return ( +
+
e.stopPropagation()}> +

Merge Branch

+ +
+
+ + +
+ +
+ + +
+ +
+ +