24c3b594ce
Co-authored-by: Kandev Agent <agent@kandev.dev> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
147 lines
5.0 KiB
TypeScript
147 lines
5.0 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
import { Checkbox } from "@kandev/ui/checkbox";
|
|
import { Label } from "@kandev/ui/label";
|
|
import { Input } from "@kandev/ui/input";
|
|
import { Switch } from "@kandev/ui/switch";
|
|
import { Tooltip, TooltipContent, TooltipTrigger } from "@kandev/ui/tooltip";
|
|
import { IconInfoCircle } from "@tabler/icons-react";
|
|
import { RepoFilterSelector } from "@/components/github/repo-filter-selector";
|
|
import type { RepoFilter } from "@/lib/types/github";
|
|
|
|
type GitHubPRConfigProps = {
|
|
config: Record<string, unknown>;
|
|
onUpdate: (config: Record<string, unknown>) => void;
|
|
};
|
|
|
|
// `value` is the GitHub PR event name persisted in the trigger config and
|
|
// matched by the backend — protocol. Label and tooltip are copy.
|
|
const PR_EVENTS = [
|
|
{
|
|
value: "opened",
|
|
labelKey: "automations:prEventOpened",
|
|
tooltipKey: "automations:prEventOpenedTooltip",
|
|
},
|
|
] as const;
|
|
|
|
export function GitHubPRConfig({ config, onUpdate }: GitHubPRConfigProps) {
|
|
const { t } = useTranslation();
|
|
const events = (config.events as string[]) ?? [];
|
|
const repos = (config.repos as RepoFilter[]) ?? [];
|
|
const allRepos = (config.all_repos as boolean) ?? true;
|
|
const configBranches = ((config.branches as string[]) ?? []).join(", ");
|
|
const configAuthors = ((config.authors as string[]) ?? []).join(", ");
|
|
const [branches, setBranches] = useState(configBranches);
|
|
const [authors, setAuthors] = useState(configAuthors);
|
|
useEffect(() => {
|
|
setBranches(configBranches);
|
|
}, [configBranches]);
|
|
useEffect(() => {
|
|
setAuthors(configAuthors);
|
|
}, [configAuthors]);
|
|
const excludeDraft = (config.exclude_draft as boolean) ?? false;
|
|
|
|
const toggleEvent = (event: string) => {
|
|
const next = events.includes(event) ? events.filter((e) => e !== event) : [...events, event];
|
|
onUpdate({ ...config, events: next });
|
|
};
|
|
|
|
const handleBranchesBlur = () => {
|
|
const parsed = branches
|
|
.split(",")
|
|
.map((b) => b.trim())
|
|
.filter(Boolean);
|
|
onUpdate({ ...config, branches: parsed });
|
|
};
|
|
|
|
const handleAuthorsBlur = () => {
|
|
const parsed = authors
|
|
.split(",")
|
|
.map((a) => a.trim())
|
|
.filter(Boolean);
|
|
onUpdate({ ...config, authors: parsed });
|
|
};
|
|
|
|
return (
|
|
<div className="space-y-3">
|
|
<EventsSection events={events} onToggle={toggleEvent} />
|
|
<RepoFilterSelector
|
|
allRepos={allRepos}
|
|
selectedRepos={repos}
|
|
onAllReposChange={(checked) => {
|
|
onUpdate({ ...config, all_repos: checked, repos: checked ? [] : repos });
|
|
}}
|
|
onSelectedReposChange={(next) => onUpdate({ ...config, repos: next })}
|
|
/>
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<div className="space-y-1.5">
|
|
<Label className="text-xs">{t("automations:baseBranchesLabel")}</Label>
|
|
{/* Example branch names — data the user types verbatim, not copy. */}
|
|
<Input
|
|
value={branches}
|
|
onChange={(e) => setBranches(e.target.value)}
|
|
onBlur={handleBranchesBlur}
|
|
// eslint-disable-next-line i18next/no-literal-string -- example branch names, see above
|
|
placeholder="main, develop"
|
|
/>
|
|
</div>
|
|
<div className="space-y-1.5">
|
|
<Label className="text-xs">{t("automations:authorsLabel")}</Label>
|
|
{/* Example GitHub usernames — data, not copy. */}
|
|
<Input
|
|
value={authors}
|
|
onChange={(e) => setAuthors(e.target.value)}
|
|
onBlur={handleAuthorsBlur}
|
|
// eslint-disable-next-line i18next/no-literal-string -- example usernames, see above
|
|
placeholder="alice, bob"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<Switch
|
|
size="sm"
|
|
checked={excludeDraft}
|
|
onCheckedChange={(checked) => onUpdate({ ...config, exclude_draft: checked })}
|
|
className="cursor-pointer"
|
|
/>
|
|
<Label className="text-xs">{t("automations:excludeDraftPrs")}</Label>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function EventsSection({
|
|
events,
|
|
onToggle,
|
|
}: {
|
|
events: string[];
|
|
onToggle: (event: string) => void;
|
|
}) {
|
|
const { t } = useTranslation();
|
|
return (
|
|
<div className="space-y-2">
|
|
<Label className="text-xs">{t("automations:eventsLabel")}</Label>
|
|
<div className="flex flex-wrap gap-3">
|
|
{PR_EVENTS.map((evt) => (
|
|
<label key={evt.value} className="flex items-center gap-1.5 cursor-pointer">
|
|
<Checkbox
|
|
checked={events.includes(evt.value)}
|
|
onCheckedChange={() => onToggle(evt.value)}
|
|
className="cursor-pointer"
|
|
/>
|
|
<span className="text-sm">{t(evt.labelKey)}</span>
|
|
<Tooltip>
|
|
<TooltipTrigger asChild>
|
|
<IconInfoCircle className="h-3 w-3 text-muted-foreground" />
|
|
</TooltipTrigger>
|
|
<TooltipContent className="max-w-[220px]">{t(evt.tooltipKey)}</TooltipContent>
|
|
</Tooltip>
|
|
</label>
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|