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