gitpilot / frontend /components /CreatePRButton.jsx
github-actions[bot]
Deploy from 2cf3f22c
5f15865
import React, { useState } from "react";
/**
* CreatePRButton — Claude-Code-on-Web parity PR creation action.
*
* When clicked, pushes session changes to a new branch and opens a PR.
* Shows loading state and links to the created PR on GitHub.
*/
export default function CreatePRButton({
repo,
sessionId,
branch,
defaultBranch,
disabled,
onPRCreated,
}) {
const [creating, setCreating] = useState(false);
const [prUrl, setPrUrl] = useState(null);
const [error, setError] = useState(null);
const handleCreate = async () => {
if (!repo || !branch || branch === defaultBranch) return;
setCreating(true);
setError(null);
try {
const token = localStorage.getItem("github_token");
const headers = {
"Content-Type": "application/json",
...(token ? { Authorization: `Bearer ${token}` } : {}),
};
const owner = repo.full_name?.split("/")[0] || repo.owner;
const name = repo.full_name?.split("/")[1] || repo.name;
const res = await fetch(`/api/repos/${owner}/${name}/pulls`, {
method: "POST",
headers,
body: JSON.stringify({
title: `[GitPilot] Changes from session ${sessionId ? sessionId.slice(0, 8) : branch}`,
head: branch,
base: defaultBranch || "main",
body: [
"## Summary",
"",
`Changes created by GitPilot AI assistant on branch \`${branch}\`.`,
"",
sessionId ? `Session ID: \`${sessionId}\`` : "",
"",
"---",
"*This PR was generated by [GitPilot](https://github.com/ruslanmv/gitpilot).*",
]
.filter(Boolean)
.join("\n"),
}),
});
const data = await res.json();
if (!res.ok) throw new Error(data.detail || "Failed to create PR");
const url = data.html_url || data.url;
setPrUrl(url);
onPRCreated?.({ pr_url: url, pr_number: data.number, branch });
} catch (err) {
setError(err.message);
} finally {
setCreating(false);
}
};
if (prUrl) {
return (
<a
href={prUrl}
target="_blank"
rel="noreferrer"
style={styles.prLink}
>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<circle cx="18" cy="18" r="3" />
<circle cx="6" cy="6" r="3" />
<path d="M13 6h3a2 2 0 0 1 2 2v7" />
<line x1="6" y1="9" x2="6" y2="21" />
</svg>
View PR on GitHub &rarr;
</a>
);
}
return (
<div>
<button
type="button"
style={{
...styles.btn,
opacity: disabled || creating ? 0.55 : 1,
cursor: disabled || creating ? "not-allowed" : "pointer",
}}
onClick={handleCreate}
disabled={disabled || creating || !branch || branch === defaultBranch}
title={
!branch || branch === defaultBranch
? "Create a session branch first"
: "Create a pull request from session changes"
}
>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<circle cx="18" cy="18" r="3" />
<circle cx="6" cy="6" r="3" />
<path d="M13 6h3a2 2 0 0 1 2 2v7" />
<line x1="6" y1="9" x2="6" y2="21" />
</svg>
{creating ? "Creating PR..." : "Create PR"}
</button>
{error && (
<div style={styles.error}>{error}</div>
)}
</div>
);
}
const styles = {
btn: {
display: "flex",
alignItems: "center",
gap: 6,
height: 38,
padding: "0 14px",
borderRadius: 8,
border: "1px solid rgba(16, 185, 129, 0.3)",
background: "rgba(16, 185, 129, 0.08)",
color: "#10B981",
fontSize: 13,
fontWeight: 600,
cursor: "pointer",
whiteSpace: "nowrap",
transition: "background-color 0.15s",
},
prLink: {
display: "flex",
alignItems: "center",
gap: 6,
height: 38,
padding: "0 14px",
borderRadius: 8,
background: "rgba(16, 185, 129, 0.10)",
color: "#10B981",
fontSize: 13,
fontWeight: 600,
textDecoration: "none",
whiteSpace: "nowrap",
},
error: {
fontSize: 11,
color: "#EF4444",
marginTop: 4,
},
};