@@ -5,6 +5,45 @@ import { ErrorMessage } from './ErrorMessage.js';
5
5
import { PackageItem } from './PackageItem.js' ;
6
6
import { Package , ReleaseType } from './types.js' ;
7
7
8
+ type SubmitButtonProps = {
9
+ isSubmitting : boolean ;
10
+ selections : Record < string , string > ;
11
+ packageDependencyErrors : Record <
12
+ string ,
13
+ { missingDependentNames : string [ ] ; missingDependencies : string [ ] }
14
+ > ;
15
+ onSubmit : ( ) => Promise < void > ;
16
+ } ;
17
+
18
+ function SubmitButton ( {
19
+ isSubmitting,
20
+ selections,
21
+ packageDependencyErrors,
22
+ onSubmit,
23
+ } : SubmitButtonProps ) {
24
+ const isDisabled =
25
+ isSubmitting ||
26
+ Object . keys ( selections ) . length === 0 ||
27
+ Object . keys ( packageDependencyErrors ) . length > 0 ||
28
+ Object . values ( selections ) . every ( ( value ) => value === 'intentionally-skip' ) ;
29
+
30
+ return (
31
+ < button
32
+ onClick = { ( ) => void onSubmit ( ) }
33
+ disabled = { isDisabled }
34
+ className = { `mt-6 px-4 py-2 rounded ${
35
+ isDisabled
36
+ ? 'bg-blue-300 cursor-not-allowed'
37
+ : 'bg-blue-500 hover:bg-blue-600'
38
+ } text-white`}
39
+ >
40
+ { isSubmitting
41
+ ? 'Processing Release (This may take a few minutes)...'
42
+ : 'Submit Release Selections' }
43
+ </ button >
44
+ ) ;
45
+ }
46
+
8
47
function App ( ) {
9
48
const [ packages , setPackages ] = useState < Package [ ] > ( [ ] ) ;
10
49
const [ selections , setSelections ] = useState < Record < string , string > > ( { } ) ;
@@ -28,6 +67,9 @@ function App() {
28
67
>
29
68
> ( { } ) ;
30
69
const [ isSuccess , setIsSuccess ] = useState ( false ) ;
70
+ const [ selectedPackages , setSelectedPackages ] = useState < Set < string > > (
71
+ new Set ( ) ,
72
+ ) ;
31
73
32
74
useEffect ( ( ) => {
33
75
fetch ( '/api/packages' )
@@ -212,6 +254,42 @@ function App() {
212
254
}
213
255
} ;
214
256
257
+ const handleBulkAction = ( action : ReleaseType ) => {
258
+ const newSelections = { ...selections } ;
259
+ selectedPackages . forEach ( ( packageName ) => {
260
+ newSelections [ packageName ] = action ;
261
+ } ) ;
262
+ setSelections ( newSelections ) ;
263
+ setSelectedPackages ( new Set ( ) ) ;
264
+ } ;
265
+
266
+ const togglePackageSelection = ( packageName : string ) => {
267
+ setSelectedPackages ( ( prev ) => {
268
+ const newSet = new Set ( prev ) ;
269
+ if ( newSet . has ( packageName ) ) {
270
+ newSet . delete ( packageName ) ;
271
+ } else {
272
+ newSet . add ( packageName ) ;
273
+ }
274
+ return newSet ;
275
+ } ) ;
276
+ } ;
277
+
278
+ if ( isSubmitting ) {
279
+ return (
280
+ < div className = "fixed inset-0 bg-white bg-opacity-90 flex items-center justify-center z-50" >
281
+ < div className = "text-center p-8" >
282
+ < h2 className = "text-2xl font-bold mb-4" > Processing Release</ h2 >
283
+ < p className = "mb-6" >
284
+ Please wait while we process your release. This may take a few
285
+ minutes...
286
+ </ p >
287
+ < div className = "animate-spin rounded-full h-12 w-12 border-b-2 border-blue-500 mx-auto" > </ div >
288
+ </ div >
289
+ </ div >
290
+ ) ;
291
+ }
292
+
215
293
if ( isSuccess ) {
216
294
return (
217
295
< div className = "fixed inset-0 bg-white flex items-center justify-center" >
@@ -235,6 +313,23 @@ function App() {
235
313
Create Release Branch Interactive UI
236
314
</ h1 >
237
315
316
+ { selectedPackages . size > 0 && (
317
+ < div className = "mb-4 p-4 bg-gray-100 rounded" >
318
+ < span className = "mr-2" >
319
+ Bulk action for { selectedPackages . size } packages:
320
+ </ span >
321
+ { [ 'major' , 'minor' , 'patch' , 'intentionally-skip' ] . map ( ( action ) => (
322
+ < button
323
+ key = { action }
324
+ onClick = { ( ) => handleBulkAction ( action as ReleaseType ) }
325
+ className = "mr-2 px-3 py-1 bg-blue-500 text-white rounded hover:bg-blue-600"
326
+ >
327
+ { action }
328
+ </ button >
329
+ ) ) }
330
+ </ div >
331
+ ) }
332
+
238
333
< div className = "space-y-4" >
239
334
{ packages . map ( ( pkg ) => (
240
335
< PackageItem
@@ -250,28 +345,19 @@ function App() {
250
345
onFetchChangelog = { fetchChangelog }
251
346
setSelections = { setSelections }
252
347
setChangelogs = { setChangelogs }
348
+ isSelected = { selectedPackages . has ( pkg . name ) }
349
+ onToggleSelect = { ( ) => togglePackageSelection ( pkg . name ) }
253
350
/>
254
351
) ) }
255
352
</ div >
256
353
257
354
{ packages . length > 0 && (
258
- < button
259
- onClick = { ( ) => void handleSubmit ( ) }
260
- disabled = {
261
- isSubmitting ||
262
- Object . keys ( selections ) . length === 0 ||
263
- Object . keys ( packageDependencyErrors ) . length > 0
264
- }
265
- className = { `mt-6 px-4 py-2 rounded ${
266
- isSubmitting ||
267
- Object . keys ( selections ) . length === 0 ||
268
- Object . keys ( packageDependencyErrors ) . length > 0
269
- ? 'bg-blue-300 cursor-not-allowed'
270
- : 'bg-blue-500 hover:bg-blue-600'
271
- } text-white`}
272
- >
273
- { isSubmitting ? 'Submitting...' : 'Submit Release Selections' }
274
- </ button >
355
+ < SubmitButton
356
+ isSubmitting = { isSubmitting }
357
+ selections = { selections }
358
+ packageDependencyErrors = { packageDependencyErrors }
359
+ onSubmit = { handleSubmit }
360
+ />
275
361
) }
276
362
277
363
{ error && < div className = "text-red-600 p-4" > Error: { error } </ div > }
0 commit comments