diff --git a/packages/frontend/src/App.tsx b/packages/frontend/src/App.tsx index 3adcfa1..d5fe969 100644 --- a/packages/frontend/src/App.tsx +++ b/packages/frontend/src/App.tsx @@ -5,6 +5,7 @@ import { Editor } from "./pages/Editor"; import { Build } from "./pages/Build"; import { Server } from "./pages/Server"; import { Toolset } from "./pages/Toolset"; +import { Repos } from "./pages/Repos"; import { IDELayout } from "./layouts/IDELayout"; import { FileExplorer } from "./components/editor/FileExplorer"; import { api } from "./services/api"; @@ -54,6 +55,7 @@ export function App() { } /> } /> } /> + } /> diff --git a/packages/frontend/src/components/CommitDialog.tsx b/packages/frontend/src/components/CommitDialog.tsx new file mode 100644 index 0000000..a8efd7a --- /dev/null +++ b/packages/frontend/src/components/CommitDialog.tsx @@ -0,0 +1,144 @@ +import { useState, useMemo } from "react"; +import { api } from "../services/api"; + +const COMMIT_TYPES = [ + "feat", "fix", "refactor", "docs", "chore", "test", "style", "perf", "ci", "build", "revert", +] as const; + +interface CommitDialogProps { + repo: string; + onClose: () => void; + onCommitted: () => void; +} + +export function CommitDialog({ repo, onClose, onCommitted }: CommitDialogProps) { + const [type, setType] = useState("feat"); + const [scope, setScope] = useState(""); + const [description, setDescription] = useState(""); + const [body, setBody] = useState(""); + const [issueRef, setIssueRef] = useState(""); + const [error, setError] = useState(""); + const [loading, setLoading] = useState(false); + + const preview = useMemo(() => { + let msg = `${type}${scope ? `(${scope})` : ""}: ${description}`; + if (body) msg += `\n\n${body}`; + if (issueRef) msg += `\n\nFixes #${issueRef}`; + return msg; + }, [type, scope, description, body, issueRef]); + + const isValid = description.trim().length > 0 && description.length <= 100; + + async function handleSubmit(andPush: boolean) { + setError(""); + setLoading(true); + try { + await api.repos.commit(repo, { type, scope: scope || undefined, message: description, body: body || undefined, issueRef: issueRef || undefined }); + if (andPush) { + await api.repos.push(repo); + } + onCommitted(); + } catch (err: unknown) { + setError(err instanceof Error ? err.message : "Commit failed"); + } finally { + setLoading(false); + } + } + + return ( +
+
e.stopPropagation()} + > +

+ Commit Changes +

+ +
+ + setScope(e.target.value)} + placeholder="scope (optional)" + className="w-28 rounded border px-2 py-1.5 text-sm" + style={{ backgroundColor: "var(--forge-bg)", borderColor: "var(--forge-border)", color: "var(--forge-text)" }} + /> +
+ + setDescription(e.target.value.slice(0, 100))} + placeholder="Description (required, max 100 chars)" + className="mb-3 w-full rounded border px-3 py-1.5 text-sm" + style={{ backgroundColor: "var(--forge-bg)", borderColor: "var(--forge-border)", color: "var(--forge-text)" }} + /> + +