Canvas: Github repository picker

Change-Id: Icb8f2ffbef2894b2fdea4e4c13c74c0f4970506b
diff --git a/apps/canvas/front/src/Integrations.tsx b/apps/canvas/front/src/Integrations.tsx
new file mode 100644
index 0000000..a566c22
--- /dev/null
+++ b/apps/canvas/front/src/Integrations.tsx
@@ -0,0 +1,132 @@
+import { useProjectId, useGithubService, useStateStore } from '@/lib/state';
+import { Form, FormControl, FormField, FormItem, FormMessage } from './components/ui/form';
+import { Input } from './components/ui/input';
+import { useForm } from 'react-hook-form';
+import { z } from 'zod';
+import { zodResolver } from '@hookform/resolvers/zod';
+import { Button } from './components/ui/button';
+import { useToast } from '@/hooks/use-toast';
+import { Checkbox } from './components/ui/checkbox';
+import { useState, useCallback } from 'react';
+
+const schema = z.object({
+    githubToken: z.string().min(1, "GitHub token is required"),
+});
+
+export function Integrations() {
+    const { toast } = useToast();
+    const store = useStateStore();
+    const projectId = useProjectId();
+    const [isEditing, setIsEditing] = useState(false);
+    const githubService = useGithubService();
+    const [isSaving, setIsSaving] = useState(false);
+
+    const form = useForm<z.infer<typeof schema>>({
+        resolver: zodResolver(schema),
+        mode: "onChange",
+    });
+
+    const onSubmit = useCallback(async (data: z.infer<typeof schema>) => {
+        if (!projectId) return;
+
+        setIsSaving(true);
+
+        try {
+            const response = await fetch(`/api/project/${projectId}/github-token`, {
+                method: "POST",
+                headers: {
+                    "Content-Type": "application/json",
+                },
+                body: JSON.stringify({ githubToken: data.githubToken }),
+            });
+
+            if (!response.ok) {
+                throw new Error("Failed to save GitHub token");
+            }
+
+            await store.refreshEnv();
+            setIsEditing(false);
+            form.reset();
+            toast({
+                title: "GitHub token saved successfully",
+            });
+        } catch (error) {
+            toast({
+                variant: "destructive",
+                title: "Failed to save GitHub token",
+                description: error instanceof Error ? error.message : "Unknown error",
+            });
+        } finally {
+            setIsSaving(false);
+        }
+    }, [projectId, store, form, toast, setIsEditing, setIsSaving]);
+
+    const handleCancel = () => {
+        setIsEditing(false);
+        form.reset();
+    };
+
+    return (
+        <div className="space-y-6">
+            <div>
+                <h3 className="text-md font-medium mb-2">GitHub</h3>
+                <div className="space-y-4">
+                    <div className="flex items-center space-x-2">
+                        <Checkbox checked={!!githubService} disabled />
+                        <label className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70">
+                            GitHub Access Token
+                        </label>
+                    </div>
+
+                    {!!githubService && !isEditing && (
+                        <Button
+                            variant="outline"
+                            onClick={() => setIsEditing(true)}
+                        >
+                            Update Access Token
+                        </Button>
+                    )}
+
+                    {(!!!githubService || isEditing) && (
+                        <Form {...form}>
+                            <form className="space-y-4" onSubmit={form.handleSubmit(onSubmit)}>
+                                <FormField
+                                    control={form.control}
+                                    name="githubToken"
+                                    render={({ field }) => (
+                                        <FormItem>
+                                            <FormControl>
+                                                <Input
+                                                    type="password"
+                                                    placeholder="GitHub Personal Access Token"
+                                                    className="border border-black"
+                                                    {...field}
+                                                />
+                                            </FormControl>
+                                            <FormMessage />
+                                        </FormItem>
+                                    )}
+                                />
+                                <div className="flex space-x-2">
+                                    <Button type="submit" disabled={isSaving}>
+                                        {isSaving ? "Saving..." : "Save"}
+                                    </Button>
+                                    {!!githubService && (
+                                        <Button
+                                            type="button"
+                                            variant="outline"
+                                            onClick={handleCancel}
+                                            disabled={isSaving}
+                                        >
+                                            Cancel
+                                        </Button>
+                                    )}
+                                </div>
+                            </form>
+                        </Form>
+                    )}
+                </div>
+            </div>
+        </div>
+    );
+} 
\ No newline at end of file