blob: fbb63e4426dd65978c7299fcb0f9e201afbcc88f [file] [log] [blame]
import { NodeRect } from "./node-rect";
import {
GithubNode,
nodeIsConnectable,
nodeLabel,
useStateStore,
useGithubService,
useGithubRepositories,
useGithubRepositoriesLoading,
useGithubRepositoriesError,
useFetchGithubRepositories,
AppNode,
} from "@/lib/state";
import { useEffect, useMemo, useState } from "react";
import { z } from "zod";
import { DeepPartial, EventType, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { Form, FormControl, FormField, FormItem, FormMessage } from "./ui/form";
import { Handle, Position } from "@xyflow/react";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select";
import { useProjectId } from "@/lib/state";
import { Alert, AlertDescription } from "./ui/alert";
import { AlertCircle, LoaderCircle, RefreshCw } from "lucide-react";
import { Button } from "./ui/button";
import { NodeDetailsProps } from "@/lib/types";
import { ImportModal } from "./import-modal";
export function NodeGithub(node: GithubNode) {
const { id, selected } = node;
const isConnectable = useMemo(() => nodeIsConnectable(node, "repository"), [node]);
return (
<NodeRect id={id} selected={selected} node={node} state={node.data.state}>
<div style={{ padding: "10px 20px" }}>
{nodeLabel(node)}
<Handle
id="repository"
type={"source"}
position={Position.Right}
isConnectableStart={isConnectable}
isConnectableEnd={isConnectable}
isConnectable={isConnectable}
/>
</div>
</NodeRect>
);
}
const schema = z.object({
repositoryId: z.number().optional(),
});
export function NodeGithubDetails({ node, disabled }: NodeDetailsProps<GithubNode>) {
const { id, data } = node;
const store = useStateStore();
const projectId = useProjectId();
const githubService = useGithubService();
const storeRepos = useGithubRepositories();
const isLoadingRepos = useGithubRepositoriesLoading();
const repoError = useGithubRepositoriesError();
const fetchStoreRepositories = useFetchGithubRepositories();
const [showImportModal, setShowImportModal] = useState(false);
const form = useForm<z.infer<typeof schema>>({
resolver: zodResolver(schema),
mode: "onChange",
defaultValues: {
repositoryId: data.repository?.id,
},
});
useEffect(() => {
form.reset({ repositoryId: data.repository?.id });
}, [data.repository?.id, form]);
useEffect(() => {
const sub = form.watch(
(
value: DeepPartial<z.infer<typeof schema>>,
{ name, type }: { name?: keyof z.infer<typeof schema> | undefined; type?: EventType | undefined },
) => {
if (type !== "change") {
return;
}
switch (name) {
case "repositoryId":
if (value.repositoryId) {
const repo = storeRepos.find((r) => r.id === value.repositoryId);
if (!repo) {
return;
}
store.setNodes(
store.nodes.map((n): AppNode => {
if (n.type === "github" && n.id === id) {
return {
...n,
data: {
...n.data,
repository: {
id: repo.id,
sshURL: repo.ssh_url,
fullName: repo.full_name,
},
},
};
} else if (n.type === "app" && n.data.repository?.repoNodeId === id) {
return {
...n,
data: { ...n.data, repository: { id: repo.id, repoNodeId: id } },
};
} else {
return n;
}
}),
);
}
break;
}
},
);
return () => sub.unsubscribe();
}, [form, store, id, storeRepos]);
return (
<>
<Form {...form}>
<form className="space-y-2">
<FormField
control={form.control}
name="repositoryId"
render={({ field }) => (
<FormItem>
<div className="flex items-center gap-2 w-full">
<div className="flex-grow">
<Select
onValueChange={(value) => field.onChange(Number(value))}
value={field.value?.toString()}
disabled={isLoadingRepos || !projectId || !githubService || disabled}
>
<FormControl>
<SelectTrigger>
<SelectValue
placeholder={
githubService
? isLoadingRepos
? "Loading..."
: storeRepos.length === 0
? "No repositories found"
: "Select a repository"
: "GitHub not configured"
}
/>
</SelectTrigger>
</FormControl>
<SelectContent>
{storeRepos.map((repo) => (
<SelectItem key={repo.id} value={repo.id.toString()}>
{repo.full_name}
{repo.description && ` - ${repo.description}`}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
{isLoadingRepos && (
<Button variant="ghost" size="icon" disabled>
<LoaderCircle className="h-5 w-5 animate-spin text-muted-foreground" />
</Button>
)}
{!isLoadingRepos && githubService && (
<Button
variant="ghost"
size="icon"
onClick={fetchStoreRepositories}
disabled={disabled}
aria-label="Refresh repositories"
>
<RefreshCw className="h-5 w-5 text-muted-foreground" />
</Button>
)}
</div>
<FormMessage />
{repoError && <p className="text-sm text-red-500 mt-1">{repoError}</p>}
{!githubService && (
<Alert variant="destructive" className="mt-2">
<AlertCircle className="h-4 w-4" />
<AlertDescription>
Please configure Github Personal Access Token in the Integrations tab.
</AlertDescription>
</Alert>
)}
</FormItem>
)}
/>
</form>
</Form>
<Button
disabled={!data.repository?.sshURL || !githubService || disabled}
onClick={() => setShowImportModal(true)}
>
Scan for services
</Button>
<ImportModal
open={showImportModal}
onOpenChange={setShowImportModal}
initialRepositoryId={data.repository?.id}
/>
</>
);
}