11'use client' ;
22
3- import { Popover , PopoverContent , PopoverTrigger } from "@/components/ui/popover" ;
3+ import { getSecrets } from "@/actions" ;
4+ import { Button } from "@/components/ui/button" ;
45import {
56 Command ,
67 CommandEmpty ,
78 CommandGroup ,
89 CommandInput ,
910 CommandItem ,
1011 CommandList ,
11- } from "@/components/ui/command"
12- import { Button } from "@/components/ui/button" ;
13- import { cn , isServiceError , unwrapServiceError } from "@/lib/utils" ;
14- import { ChevronsUpDown , Check , PlusCircleIcon , Loader2 , Eye , EyeOff , TriangleAlert } from "lucide-react" ;
15- import { useCallback , useMemo , useState } from "react" ;
12+ } from "@/components/ui/command" ;
13+ import { Popover , PopoverContent , PopoverTrigger } from "@/components/ui/popover" ;
1614import { Separator } from "@/components/ui/separator" ;
17- import { useQuery } from "@tanstack/react-query" ;
18- import { checkIfSecretExists , createSecret , getSecrets } from "@/actions" ;
19- import { useDomain } from "@/hooks/useDomain" ;
20- import { Dialog , DialogTitle , DialogContent , DialogHeader , DialogDescription } from "@/components/ui/dialog" ;
21- import Link from "next/link" ;
22- import { Form , FormLabel , FormControl , FormDescription , FormItem , FormField , FormMessage } from "@/components/ui/form" ;
23- import { Input } from "@/components/ui/input" ;
24- import { z } from "zod" ;
25- import { zodResolver } from "@hookform/resolvers/zod" ;
26- import { useForm } from "react-hook-form" ;
27- import { useToast } from "@/components/hooks/use-toast" ;
28- import Image from "next/image" ;
29- import githubPatCreation from "@/public/github_pat_creation.png"
30- import { CodeHostType } from "@/lib/utils" ;
3115import { Tooltip , TooltipContent , TooltipProvider , TooltipTrigger } from "@/components/ui/tooltip" ;
32- import { isDefined } from '@/lib/utils'
3316import useCaptureEvent from "@/hooks/useCaptureEvent" ;
17+ import { useDomain } from "@/hooks/useDomain" ;
18+ import { cn , CodeHostType , isDefined , isServiceError , unwrapServiceError } from "@/lib/utils" ;
19+ import { useQuery } from "@tanstack/react-query" ;
20+ import { Check , ChevronsUpDown , Loader2 , PlusCircleIcon , TriangleAlert } from "lucide-react" ;
21+ import { useCallback , useState } from "react" ;
22+ import { ImportSecretDialog } from "../importSecretDialog" ;
23+
3424interface SecretComboBoxProps {
3525 isDisabled : boolean ;
3626 codeHostType : CodeHostType ;
@@ -171,256 +161,3 @@ export const SecretCombobox = ({
171161 </ >
172162 )
173163}
174-
175- interface ImportSecretDialogProps {
176- open : boolean ;
177- onOpenChange : ( open : boolean ) => void ;
178- onSecretCreated : ( key : string ) => void ;
179- codeHostType : CodeHostType ;
180- }
181-
182-
183- const ImportSecretDialog = ( { open, onOpenChange, onSecretCreated, codeHostType } : ImportSecretDialogProps ) => {
184- const [ showValue , setShowValue ] = useState ( false ) ;
185- const domain = useDomain ( ) ;
186- const { toast } = useToast ( ) ;
187- const captureEvent = useCaptureEvent ( ) ;
188-
189- const formSchema = z . object ( {
190- key : z . string ( ) . min ( 1 ) . refine ( async ( key ) => {
191- const doesSecretExist = await checkIfSecretExists ( key , domain ) ;
192- if ( ! isServiceError ( doesSecretExist ) ) {
193- captureEvent ( 'wa_secret_combobox_import_secret_fail' , {
194- type : codeHostType ,
195- error : "A secret with this key already exists." ,
196- } ) ;
197- }
198- return isServiceError ( doesSecretExist ) || ! doesSecretExist ;
199- } , "A secret with this key already exists." ) ,
200- value : z . string ( ) . min ( 1 ) ,
201- } ) ;
202-
203- const form = useForm < z . infer < typeof formSchema > > ( {
204- resolver : zodResolver ( formSchema ) ,
205- defaultValues : {
206- key : "" ,
207- value : "" ,
208- } ,
209- } ) ;
210- const { isSubmitting } = form . formState ;
211-
212- const onSubmit = useCallback ( async ( data : z . infer < typeof formSchema > ) => {
213- const response = await createSecret ( data . key , data . value , domain ) ;
214- if ( isServiceError ( response ) ) {
215- toast ( {
216- description : `❌ Failed to create secret`
217- } ) ;
218- captureEvent ( 'wa_secret_combobox_import_secret_fail' , {
219- type : codeHostType ,
220- error : response . message ,
221- } ) ;
222- } else {
223- toast ( {
224- description : `✅ Secret created successfully!`
225- } ) ;
226- captureEvent ( 'wa_secret_combobox_import_secret_success' , {
227- type : codeHostType ,
228- } ) ;
229- form . reset ( ) ;
230- onOpenChange ( false ) ;
231- onSecretCreated ( data . key ) ;
232- }
233- } , [ domain , toast , onOpenChange , onSecretCreated , form , codeHostType , captureEvent ] ) ;
234-
235- const codeHostSpecificStep = useMemo ( ( ) => {
236- switch ( codeHostType ) {
237- case 'github' :
238- return < GitHubPATCreationStep step = { 1 } /> ;
239- case 'gitlab' :
240- return < GitLabPATCreationStep step = { 1 } /> ;
241- case 'gitea' :
242- return < GiteaPATCreationStep step = { 1 } /> ;
243- case 'gerrit' :
244- return null ;
245- }
246- } , [ codeHostType ] ) ;
247-
248-
249- return (
250- < Dialog
251- open = { open }
252- onOpenChange = { onOpenChange }
253- >
254- < DialogContent
255- className = "p-16 max-w-[90vw] sm:max-w-2xl max-h-[80vh] overflow-scroll rounded-lg"
256- >
257- < DialogHeader >
258- < DialogTitle className = "text-2xl font-semibold" > Import a secret</ DialogTitle >
259- < DialogDescription >
260- Secrets are used to authenticate with a code host. They are encrypted at rest using < Link href = "https://en.wikipedia.org/wiki/Advanced_Encryption_Standard" target = "_blank" className = "underline" > AES-256-CBC</ Link > .
261- Checkout our < Link href = "https://sourcebot.dev/security" target = "_blank" className = "underline" > security docs</ Link > for more information.
262- </ DialogDescription >
263- </ DialogHeader >
264-
265- < Form
266- { ...form }
267- >
268- < form
269- className = "space-y-4 flex flex-col mt-4 gap-4"
270- onSubmit = { ( event ) => {
271- event . stopPropagation ( ) ;
272- form . handleSubmit ( onSubmit ) ( event ) ;
273- } }
274- >
275- { codeHostSpecificStep }
276-
277- < SecretCreationStep
278- step = { 2 }
279- title = "Import the secret"
280- description = "Copy the generated token and paste it below."
281- >
282- < FormField
283- control = { form . control }
284- name = "value"
285- render = { ( { field } ) => (
286- < FormItem >
287- < FormLabel > Value</ FormLabel >
288- < FormControl >
289- < div className = "relative" >
290- < Input
291- { ...field }
292- type = { showValue ? "text" : "password" }
293- placeholder = "Enter your secret value"
294- />
295- < Button
296- type = "button"
297- variant = "ghost"
298- size = "sm"
299- className = "absolute right-2 top-1/2 -translate-y-1/2"
300- onClick = { ( ) => setShowValue ( ! showValue ) }
301- >
302- { showValue ? (
303- < EyeOff className = "h-4 w-4" />
304- ) : (
305- < Eye className = "h-4 w-4" />
306- ) }
307- </ Button >
308- </ div >
309- </ FormControl >
310- < FormDescription >
311- The secret value to store securely.
312- </ FormDescription >
313- < FormMessage />
314- </ FormItem >
315- ) }
316- />
317- </ SecretCreationStep >
318-
319- < SecretCreationStep
320- step = { 3 }
321- title = "Name the secret"
322- description = "Give the secret a unique name so that it can be referenced in a connection config."
323- >
324- < FormField
325- control = { form . control }
326- name = "key"
327- render = { ( { field } ) => (
328- < FormItem >
329- < FormLabel > Key</ FormLabel >
330- < FormControl >
331- < Input
332- placeholder = "my-github-token"
333- { ...field }
334- />
335- </ FormControl >
336- < FormDescription >
337- A unique name to identify this secret.
338- </ FormDescription >
339- < FormMessage />
340- </ FormItem >
341- ) }
342- />
343- </ SecretCreationStep >
344-
345- < div className = "flex justify-end w-full" >
346- < Button
347- type = "submit"
348- disabled = { isSubmitting }
349- >
350- { isSubmitting && < Loader2 className = "h-4 w-4 animate-spin mr-2" /> }
351- Import Secret
352- </ Button >
353- </ div >
354- </ form >
355- </ Form >
356- </ DialogContent >
357- </ Dialog >
358- )
359- }
360-
361- const GitHubPATCreationStep = ( { step } : { step : number } ) => {
362- return (
363- < SecretCreationStep
364- step = { step }
365- title = "Create a Personal Access Token"
366- description = < span > Navigate to < Link href = "https://github.com/settings/tokens/new" target = "_blank" className = "underline" > here on github.com</ Link > (or your enterprise instance) and create a new personal access token. Sourcebot needs the < strong > repo</ strong > scope in order to access private repositories:</ span >
367- >
368- < Image
369- className = "mx-auto"
370- src = { githubPatCreation }
371- alt = "Create a personal access token"
372- width = { 500 }
373- height = { 500 }
374- />
375- </ SecretCreationStep >
376- )
377- }
378-
379- const GitLabPATCreationStep = ( { step } : { step : number } ) => {
380- return (
381- < SecretCreationStep
382- step = { step }
383- title = "Create a Personal Access Token"
384- description = "todo"
385- >
386- < p > todo</ p >
387- </ SecretCreationStep >
388- )
389- }
390-
391- const GiteaPATCreationStep = ( { step } : { step : number } ) => {
392- return (
393- < SecretCreationStep
394- step = { step }
395- title = "Create a Personal Access Token"
396- description = "todo"
397- >
398- < p > todo</ p >
399- </ SecretCreationStep >
400- )
401- }
402-
403- interface SecretCreationStepProps {
404- step : number ;
405- title : string ;
406- description : string | React . ReactNode ;
407- children : React . ReactNode ;
408- }
409-
410- const SecretCreationStep = ( { step, title, description, children } : SecretCreationStepProps ) => {
411- return (
412- < div className = "relative flex flex-col gap-2" >
413- < div className = "absolute -left-10 flex flex-col items-center gap-2 h-full" >
414- < span className = "text-md font-semibold border rounded-full px-2" > { step } </ span >
415- < Separator className = "h-5/6" orientation = "vertical" />
416- </ div >
417- < h3 className = "text-md font-semibold" >
418- { title }
419- </ h3 >
420- < p className = "text-sm text-muted-foreground" >
421- { description }
422- </ p >
423- { children }
424- </ div >
425- )
426- }
0 commit comments