Files
kandev/apps/web/hooks/domains/github/use-github-action-presets.ts
João Salavisa ccc62d9324 feat(github): configurable quick-action presets and PR branch checkout (#689)
* feat(github): configurable quick-action presets and PR branch checkout

- Add workspace-scoped action presets (icon, label, hint, prompt template) for PRs and Issues, editable from GitHub settings with reset-to-defaults.
- When starting a task from a PR, check out the PR head branch directly in the worktree instead of branching off main.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(github): extract action-preset service to keep service.go under line limit

Splits the preset CRUD methods out of the oversized service.go and applies prettier formatting to three frontend files flagged by CI.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(github): address review feedback on action presets

- Prevent infinite refetch loop when fetching presets fails by tracking attempted workspaces in a ref instead of re-entering the effect on every loading-state change.
- Drop unused savePR/saveIssue helpers from the hook; only save() is consumed.
- Reorder the init spread in updateGitHubActionPresets/resetGitHubActionPresets so callers can't accidentally overwrite method/body.
- Tighten the GitHubActionPreset.icon type with string & {} so known icon keys keep autocomplete.
- Sharpen the eye-icon test to assert the exact IconEye mapping.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(github): address second round of review feedback

- Seed preset editor drafts from current store state via lazy initializers so a warm mount shows the user's saved customisations instead of silently reverting to defaults when Save is clicked.
- Guard Reset button with its own in-flight state to prevent double-firing.
- Stop freezing resolved defaults into the stored row on a partial update; persist only what the store has for the untouched kind so future default improvements still reach those workspaces.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor(github): tighten quick-actions editor UI

Replace the stacked PR/Issue editors and multi-line preset cards with a tabbed layout and single-line rows. Each row shows icon · label · hint inline; an "Edit prompt" toggle reveals the template textarea only when needed. Drops the redundant section headings, up/down reorder buttons, and verbose template helper label (hint moved into the textarea placeholder).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(github): widen icon select trigger so the icon is visible

The icon select trigger had a fixed 40px width which the SelectTrigger's built-in chevron, padding, and icon gap all competed for, clipping the icon out of view. Drop the width override so the trigger sizes to its content.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Kandev Agent <agent@kandev.dev>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-24 14:10:59 +01:00

66 lines
2.2 KiB
TypeScript

"use client";
import { useEffect, useCallback, useRef } from "react";
import {
fetchGitHubActionPresets,
updateGitHubActionPresets,
resetGitHubActionPresets,
} from "@/lib/api/domains/github-api";
import { useAppStore } from "@/components/state-provider";
import type { UpdateGitHubActionPresetsRequest } from "@/lib/types/github";
export function useGitHubActionPresets(workspaceId: string | null) {
const presets = useAppStore((state) =>
workspaceId ? (state.actionPresets.byWorkspaceId[workspaceId] ?? null) : null,
);
const loading = useAppStore((state) =>
workspaceId ? Boolean(state.actionPresets.loading[workspaceId]) : false,
);
const setPresets = useAppStore((state) => state.setActionPresets);
const setLoading = useAppStore((state) => state.setActionPresetsLoading);
// Tracks which workspaces we've already attempted a fetch for so a
// network failure does not cause the effect to re-fire indefinitely.
const attemptedRef = useRef<Set<string>>(new Set());
useEffect(() => {
if (!workspaceId) return;
if (presets || attemptedRef.current.has(workspaceId)) return;
attemptedRef.current.add(workspaceId);
setLoading(workspaceId, true);
fetchGitHubActionPresets(workspaceId, { cache: "no-store" })
.then((response) => {
if (response) setPresets(workspaceId, response);
})
.catch(() => {
// Leave presets unset on failure; consumers fall back to defaults.
})
.finally(() => {
setLoading(workspaceId, false);
});
}, [workspaceId, presets, setPresets, setLoading]);
const save = useCallback(
async (payload: Omit<UpdateGitHubActionPresetsRequest, "workspace_id">) => {
if (!workspaceId) return null;
const response = await updateGitHubActionPresets({ workspace_id: workspaceId, ...payload });
if (response) setPresets(workspaceId, response);
return response;
},
[workspaceId, setPresets],
);
const reset = useCallback(async () => {
if (!workspaceId) return null;
const response = await resetGitHubActionPresets(workspaceId);
if (response) setPresets(workspaceId, response);
return response;
}, [workspaceId, setPresets]);
return {
presets,
loading,
save,
reset,
};
}