11import { standardSchemaResolver as zodResolver } from "@hookform/resolvers/standard-schema" ;
2- import { HelpCircle , PlusIcon } from "lucide-react" ;
2+ import { HelpCircle , PlusIcon , SquarePen } from "lucide-react" ;
33import { useEffect , useState } from "react" ;
44import { useForm } from "react-hook-form" ;
55import { toast } from "sonner" ;
@@ -47,108 +47,157 @@ const certificateDataHolder =
4747const privateKeyDataHolder =
4848 "-----BEGIN PRIVATE KEY-----\nMIIFRDCCAyygAwIBAgIUEPOR47ys6VDwMVB9tYoeEka83uQwDQYJKoZIhvcNAQELBQAwGTEXMBUGA1UEAwwObWktZG9taW5pby5jb20wHhcNMjQwMzExMDQyNzU3WhcN\n-----END PRIVATE KEY-----" ;
4949
50- const addCertificate = z . object ( {
50+ const handleCertificateSchema = z . object ( {
5151 name : z . string ( ) . min ( 1 , "Name is required" ) ,
5252 certificateData : z . string ( ) . min ( 1 , "Certificate data is required" ) ,
5353 privateKey : z . string ( ) . min ( 1 , "Private key is required" ) ,
54- autoRenew : z . boolean ( ) . optional ( ) ,
5554 serverId : z . string ( ) . optional ( ) ,
5655} ) ;
5756
58- type AddCertificate = z . infer < typeof addCertificate > ;
57+ type HandleCertificateForm = z . infer < typeof handleCertificateSchema > ;
5958
60- export const AddCertificate = ( ) => {
59+ interface Props {
60+ certificateId ?: string ;
61+ }
62+
63+ export const HandleCertificate = ( { certificateId } : Props ) => {
6164 const [ open , setOpen ] = useState ( false ) ;
6265 const utils = api . useUtils ( ) ;
6366
6467 const { data : isCloud } = api . settings . isCloud . useQuery ( ) ;
65- const { mutateAsync, isError, error, isPending } =
66- api . certificates . create . useMutation ( ) ;
6768 const { data : servers } = api . server . withSSHKey . useQuery ( ) ;
6869 const hasServers = servers && servers . length > 0 ;
69- // Show dropdown logic based on cloud environment
70- // Cloud: show only if there are remote servers (no Dokploy option)
71- // Self-hosted: show only if there are remote servers (Dokploy is default, hide if no remote servers)
72- const shouldShowServerDropdown = hasServers ;
70+ const shouldShowServerDropdown = hasServers && ! certificateId ; // Hide on edit
71+
72+ const { data : existingCert , refetch } = api . certificates . one . useQuery (
73+ { certificateId : certificateId || "" } ,
74+ { enabled : ! ! certificateId } ,
75+ ) ;
7376
74- const form = useForm < AddCertificate > ( {
77+ const createMutation = api . certificates . create . useMutation ( ) ;
78+ const updateMutation = api . certificates . update . useMutation ( ) ;
79+ const mutation = certificateId ? updateMutation : createMutation ;
80+ const { mutateAsync, isError, error, isPending } = mutation ;
81+
82+ const form = useForm < HandleCertificateForm > ( {
7583 defaultValues : {
7684 name : "" ,
7785 certificateData : "" ,
7886 privateKey : "" ,
79- autoRenew : false ,
8087 } ,
81- resolver : zodResolver ( addCertificate ) ,
88+ resolver : zodResolver ( handleCertificateSchema ) ,
8289 } ) ;
90+
8391 useEffect ( ( ) => {
84- form . reset ( ) ;
85- } , [ form , form . formState . isSubmitSuccessful , form . reset ] ) ;
92+ if ( existingCert ) {
93+ form . reset ( {
94+ name : existingCert . name ,
95+ certificateData : existingCert . certificateData ,
96+ privateKey : existingCert . privateKey ,
97+ } ) ;
98+ } else {
99+ form . reset ( {
100+ name : "" ,
101+ certificateData : "" ,
102+ privateKey : "" ,
103+ } ) ;
104+ }
105+ } , [ existingCert , form , open ] ) ;
86106
87- const onSubmit = async ( data : AddCertificate ) => {
88- await mutateAsync ( {
107+ const onSubmit = async ( data : HandleCertificateForm ) => {
108+ const basePayload = {
89109 name : data . name ,
90110 certificateData : data . certificateData ,
91111 privateKey : data . privateKey ,
92- autoRenew : data . autoRenew ,
93- serverId : data . serverId === "dokploy" ? undefined : data . serverId ,
94- organizationId : "" ,
95- } )
112+ } ;
113+
114+ const promise = certificateId
115+ ? updateMutation . mutateAsync ( {
116+ certificateId,
117+ ...basePayload ,
118+ } )
119+ : createMutation . mutateAsync ( {
120+ ...basePayload ,
121+ serverId : data . serverId === "dokploy" ? undefined : data . serverId ,
122+ organizationId : "" ,
123+ } ) ;
124+
125+ await promise
96126 . then ( async ( ) => {
97- toast . success ( "Certificate Created" ) ;
127+ toast . success (
128+ certificateId ? "Certificate Updated" : "Certificate Created" ,
129+ ) ;
98130 await utils . certificates . all . invalidate ( ) ;
131+ if ( certificateId ) {
132+ refetch ( ) ;
133+ }
99134 setOpen ( false ) ;
100135 } )
101136 . catch ( ( ) => {
102- toast . error ( "Error creating the Certificate" ) ;
137+ toast . error (
138+ certificateId
139+ ? "Error updating the Certificate"
140+ : "Error creating the Certificate" ,
141+ ) ;
103142 } ) ;
104143 } ;
144+
105145 return (
106146 < Dialog open = { open } onOpenChange = { setOpen } >
107- < DialogTrigger className = "" asChild >
108- < Button >
109- { " " }
110- < PlusIcon className = "h-4 w-4" />
111- Add Certificate
112- </ Button >
147+ < DialogTrigger asChild >
148+ { certificateId ? (
149+ < Button
150+ variant = "ghost"
151+ size = "icon"
152+ className = "group hover:bg-blue-500/10"
153+ >
154+ < SquarePen className = "size-3.5 text-primary group-hover:text-blue-500" />
155+ </ Button >
156+ ) : (
157+ < Button >
158+ < PlusIcon className = "h-4 w-4" />
159+ Add Certificate
160+ </ Button >
161+ ) }
113162 </ DialogTrigger >
114163 < DialogContent className = "sm:max-w-2xl" >
115164 < DialogHeader >
116- < DialogTitle > Add New Certificate</ DialogTitle >
165+ < DialogTitle >
166+ { certificateId ? "Update" : "Add New" } Certificate
167+ </ DialogTitle >
117168 < DialogDescription >
118- Upload or generate a certificate to secure your application
169+ { certificateId
170+ ? "Modify the certificate details"
171+ : "Upload or generate a certificate to secure your application" }
119172 </ DialogDescription >
120173 </ DialogHeader >
121174 { isError && < AlertBlock type = "error" > { error ?. message } </ AlertBlock > }
122175
123176 < Form { ...form } >
124177 < form
125- id = "hook-form-add -certificate"
178+ id = "hook-form-handle -certificate"
126179 onSubmit = { form . handleSubmit ( onSubmit ) }
127- className = "grid w-full gap-4 "
180+ className = "grid w-full gap-4"
128181 >
129182 < FormField
130183 control = { form . control }
131184 name = "name"
132- render = { ( { field } ) => {
133- return (
134- < FormItem >
135- < FormLabel > Certificate Name</ FormLabel >
136- < FormControl >
137- < Input placeholder = { "My Certificate" } { ...field } />
138- </ FormControl >
139- < FormMessage />
140- </ FormItem >
141- ) ;
142- } }
185+ render = { ( { field } ) => (
186+ < FormItem >
187+ < FormLabel > Certificate Name</ FormLabel >
188+ < FormControl >
189+ < Input placeholder = "My Certificate" { ...field } />
190+ </ FormControl >
191+ < FormMessage />
192+ </ FormItem >
193+ ) }
143194 />
144195 < FormField
145196 control = { form . control }
146197 name = "certificateData"
147198 render = { ( { field } ) => (
148199 < FormItem >
149- < div className = "space-y-0.5" >
150- < FormLabel > Certificate Data</ FormLabel >
151- </ div >
200+ < FormLabel > Certificate Data</ FormLabel >
152201 < FormControl >
153202 < Textarea
154203 className = "h-32"
@@ -165,9 +214,7 @@ export const AddCertificate = () => {
165214 name = "privateKey"
166215 render = { ( { field } ) => (
167216 < FormItem >
168- < div className = "space-y-0.5" >
169- < FormLabel > Private Key</ FormLabel >
170- </ div >
217+ < FormLabel > Private Key</ FormLabel >
171218 < FormControl >
172219 < Textarea
173220 className = "h-32"
@@ -248,10 +295,10 @@ export const AddCertificate = () => {
248295 < DialogFooter className = "flex w-full flex-row !justify-end" >
249296 < Button
250297 isLoading = { isPending }
251- form = "hook-form-add -certificate"
298+ form = "hook-form-handle -certificate"
252299 type = "submit"
253300 >
254- Create
301+ { certificateId ? "Update" : " Create" }
255302 </ Button >
256303 </ DialogFooter >
257304 </ Form >
0 commit comments