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.
This commit is contained in:
Fusion
2026-05-19 15:05:55 +02:00
parent 875594d66d
commit a9f4657d03
3 changed files with 239 additions and 0 deletions
+23
View File
@@ -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 ? <span className="badge">{status.ahead}</span> : null}
</button>
<button
className="toolbar-button"
onClick={() => setShowMergeDialog(true)}
disabled={loading}
type="button"
>
🔀 Merge
</button>
</div>
</div>
@@ -227,6 +237,19 @@ export const GitToolbar = ({
{status.untracked.length > 0 && <span className="status-badge untracked"> {status.untracked.length} untracked</span>}
</div>
)}
<MergeDialog
projectId={projectId}
repoId={repoId}
branches={branches}
currentBranch={currentBranch}
isOpen={showMergeDialog}
onClose={() => setShowMergeDialog(false)}
onMerge={() => {
void loadStatus();
onRefresh();
}}
/>
</div>
);
};
+132
View File
@@ -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<string | null>(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 (
<div className="modal-overlay" onClick={onClose}>
<div className="modal-content" onClick={(e) => e.stopPropagation()}>
<h2>Merge Branch</h2>
<div className="merge-form">
<div className="form-field">
<label>Source Branch (merge from)</label>
<select
value={sourceBranch}
onChange={(e) => setSourceBranch(e.target.value)}
disabled={loading}
>
<option value="">Select branch...</option>
{availableBranches.map((branch) => (
<option key={branch} value={branch}>
{branch}
</option>
))}
</select>
</div>
<div className="form-field">
<label>Target Branch (merge into)</label>
<input
type="text"
value={currentBranch}
disabled
className="input-disabled"
/>
</div>
<div className="form-field">
<label>Commit Message (optional)</label>
<textarea
value={commitMessage}
onChange={(e) => setCommitMessage(e.target.value)}
placeholder={`Merge ${sourceBranch || "branch"} into ${currentBranch}`}
rows={3}
disabled={loading}
/>
</div>
{error && <div className="error-text">{error}</div>}
{success && (
<div className="success-text">Merge successful!</div>
)}
<div className="modal-actions">
<button
className="secondary-button"
onClick={onClose}
disabled={loading}
type="button"
>
Cancel
</button>
<button
className="primary-button"
onClick={handleMerge}
disabled={loading || !sourceBranch}
type="button"
>
{loading ? "Merging..." : "Merge"}
</button>
</div>
</div>
</div>
</div>
);
};