@@ -45,8 +45,7 @@ const DescriptionInputModel = z
45
45
const CreateAccessTokenFormModel = z . object ( {
46
46
title : TitleInputModel ,
47
47
description : DescriptionInputModel ,
48
- selectedPermissions : z . array ( z . string ( ) ) ,
49
- assignedResources : z . any ( ) ,
48
+ permissions : z . array ( z . string ( ) ) . min ( 1 , 'Please select at least one permission.' ) ,
50
49
} ) ;
51
50
52
51
const CreateAccessTokenSheetContent_OrganizationFragment = graphql ( `
@@ -119,16 +118,14 @@ export function CreateAccessTokenSheetContent(
119
118
mode : GraphQLSchema . ResourceAssignmentMode . All ,
120
119
projects : [ ] ,
121
120
} ) ) ;
122
- const [ selectedPermissionIds , setSelectedPermissionIds ] = useState < ReadonlySet < string > > (
123
- ( ) => new Set < string > ( ) ,
124
- ) ;
125
121
126
- const form = useForm ( {
122
+ const form = useForm < z . TypeOf < typeof CreateAccessTokenFormModel > > ( {
127
123
mode : 'onChange' ,
128
124
resolver : zodResolver ( CreateAccessTokenFormModel ) ,
129
125
defaultValues : {
130
126
title : '' ,
131
127
description : '' ,
128
+ permissions : [ ] ,
132
129
} ,
133
130
} ) ;
134
131
@@ -151,7 +148,7 @@ export function CreateAccessTokenSheetContent(
151
148
} ,
152
149
title : formValues . title ?? '' ,
153
150
description : formValues . description ?? '' ,
154
- permissions : Array . from ( selectedPermissionIds ) ,
151
+ permissions : formValues . permissions ,
155
152
resources : resourceSlectionToGraphQLSchemaResourceAssignmentInput ( resourceSelection ) ,
156
153
} ,
157
154
} ) ;
@@ -199,6 +196,7 @@ export function CreateAccessTokenSheetContent(
199
196
{ stepper . switch ( {
200
197
'step-1-general' : ( ) => (
201
198
< >
199
+ < Heading > General</ Heading >
202
200
< div className = "grid w-full max-w-sm items-center gap-1.5" >
203
201
< Form . FormField
204
202
control = { form . control }
@@ -239,25 +237,39 @@ export function CreateAccessTokenSheetContent(
239
237
</ >
240
238
) ,
241
239
'step-2-permissions' : ( ) => (
242
- < >
243
- < div className = "grid w-full items-center gap-1.5" >
244
- < Form . FormItem >
245
- < Form . FormLabel > Permissions</ Form . FormLabel >
246
- < Form . FormControl >
247
- < PermissionSelector
248
- permissionGroups = {
249
- organization . availableOrganizationPermissionGroups
250
- }
251
- selectedPermissionIds = { selectedPermissionIds }
252
- onSelectedPermissionsChange = { selectedPermissionIds => {
253
- setSelectedPermissionIds ( new Set ( selectedPermissionIds ) ) ;
254
- } }
255
- />
256
- </ Form . FormControl >
257
- < Form . FormMessage />
258
- </ Form . FormItem >
259
- </ div >
260
- </ >
240
+ < Form . FormField
241
+ control = { form . control }
242
+ name = "permissions"
243
+ render = { ( ) => (
244
+ < div className = "grid w-full items-center gap-1.5" >
245
+ < Form . FormItem >
246
+ < Form . FormLabel >
247
+ < Heading > Permissions</ Heading >
248
+ </ Form . FormLabel >
249
+ < Form . FormControl >
250
+ < PermissionSelector
251
+ permissionGroups = {
252
+ organization . availableOrganizationPermissionGroups
253
+ }
254
+ selectedPermissionIds = { new Set ( form . getValues ( ) [ 'permissions' ] ) }
255
+ onSelectedPermissionsChange = { selectedPermissionIds => {
256
+ form . setValue (
257
+ 'permissions' ,
258
+ Array . from ( selectedPermissionIds ) ,
259
+ {
260
+ shouldValidate : true ,
261
+ shouldTouch : true ,
262
+ shouldDirty : true ,
263
+ } ,
264
+ ) ;
265
+ } }
266
+ />
267
+ </ Form . FormControl >
268
+ < Form . FormMessage />
269
+ </ Form . FormItem >
270
+ </ div >
271
+ ) }
272
+ />
261
273
) ,
262
274
'step-3-resources' : ( ) => (
263
275
< >
@@ -278,16 +290,16 @@ export function CreateAccessTokenSheetContent(
278
290
) ,
279
291
'step-4-confirmation' : ( ) => (
280
292
< >
281
- < Heading > Confirm and create access token </ Heading >
293
+ < Heading > Confirm and create Access Token </ Heading >
282
294
< p className = "text-muted-foreground text-sm" >
283
295
Please please review the selected permissions and resources to ensure they
284
296
align with your intended access needs.
285
297
</ p >
286
- { selectedPermissionIds . size === 0 ? (
298
+ { form . getValues ( ) . permissions . length === 0 ? (
287
299
< p className = "mt-3" > No permissions are selected.</ p >
288
300
) : (
289
301
< SelectedPermissionOverview
290
- activePermissionIds = { Array . from ( selectedPermissionIds ) }
302
+ activePermissionIds = { form . getValues ( ) . permissions }
291
303
permissionsGroups = { organization . availableOrganizationPermissionGroups }
292
304
showOnlyAllowedPermissions
293
305
isExpanded
@@ -344,7 +356,44 @@ export function CreateAccessTokenSheetContent(
344
356
: 'Create Access Token' }
345
357
</ Button >
346
358
) : (
347
- < Button onClick = { stepper . next } > Next</ Button >
359
+ < Button
360
+ onClick = { ev => {
361
+ if ( stepper . current . id === 'step-1-general' ) {
362
+ Promise . all ( [ form . trigger ( 'title' ) , form . trigger ( 'description' ) ] ) . then (
363
+ ( [ title , description ] ) => {
364
+ if ( ! title ) {
365
+ shakeElement ( ev ) ;
366
+ form . setFocus ( 'title' ) ;
367
+ return ;
368
+ }
369
+ if ( ! description ) {
370
+ shakeElement ( ev ) ;
371
+ form . setFocus ( 'description' ) ;
372
+ return ;
373
+ }
374
+ stepper . next ( ) ;
375
+ } ,
376
+ ) ;
377
+ }
378
+
379
+ if ( stepper . current . id === 'step-2-permissions' ) {
380
+ form . trigger ( 'permissions' ) . then ( permissions => {
381
+ if ( ! permissions ) {
382
+ shakeElement ( ev ) ;
383
+ return ;
384
+ }
385
+
386
+ stepper . next ( ) ;
387
+ } ) ;
388
+ }
389
+
390
+ if ( stepper . current . id === 'step-3-resources' ) {
391
+ stepper . next ( ) ;
392
+ }
393
+ } }
394
+ >
395
+ Next
396
+ </ Button >
348
397
) }
349
398
</ Stepper . StepperControls >
350
399
</ Sheet . SheetFooter >
@@ -410,3 +459,15 @@ function AcessTokenCreatedConfirmationDialogue(props: {
410
459
</ AlertDialog . AlertDialog >
411
460
) ;
412
461
}
462
+
463
+ function shakeElement ( ev : React . MouseEvent < HTMLElement > ) {
464
+ const el = ev . target as HTMLElement ;
465
+ el . classList . add ( 'animate-shake' ) ;
466
+ el . addEventListener (
467
+ 'animationend' ,
468
+ ( ) => {
469
+ el . classList . remove ( 'animate-shake' ) ;
470
+ } ,
471
+ { once : true } ,
472
+ ) ;
473
+ }
0 commit comments