278638ccf1
Users can now actually create a multi-repo task: a "+ Add repository" button below the primary repo + branch selectors expands the form with additional (repository, base-branch) rows. Each extra row picks from a filtered repository list that excludes the primary and any other already- chosen extras, so duplicates are impossible to enter. - ExtraRepositoryRow type + form-state hook (extraRepositories, add / remove / update / reset). Lives in its own file so the main state hook stays under the file-length lint cap. - ExtraRepositoryRows component renders the rows and the "+ Add" button; per-row branches are loaded on demand via the existing useRepositoryBranches hook. - buildRepositoriesPayload accepts the extras and appends them after the primary, dropping rows with empty repository_id silently so an unfinished row does not block submit. The backend's duplicate-repo validation (Phase 8) catches anything that slips through. - DialogFormBody is split: CreateModeSelectors (selectors + extra rows) and useDialogHandlers move to dedicated files, keeping touched files under the lint thresholds. - Tests cover the multi-repo payload (ordering, drop-empty, no-primary recovery, single-repo unchanged) and the existing store-multi-repo.test.ts gets named constants to satisfy the no-duplicate-string rule. Verified: pnpm exec tsc --noEmit clean, vitest 608/608 green, eslint --max-warnings 0 clean. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
50 lines
1.5 KiB
TypeScript
50 lines
1.5 KiB
TypeScript
"use client";
|
|
|
|
import { useCallback, useRef, useState } from "react";
|
|
import type { ExtraRepositoryRow } from "@/components/task-create-dialog-types";
|
|
|
|
/**
|
|
* Manages the extra-repository rows for multi-repo task creation. The primary
|
|
* repo lives on the form's repositoryId/branch; extras here represent the
|
|
* 2nd, 3rd, ... rows that the user adds via "+ Add repository".
|
|
*
|
|
* `nextKey` increments to give each row a stable client-side key without
|
|
* relying on array indices (which would shift on removal and break inputs).
|
|
*/
|
|
export function useExtraRepositoriesState() {
|
|
const [extraRepositories, setExtraRepositories] = useState<ExtraRepositoryRow[]>([]);
|
|
const nextKeyRef = useRef(0);
|
|
|
|
const addExtraRepository = useCallback(() => {
|
|
nextKeyRef.current += 1;
|
|
const key = `extra-${nextKeyRef.current}`;
|
|
setExtraRepositories((rows) => [...rows, { key, repositoryId: "", branch: "" }]);
|
|
}, []);
|
|
|
|
const removeExtraRepository = useCallback((key: string) => {
|
|
setExtraRepositories((rows) => rows.filter((r) => r.key !== key));
|
|
}, []);
|
|
|
|
const updateExtraRepository = useCallback(
|
|
(key: string, patch: Partial<ExtraRepositoryRow>) => {
|
|
setExtraRepositories((rows) =>
|
|
rows.map((r) => (r.key === key ? { ...r, ...patch } : r)),
|
|
);
|
|
},
|
|
[],
|
|
);
|
|
|
|
const resetExtraRepositories = useCallback(() => {
|
|
setExtraRepositories([]);
|
|
nextKeyRef.current = 0;
|
|
}, []);
|
|
|
|
return {
|
|
extraRepositories,
|
|
addExtraRepository,
|
|
removeExtraRepository,
|
|
updateExtraRepository,
|
|
resetExtraRepositories,
|
|
};
|
|
}
|