blob: 185a884b42b87d4cb8555a7dbda9d3d69c7e2e18 [file] [log] [blame]
giod0026612025-05-08 13:00:36 +00001import { useProjectId, useGithubService, useStateStore } from "@/lib/state";
2import { Form, FormControl, FormField, FormItem, FormMessage } from "./components/ui/form";
3import { Input } from "./components/ui/input";
4import { useForm } from "react-hook-form";
5import { z } from "zod";
6import { zodResolver } from "@hookform/resolvers/zod";
7import { Button } from "./components/ui/button";
8import { useToast } from "@/hooks/use-toast";
gio02f1cad2025-05-13 11:51:55 +00009import { CircleCheck, CircleX } from "lucide-react";
giod0026612025-05-08 13:00:36 +000010import { useState, useCallback } from "react";
gio7f98e772025-05-07 11:00:14 +000011
12const schema = z.object({
giod0026612025-05-08 13:00:36 +000013 githubToken: z.string().min(1, "GitHub token is required"),
gio7f98e772025-05-07 11:00:14 +000014});
15
16export function Integrations() {
giod0026612025-05-08 13:00:36 +000017 const { toast } = useToast();
18 const store = useStateStore();
19 const projectId = useProjectId();
20 const [isEditing, setIsEditing] = useState(false);
21 const githubService = useGithubService();
22 const [isSaving, setIsSaving] = useState(false);
gio7f98e772025-05-07 11:00:14 +000023
giod0026612025-05-08 13:00:36 +000024 const form = useForm<z.infer<typeof schema>>({
25 resolver: zodResolver(schema),
26 mode: "onChange",
27 });
gio7f98e772025-05-07 11:00:14 +000028
giod0026612025-05-08 13:00:36 +000029 const onSubmit = useCallback(
30 async (data: z.infer<typeof schema>) => {
31 if (!projectId) return;
gio7f98e772025-05-07 11:00:14 +000032
giod0026612025-05-08 13:00:36 +000033 setIsSaving(true);
gio7f98e772025-05-07 11:00:14 +000034
giod0026612025-05-08 13:00:36 +000035 try {
36 const response = await fetch(`/api/project/${projectId}/github-token`, {
37 method: "POST",
38 headers: {
39 "Content-Type": "application/json",
40 },
41 body: JSON.stringify({ githubToken: data.githubToken }),
42 });
gio7f98e772025-05-07 11:00:14 +000043
giod0026612025-05-08 13:00:36 +000044 if (!response.ok) {
45 throw new Error("Failed to save GitHub token");
46 }
gio7f98e772025-05-07 11:00:14 +000047
giod0026612025-05-08 13:00:36 +000048 await store.refreshEnv();
49 setIsEditing(false);
50 form.reset();
51 toast({
52 title: "GitHub token saved successfully",
53 });
54 } catch (error) {
55 toast({
56 variant: "destructive",
57 title: "Failed to save GitHub token",
58 description: error instanceof Error ? error.message : "Unknown error",
59 });
60 } finally {
61 setIsSaving(false);
62 }
63 },
64 [projectId, store, form, toast, setIsEditing, setIsSaving],
65 );
gio7f98e772025-05-07 11:00:14 +000066
giod0026612025-05-08 13:00:36 +000067 const handleCancel = () => {
68 setIsEditing(false);
69 form.reset();
70 };
gio7f98e772025-05-07 11:00:14 +000071
giod0026612025-05-08 13:00:36 +000072 return (
gio02f1cad2025-05-13 11:51:55 +000073 <div className="px-4 py-1">
74 <div className="flex flex-col gap-1">
75 <div className="flex flex-row items-center gap-1">
76 {githubService ? <CircleCheck /> : <CircleX />}
77 <div>Github</div>
giod0026612025-05-08 13:00:36 +000078 </div>
gio3a921b82025-05-10 07:36:09 +000079
80 {!!githubService && !isEditing && (
gio02f1cad2025-05-13 11:51:55 +000081 <Button variant="outline" className="w-fit" onClick={() => setIsEditing(true)}>
gio3a921b82025-05-10 07:36:09 +000082 Update Access Token
83 </Button>
84 )}
85
86 {(!githubService || isEditing) && (
gio02f1cad2025-05-13 11:51:55 +000087 <div className="flex flex-row items-center gap-1 text-sm">
gio33046722025-05-16 14:49:55 +000088 <div>
89 Follow the link to generate new PAT:{" "}
90 <a href="https://github.com/settings/personal-access-tokens" target="_blank">
91 https://github.com/settings/personal-access-tokens
92 </a>
93 <br />
94 Grant following <b>Repository</b> permissions:
95 <ul>
96 <li>
97 <b>Contents</b> - Read-only access so dodo can clone the repository
98 </li>
99 <li>
100 <b>Metadata</b> - Read-only access so dodo can search for repositories
101 </li>
102 <li>
103 <b>Administration</b> - Read and write access so dodo automatically add deploy keys
104 to repositories
105 </li>
106 </ul>
107 </div>
gio02f1cad2025-05-13 11:51:55 +0000108 </div>
109 )}
110 {(!githubService || isEditing) && (
gio3a921b82025-05-10 07:36:09 +0000111 <Form {...form}>
gio02f1cad2025-05-13 11:51:55 +0000112 <form className="space-y-2" onSubmit={form.handleSubmit(onSubmit)}>
gio3a921b82025-05-10 07:36:09 +0000113 <FormField
114 control={form.control}
115 name="githubToken"
116 render={({ field }) => (
117 <FormItem>
118 <FormControl>
119 <Input
120 type="password"
gio02f1cad2025-05-13 11:51:55 +0000121 placeholder="Personal Access Token"
122 className="w-1/4"
gio3a921b82025-05-10 07:36:09 +0000123 {...field}
124 />
125 </FormControl>
126 <FormMessage />
127 </FormItem>
128 )}
129 />
gio02f1cad2025-05-13 11:51:55 +0000130 <div className="flex flex-row items-center gap-1">
gio3a921b82025-05-10 07:36:09 +0000131 <Button type="submit" disabled={isSaving}>
132 {isSaving ? "Saving..." : "Save"}
133 </Button>
134 {!!githubService && (
135 <Button type="button" variant="outline" onClick={handleCancel} disabled={isSaving}>
136 Cancel
137 </Button>
138 )}
139 </div>
140 </form>
141 </Form>
142 )}
giod0026612025-05-08 13:00:36 +0000143 </div>
144 </div>
145 );
146}