Canvas: Github repository picker

Change-Id: Icb8f2ffbef2894b2fdea4e4c13c74c0f4970506b
diff --git a/apps/canvas/front/src/components/node-github.tsx b/apps/canvas/front/src/components/node-github.tsx
index aa48cd1..3ae779e 100644
--- a/apps/canvas/front/src/components/node-github.tsx
+++ b/apps/canvas/front/src/components/node-github.tsx
@@ -1,12 +1,16 @@
 import { NodeRect } from './node-rect';
-import { GithubNode, nodeIsConnectable, nodeLabel, useStateStore } from '@/lib/state';
-import { useEffect, useMemo } from 'react';
+import { GithubNode, nodeIsConnectable, nodeLabel, useStateStore, useGithubService } 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 { Input } from './ui/input';
 import { Handle, Position } from "@xyflow/react";
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './ui/select';
+import { GitHubRepository } from '../lib/github';
+import { useProjectId } from '@/lib/state';
+import { Alert, AlertDescription } from './ui/alert';
+import { AlertCircle } from 'lucide-react';
 
 export function NodeGithub(node: GithubNode) {
   const { id, selected } = node;
@@ -22,54 +26,136 @@
           isConnectableStart={isConnectable}
           isConnectableEnd={isConnectable}
           isConnectable={isConnectable}
-         />
+        />
       </div>
     </NodeRect>
   );
 }
 
 const schema = z.object({
-  address: z.string().min(1),
+  repositoryId: z.number().optional(),
 });
 
 export function NodeGithubDetails(node: GithubNode) {
   const { id, data } = node;
   const store = useStateStore();
+  const projectId = useProjectId();
+  const [repos, setRepos] = useState<GitHubRepository[]>([]);
+  const [loading, setLoading] = useState(false);
+  const [error, setError] = useState<string | null>(null);
+  const githubService = useGithubService();
+
+  useEffect(() => {
+    if (data.repository) {
+      const { id, sshURL } = data.repository;
+      setRepos(prevRepos => {
+        if (!prevRepos.some(r => r.id === id)) {
+          return [...prevRepos, {
+            id,
+            name: sshURL.split('/').pop() || '',
+            full_name: sshURL.split('/').slice(-2).join('/'),
+            html_url: '',
+            ssh_url: sshURL,
+            description: null,
+            private: false,
+            default_branch: 'main'
+          }];
+        }
+        return prevRepos;
+      });
+    }
+  }, [data.repository]);
+
   const form = useForm<z.infer<typeof schema>>({
     resolver: zodResolver(schema),
     mode: "onChange",
     defaultValues: {
-      address: data.address,
+      repositoryId: data.repository?.id,
     }
   });
+
   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 "address":
-          store.updateNodeData<"github">(id, {
-            address: value.address,
-          });
+        case "repositoryId":
+          if (value.repositoryId) {
+            const repo = repos.find(r => r.id === value.repositoryId);
+            if (repo) {
+              store.updateNodeData<"github">(id, {
+                repository: {
+                  id: repo.id,
+                  sshURL: repo.ssh_url,
+                },
+              });
+            }
+          }
           break;
       }
     });
     return () => sub.unsubscribe();
-  }, [form, store]);
+  }, [form, store, id, repos]);
+
+  useEffect(() => {
+    const fetchRepositories = async () => {
+      if (!!!githubService) return;
+
+      setLoading(true);
+      setError(null);
+      try {
+        const repositories = await githubService.getRepositories();
+        setRepos(repositories);
+      } catch (err) {
+        setError(err instanceof Error ? err.message : "Failed to fetch repositories");
+      } finally {
+        setLoading(false);
+      }
+    };
+
+    fetchRepositories();
+  }, [githubService]);
+
   return (
     <>
       <Form {...form}>
         <form className="space-y-2">
-          <FormField 
+          <FormField
             control={form.control}
-            name="address"
+            name="repositoryId"
             render={({ field }) => (
               <FormItem>
-                <FormControl>
-                  <Input placeholder="address" className="border border-black" {...field} />
-                </FormControl>
+                <Select
+                  onValueChange={(value) => field.onChange(Number(value))}
+                  value={field.value?.toString()}
+                  disabled={loading || !projectId || !!!githubService}
+                >
+                  <FormControl>
+                    <SelectTrigger>
+                      <SelectValue placeholder={!!githubService ? "Select a repository" : "GitHub not configured"} />
+                    </SelectTrigger>
+                  </FormControl>
+                  <SelectContent>
+                    {repos.map((repo) => (
+                      <SelectItem key={repo.id} value={repo.id.toString()}>
+                        {repo.full_name}
+                        {repo.description && ` - ${repo.description}`}
+                      </SelectItem>
+                    ))}
+                  </SelectContent>
+                </Select>
                 <FormMessage />
+                {error && <p className="text-sm text-red-500">{error}</p>}
+                {loading && <p className="text-sm text-gray-500">Loading repositories...</p>}
+                {!!!githubService && (
+                  <Alert variant="destructive" className="mt-2">
+                    <AlertCircle className="h-4 w-4" />
+                    <AlertDescription>
+                      GitHub access token is not configured. Please configure it in the Integrations tab.
+                    </AlertDescription>
+                  </Alert>
+                )}
               </FormItem>
             )}
           />