diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a19fa61..16eb4ac 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3046,8 +3046,8 @@ snapshots: '@typescript-eslint/parser': 8.59.1(eslint@8.57.1)(typescript@5.9.3) eslint: 8.57.1 eslint-import-resolver-node: 0.3.10 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.1(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.59.1(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.1(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.1) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.59.1(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1) eslint-plugin-react: 7.37.5(eslint@8.57.1) eslint-plugin-react-hooks: 5.0.0-canary-7118f5dd7-20230705(eslint@8.57.1) @@ -3066,7 +3066,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.1(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.1): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.3 @@ -3077,22 +3077,22 @@ snapshots: tinyglobby: 0.2.16 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.59.1(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.1(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.59.1(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.59.1(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.1(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.59.1(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.59.1(eslint@8.57.1)(typescript@5.9.3) eslint: 8.57.1 eslint-import-resolver-node: 0.3.10 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.1(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.1) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.1(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.1(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.1(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -3103,7 +3103,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.10 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.59.1(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.1(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.59.1(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) hasown: 2.0.3 is-core-module: 2.16.1 is-glob: 4.0.3 diff --git a/src/app/dashboard/dev/page.tsx b/src/app/dashboard/dev/page.tsx index 5e9e193..4d485d6 100644 --- a/src/app/dashboard/dev/page.tsx +++ b/src/app/dashboard/dev/page.tsx @@ -75,6 +75,14 @@ export default function DevDashboardPage() { const [orgDropdownOpen, setOrgDropdownOpen] = useState(false); const orgDropdownRef = useRef(null); + // Enable CI modal state + const [enableCIModal, setEnableCIModal] = useState<{ + repo: Repo; + name: string; + icon: string; + branch: string; + } | null>(null); + useEffect(() => { if (sessionStatus !== "authenticated") return; fetchRepos(1, null); @@ -198,43 +206,80 @@ export default function DevDashboardPage() { }; const toggleCI = async (repo: Repo) => { + if (!repo.ciEnabled) { + // Show config modal before enabling + setEnableCIModal({ + repo, + name: repo.name, + icon: "icon.png", + branch: repo.defaultBranch || "main", + }); + return; + } + + // Disable CI setToggling(repo.id); const apiUrl = process.env.NEXT_PUBLIC_API_URL || "http://localhost:4000"; const token = (session?.user as any)?.apiToken; try { - let res; - if (repo.ciEnabled && repo.pluginId) { - // Disable CI - res = await fetch( - `${apiUrl}/api/v1/github/repos/${repo.pluginId}/disable`, - { - method: "POST", - headers: { - Authorization: `Bearer ${token}`, - "Content-Type": "application/json", - }, - }, - ); - } else { - // Enable CI - res = await fetch(`${apiUrl}/api/v1/github/repos/${repo.id}/enable`, { + const res = await fetch( + `${apiUrl}/api/v1/github/repos/${repo.pluginId}/disable`, + { method: "POST", headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", }, - body: JSON.stringify({ - name: repo.name, - fullName: repo.fullName, - htmlUrl: repo.htmlUrl, - language: repo.language, - defaultBranch: repo.defaultBranch, - description: repo.description, - }), - }); + }, + ); + + const json = await res.json().catch(() => ({})); + if (!res.ok || json.success === false) { + alert(`Failed to disable CI:\n${json.error || "Unknown error"}`); + setToggling(null); + return; } + await fetchRepos(1, selectedOrg); + } catch (err: any) { + alert(`An error occurred while toggling CI: ${err.message}`); + } finally { + setToggling(null); + } + }; + + const confirmEnableCI = async () => { + if (!enableCIModal) return; + const { repo, name, icon, branch } = enableCIModal; + + setEnableCIModal(null); + setToggling(repo.id); + const apiUrl = process.env.NEXT_PUBLIC_API_URL || "http://localhost:4000"; + const token = (session?.user as any)?.apiToken; + + try { + const res = await fetch(`${apiUrl}/api/v1/github/repos/${repo.id}/enable`, { + method: "POST", + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + name: repo.name, + fullName: repo.fullName, + htmlUrl: repo.htmlUrl, + language: repo.language, + defaultBranch: repo.defaultBranch, + description: repo.description, + endgitConfig: { + name, + icon, + branch: branch.split(",").map((b) => b.trim()).filter(Boolean), + }, + }), + }); + const json = await res.json().catch(() => ({})); if (!res.ok || json.success === false) { if ( @@ -250,17 +295,14 @@ export default function DevDashboardPage() { return; } - alert( - `Failed to ${repo.ciEnabled ? "disable" : "enable"} CI:\n${json.error || "Unknown error"}`, - ); + alert(`Failed to enable CI:\n${json.error || "Unknown error"}`); setToggling(null); return; } - // Refresh current repos by reloading page 1 await fetchRepos(1, selectedOrg); } catch (err: any) { - alert(`An error occurred while toggling CI: ${err.message}`); + alert(`An error occurred while enabling CI: ${err.message}`); } finally { setToggling(null); } @@ -1569,6 +1611,209 @@ export default function DevDashboardPage() { )} )} + + {/* Enable CI Config Modal */} + {enableCIModal && ( +
setEnableCIModal(null)} + > +
e.stopPropagation()} + > +
+

+ Enable CI for {enableCIModal.repo.name} +

+

+ Configure your plugin settings. A{" "} + + .endgit.yml + {" "} + file will be committed to your repository. +

+ +
+
+ + + setEnableCIModal({ ...enableCIModal, name: e.target.value }) + } + className="input" + style={{ + width: "100%", + padding: "0.5rem", + borderRadius: "var(--radius-md)", + border: "1px solid var(--border-color)", + background: "var(--bg-secondary)", + color: "var(--text-primary)", + }} + /> +
+ +
+ + + setEnableCIModal({ ...enableCIModal, icon: e.target.value }) + } + placeholder="icon.png" + className="input" + style={{ + width: "100%", + padding: "0.5rem", + borderRadius: "var(--radius-md)", + border: "1px solid var(--border-color)", + background: "var(--bg-secondary)", + color: "var(--text-primary)", + }} + /> +

+ Relative path to icon in your repository +

+
+ +
+ + + setEnableCIModal({ + ...enableCIModal, + branch: e.target.value, + }) + } + placeholder="main, develop" + className="input" + style={{ + width: "100%", + padding: "0.5rem", + borderRadius: "var(--radius-md)", + border: "1px solid var(--border-color)", + background: "var(--bg-secondary)", + color: "var(--text-primary)", + }} + /> +

+ Comma-separated branches that trigger builds +

+
+
+ +
+ + +
+
+
+
+ )} ); }