AddNewSecretModal.tsx143 lines · main
1import { zodResolver } from '@hookform/resolvers/zod'
2import { parseAsBoolean, useQueryState } from 'nuqs'
3import { SubmitHandler, useForm } from 'react-hook-form'
4import { toast } from 'sonner'
5import {
6 Button,
7 Dialog,
8 DialogContent,
9 DialogFooter,
10 DialogHeader,
11 DialogSection,
12 DialogSectionSeparator,
13 DialogTitle,
14 Form,
15 FormControl,
16 FormField,
17 Input,
18} from 'ui'
19import { Input as PasswordInput } from 'ui-patterns/DataInputs/Input'
20import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
21import * as z from 'zod'
22
23import { useVaultSecretCreateMutation } from '@/data/vault/vault-secret-create-mutation'
24import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
25
26const formSchema = z.object({
27 name: z.string().min(1, 'Please provide a name for your secret'),
28 description: z.string().optional(),
29 secret: z.string().min(1, 'Please enter your secret value'),
30})
31
32type FormSchema = z.infer<typeof formSchema>
33
34const formId = 'add-new-secret-form'
35export const AddNewSecretModal = () => {
36 const { data: project } = useSelectedProjectQuery()
37
38 const { mutateAsync: addSecret } = useVaultSecretCreateMutation()
39
40 const [showAddSecretModal, setShowAddSecretModal] = useQueryState(
41 'new',
42 parseAsBoolean.withDefault(false)
43 )
44
45 const handleClose = () => {
46 setShowAddSecretModal(null)
47 form.reset()
48 }
49
50 const onAddNewSecret: SubmitHandler<FormSchema> = async (values) => {
51 if (!project) return console.error('Project is required')
52
53 try {
54 await addSecret({
55 projectRef: project.ref,
56 connectionString: project?.connectionString,
57 name: values.name,
58 description: values.description,
59 secret: values.secret,
60 })
61 toast.success(`Successfully added new secret ${values.name}`)
62 handleClose()
63 } catch (error: any) {
64 // [Joshen] No error handler required as they are all handled within the mutations already
65 } finally {
66 }
67 }
68
69 const form = useForm<FormSchema>({
70 resolver: zodResolver(formSchema as any),
71 defaultValues: { name: '', description: '', secret: '' },
72 })
73
74 const { isDirty, isSubmitting } = form.formState
75
76 return (
77 <Dialog open={showAddSecretModal} onOpenChange={handleClose}>
78 <DialogContent className="sm:max-w-[425px]">
79 <DialogHeader>
80 <DialogTitle>Add new secret</DialogTitle>
81 </DialogHeader>
82 <DialogSectionSeparator />
83 <DialogSection className="space-y-4">
84 <Form {...form}>
85 <form
86 id={formId}
87 noValidate
88 onSubmit={form.handleSubmit(onAddNewSecret)}
89 className="space-y-4"
90 >
91 <FormField
92 control={form.control}
93 name="name"
94 render={({ field }) => (
95 <FormItemLayout layout="vertical" label="Name">
96 <FormControl className="col-span-6">
97 <Input {...field} />
98 </FormControl>
99 </FormItemLayout>
100 )}
101 />
102 <FormField
103 control={form.control}
104 name="description"
105 render={({ field }) => (
106 <FormItemLayout layout="vertical" label="Description" labelOptional="Optional">
107 <FormControl className="col-span-6">
108 <Input {...field} />
109 </FormControl>
110 </FormItemLayout>
111 )}
112 />
113 <FormField
114 control={form.control}
115 name="secret"
116 render={({ field }) => (
117 <FormItemLayout layout="vertical" label="Secret value">
118 <FormControl className="col-span-6">
119 <PasswordInput reveal copy {...field} />
120 </FormControl>
121 </FormItemLayout>
122 )}
123 />
124 </form>
125 </Form>
126 </DialogSection>
127 <DialogFooter>
128 <Button type="default" disabled={isSubmitting} onClick={handleClose}>
129 Cancel
130 </Button>
131 <Button
132 form={formId}
133 htmlType="submit"
134 disabled={!isDirty || isSubmitting}
135 loading={isSubmitting}
136 >
137 Add secret
138 </Button>
139 </DialogFooter>
140 </DialogContent>
141 </Dialog>
142 )
143}