7
7
*/
8
8
import { createColumnHelper } from '@tanstack/react-table'
9
9
import { useCallback , useMemo , useState } from 'react'
10
- import { useForm , type FieldValues } from 'react-hook-form'
10
+ import { useForm } from 'react-hook-form'
11
11
import { Outlet } from 'react-router'
12
12
13
13
import {
@@ -24,6 +24,7 @@ import { DocsPopover } from '~/components/DocsPopover'
24
24
import { ComboboxField } from '~/components/form/fields/ComboboxField'
25
25
import { toImageComboboxItem } from '~/components/form/fields/ImageSelectField'
26
26
import { ListboxField } from '~/components/form/fields/ListboxField'
27
+ import { ModalForm } from '~/components/form/ModalForm'
27
28
import { HL } from '~/components/HL'
28
29
import { confirmDelete } from '~/stores/confirm-delete'
29
30
import { addToast } from '~/stores/toast'
@@ -35,7 +36,6 @@ import { Button } from '~/ui/lib/Button'
35
36
import { toComboboxItems } from '~/ui/lib/Combobox'
36
37
import { EmptyMessage } from '~/ui/lib/EmptyMessage'
37
38
import { Message } from '~/ui/lib/Message'
38
- import { Modal } from '~/ui/lib/Modal'
39
39
import { PageHeader , PageTitle } from '~/ui/lib/PageHeader'
40
40
import { TableActions } from '~/ui/lib/Table'
41
41
import { docLinks } from '~/util/links'
@@ -129,7 +129,7 @@ type Values = { project: string | null; image: string | null }
129
129
const defaultValues : Values = { project : null , image : null }
130
130
131
131
const PromoteImageModal = ( { onDismiss } : { onDismiss : ( ) => void } ) => {
132
- const { control , handleSubmit , watch , resetField } = useForm ( { defaultValues } )
132
+ const form = useForm ( { defaultValues } )
133
133
134
134
const queryClient = useApiQueryClient ( )
135
135
@@ -146,7 +146,7 @@ const PromoteImageModal = ({ onDismiss }: { onDismiss: () => void }) => {
146
146
147
147
const projects = useApiQuery ( 'projectList' , { } )
148
148
const projectItems = useMemo ( ( ) => toComboboxItems ( projects . data ?. items ) , [ projects . data ] )
149
- const selectedProject = watch ( 'project' )
149
+ const selectedProject = form . watch ( 'project' )
150
150
151
151
// can only fetch images if a project is selected
152
152
const images = useApiQuery (
@@ -159,74 +159,70 @@ const PromoteImageModal = ({ onDismiss }: { onDismiss: () => void }) => {
159
159
[ images . data ]
160
160
)
161
161
162
- const onSubmit = ( { image, project } : Values ) => {
163
- if ( ! image || ! project ) return
164
- promoteImage . mutate ( { path : { image } } )
165
- }
166
-
167
162
return (
168
- < Modal isOpen onDismiss = { onDismiss } title = "Promote image" >
169
- < Modal . Body >
170
- < Modal . Section >
171
- < form autoComplete = "off" onSubmit = { handleSubmit ( onSubmit ) } className = "space-y-4" >
172
- < ComboboxField
173
- placeholder = "Select a project"
174
- name = "project"
175
- label = "Project"
176
- items = { projectItems }
177
- onChange = { ( ) => {
178
- resetField ( 'image' ) // reset image field when the project changes
179
- } }
180
- isLoading = { projects . isPending }
181
- required
182
- control = { control }
183
- />
184
- < ListboxField
185
- control = { control }
186
- name = "image"
187
- placeholder = "Select an image"
188
- items = { imageItems }
189
- isLoading = { images . isPending }
190
- required
191
- disabled = { ! selectedProject }
192
- />
193
- </ form >
194
- < Message
195
- variant = "info"
196
- content = "Once an image has been promoted it is visible to all projects in a silo"
197
- />
198
- </ Modal . Section >
199
- </ Modal . Body >
200
- < Modal . Footer
201
- onDismiss = { onDismiss }
202
- onAction = { handleSubmit ( onSubmit ) }
203
- actionText = "Promote"
163
+ < ModalForm
164
+ title = "Promote image"
165
+ form = { form }
166
+ loading = { promoteImage . isPending }
167
+ submitError = { promoteImage . error }
168
+ onSubmit = { ( { image, project } ) => {
169
+ if ( ! image || ! project ) return // shouldn't happen because of validation
170
+ promoteImage . mutate ( { path : { image } } )
171
+ } }
172
+ onDismiss = { onDismiss }
173
+ submitLabel = "Promote"
174
+ >
175
+ < ComboboxField
176
+ placeholder = "Select a project"
177
+ name = "project"
178
+ label = "Project"
179
+ items = { projectItems }
180
+ onChange = { ( ) => {
181
+ form . resetField ( 'image' ) // reset image field when the project changes
182
+ } }
183
+ isLoading = { projects . isPending }
184
+ required
185
+ control = { form . control }
186
+ />
187
+ < ListboxField
188
+ control = { form . control }
189
+ name = "image"
190
+ placeholder = "Select an image"
191
+ items = { imageItems }
192
+ isLoading = { images . isPending }
193
+ required
194
+ disabled = { ! selectedProject }
204
195
/>
205
- </ Modal >
196
+ < Message
197
+ variant = "info"
198
+ content = "Once an image has been promoted it is visible to all projects in a silo"
199
+ />
200
+ </ ModalForm >
206
201
)
207
202
}
208
203
204
+ type DemoteFormValues = {
205
+ project : string | undefined
206
+ }
207
+
209
208
const DemoteImageModal = ( {
210
209
onDismiss,
211
210
image,
212
211
} : {
213
212
onDismiss : ( ) => void
214
213
image : Image
215
214
} ) => {
216
- const { control, handleSubmit, watch } = useForm ( )
215
+ const defaultValues : DemoteFormValues = { project : undefined }
216
+ const form = useForm ( { defaultValues } )
217
217
218
- const selectedProject : string | undefined = watch ( 'project' )
218
+ const selectedProject : string | undefined = form . watch ( 'project' )
219
219
220
220
const queryClient = useApiQueryClient ( )
221
221
222
222
const demoteImage = useApiMutation ( 'imageDemote' , {
223
223
onSuccess ( data ) {
224
224
addToast ( {
225
- content : (
226
- < >
227
- Image < HL > { data . name } </ HL > demoted
228
- </ >
229
- ) ,
225
+ content : < > Image < HL > { data . name } </ HL > demoted</ > , // prettier-ignore
230
226
cta : selectedProject
231
227
? {
232
228
text : `View images in ${ selectedProject } ` ,
@@ -243,51 +239,40 @@ const DemoteImageModal = ({
243
239
onSettled : onDismiss ,
244
240
} )
245
241
246
- const onSubmit = ( data : FieldValues ) => {
247
- demoteImage . mutate ( { path : { image : image . id } , query : { project : data . project } } )
248
- }
249
-
250
242
const projects = useApiQuery ( 'projectList' , { } )
251
243
const projectItems = useMemo ( ( ) => toComboboxItems ( projects . data ?. items ) , [ projects . data ] )
252
244
253
245
return (
254
- < Modal isOpen onDismiss = { onDismiss } title = "Demote image" >
255
- < Modal . Body >
256
- < Modal . Section >
257
- < form
258
- autoComplete = "off"
259
- onSubmit = { ( e ) => {
260
- e . stopPropagation ( )
261
- handleSubmit ( onSubmit ) ( e )
262
- } }
263
- className = "space-y-4"
264
- >
265
- < p >
266
- Demoting: < span className = "text-sans-semi-md text-raise" > { image . name } </ span >
267
- </ p >
246
+ < ModalForm
247
+ title = "Demote image"
248
+ form = { form }
249
+ loading = { demoteImage . isPending }
250
+ submitError = { demoteImage . error }
251
+ onSubmit = { ( { project } ) => {
252
+ if ( ! project ) return // shouldn't happen because of validation
253
+ demoteImage . mutate ( { path : { image : image . id } , query : { project } } )
254
+ } }
255
+ onDismiss = { onDismiss }
256
+ submitLabel = "Demote"
257
+ >
258
+ < p >
259
+ Demoting: < span className = "text-sans-semi-md text-raise" > { image . name } </ span >
260
+ </ p >
268
261
269
- < Message
270
- variant = "info"
271
- content = "Once an image has been demoted it is only visible to the project that it is demoted into. This will not affect disks already created with the image."
272
- />
262
+ < Message
263
+ variant = "info"
264
+ content = "Once an image has been demoted it is only visible within the project that it is demoted into. This will not affect disks already created with the image."
265
+ />
273
266
274
- < ComboboxField
275
- placeholder = "Select project for image"
276
- name = "project"
277
- label = "Project"
278
- items = { projectItems }
279
- isLoading = { projects . isPending }
280
- required
281
- control = { control }
282
- />
283
- </ form >
284
- </ Modal . Section >
285
- </ Modal . Body >
286
- < Modal . Footer
287
- onDismiss = { onDismiss }
288
- onAction = { handleSubmit ( onSubmit ) }
289
- actionText = "Demote"
267
+ < ComboboxField
268
+ placeholder = "Select project for image"
269
+ name = "project"
270
+ label = "Project"
271
+ items = { projectItems }
272
+ isLoading = { projects . isPending }
273
+ required
274
+ control = { form . control }
290
275
/>
291
- </ Modal >
276
+ </ ModalForm >
292
277
)
293
278
}
0 commit comments