Files
kandev/apps/web/components/task-create-dialog-header.tsx
T
2026-05-01 15:18:18 +01:00

180 lines
6.0 KiB
TypeScript

"use client";
import { DialogTitle } from "@kandev/ui/dialog";
import { RepositorySelector, InlineTaskName } from "@/components/task-create-dialog-selectors";
import type { useRepositoryOptions } from "@/components/task-create-dialog-options";
function getRepositoryPlaceholder(
workspaceId: string | null,
repositoriesLoading: boolean,
discoverReposLoading: boolean,
) {
if (!workspaceId) return "Select workspace first";
if (repositoriesLoading || discoverReposLoading) return "Loading...";
return "Select repository";
}
export type DialogHeaderContentProps = {
isCreateMode: boolean;
isEditMode: boolean;
isTaskStarted: boolean;
sessionRepoName?: string;
initialTitle?: string;
taskName: string;
repositoryId: string;
discoveredRepoPath: string;
workspaceId: string | null;
repositoriesLoading: boolean;
discoverReposLoading: boolean;
headerRepositoryOptions: ReturnType<typeof useRepositoryOptions>["headerRepositoryOptions"];
onRepositoryChange: (v: string) => void;
onTaskNameChange: (v: string) => void;
useGitHubUrl: boolean;
githubUrl: string;
githubUrlError: string | null;
onToggleGitHubUrl: () => void;
onGitHubUrlChange: (v: string) => void;
repositoryLocked?: boolean;
};
type RepoSourceInputProps = Pick<
DialogHeaderContentProps,
| "useGitHubUrl"
| "githubUrl"
| "githubUrlError"
| "onGitHubUrlChange"
| "isTaskStarted"
| "headerRepositoryOptions"
| "repositoryId"
| "discoveredRepoPath"
| "onRepositoryChange"
| "workspaceId"
| "repositoriesLoading"
| "discoverReposLoading"
| "repositoryLocked"
>;
function GhUrlInput({
githubUrl,
githubUrlError,
onGitHubUrlChange,
isTaskStarted,
}: Pick<
RepoSourceInputProps,
"githubUrl" | "githubUrlError" | "onGitHubUrlChange" | "isTaskStarted"
>): React.ReactNode {
return (
<div className="relative">
<input
type="text"
value={githubUrl}
onChange={(e) => onGitHubUrlChange(e.target.value)}
placeholder="github.com/owner/repo"
data-testid="github-url-input"
aria-label="GitHub repository URL"
aria-invalid={!!githubUrlError}
aria-describedby={githubUrlError ? "github-url-error" : undefined}
size={Math.max((githubUrl || "").length + 1, "github.com/owner/repo".length)}
className={`bg-transparent border-none outline-none focus:ring-0 text-sm font-medium min-w-0 h-7 rounded-md px-2 hover:bg-muted focus:bg-muted transition-colors placeholder:text-muted-foreground ${githubUrlError ? "text-destructive" : ""}`}
disabled={isTaskStarted}
autoFocus
/>
{githubUrlError && (
<div
id="github-url-error"
role="alert"
className="absolute left-0 top-full mt-1 z-50 rounded-md border bg-popover px-2 py-1 text-[11px] text-destructive shadow-md whitespace-nowrap"
data-testid="github-url-error"
>
{githubUrlError}
</div>
)}
</div>
);
}
function resolveSelectorPlaceholder(p: RepoSourceInputProps): string {
if (p.repositoryLocked && !(p.repositoryId || p.discoveredRepoPath)) {
return "Preparing kandev repository...";
}
return getRepositoryPlaceholder(p.workspaceId, p.repositoriesLoading, p.discoverReposLoading);
}
function RepoSourceInput(p: RepoSourceInputProps): React.ReactNode {
// When the dialog locks the repository (e.g. Improve Kandev pinning kdlbs/kandev),
// never expose the editable GitHub URL input — even if useGitHubUrl is on.
if (p.useGitHubUrl && !p.repositoryLocked) return <GhUrlInput {...p} />;
const isDisabled =
p.repositoryLocked ||
p.isTaskStarted ||
!p.workspaceId ||
p.repositoriesLoading ||
p.discoverReposLoading;
const emptyMessage =
p.repositoriesLoading || p.discoverReposLoading
? "Loading repositories..."
: "No repositories found.";
return (
<RepositorySelector
options={p.headerRepositoryOptions}
value={p.repositoryId || p.discoveredRepoPath}
onValueChange={p.onRepositoryChange}
placeholder={resolveSelectorPlaceholder(p)}
searchPlaceholder="Search repositories..."
emptyMessage={emptyMessage}
disabled={isDisabled}
triggerClassName="w-auto text-sm"
/>
);
}
export function DialogHeaderContent(props: DialogHeaderContentProps) {
const { isCreateMode, isEditMode, isTaskStarted, sessionRepoName, initialTitle } = props;
const { taskName, onTaskNameChange, useGitHubUrl, onToggleGitHubUrl, repositoryLocked } = props;
if (isCreateMode || isEditMode) {
return (
<>
<DialogTitle asChild>
<div className="flex items-center gap-1 text-sm font-medium min-w-0">
<RepoSourceInput {...props} />
<span className="text-muted-foreground mr-2">/</span>
<InlineTaskName
value={taskName}
onChange={onTaskNameChange}
autoFocus={!isEditMode && !useGitHubUrl}
/>
</div>
</DialogTitle>
{!isTaskStarted && !repositoryLocked && (
<div className="flex items-center gap-2 pl-2">
<button
type="button"
onClick={onToggleGitHubUrl}
className="text-xs text-muted-foreground hover:text-foreground cursor-pointer transition-colors"
data-testid="toggle-github-url"
>
{useGitHubUrl ? "or select a repository" : "or paste a GitHub URL"}
</button>
</div>
)}
</>
);
}
return (
<DialogTitle asChild>
<div className="flex items-center gap-1 min-w-0 text-sm font-medium">
{sessionRepoName && (
<>
<span className="truncate text-muted-foreground">{sessionRepoName}</span>
<span className="text-muted-foreground mx-0.5">/</span>
</>
)}
<span className="truncate">{initialTitle || "Task"}</span>
<span className="text-muted-foreground mx-0.5">/</span>
<span className="text-muted-foreground whitespace-nowrap">new session</span>
</div>
</DialogTitle>
);
}