@@ -4,7 +4,7 @@ import { useRouter } from "next/router";
4
4
import { ApiGetCall } from "/src/api/ApiCall" ;
5
5
import CippFormSkeleton from "/src/components/CippFormPages/CippFormSkeleton" ;
6
6
import CalendarIcon from "@heroicons/react/24/outline/CalendarIcon" ;
7
- import { Check , Error , Mail , Fingerprint , Launch } from "@mui/icons-material" ;
7
+ import { Check , Error , Mail , Fingerprint , Launch , Delete , Star , Close } from "@mui/icons-material" ;
8
8
import { HeaderedTabbedLayout } from "../../../../../layouts/HeaderedTabbedLayout" ;
9
9
import tabOptions from "./tabOptions" ;
10
10
import { CippTimeAgo } from "../../../../../components/CippComponents/CippTimeAgo" ;
@@ -16,18 +16,26 @@ import { CippExchangeInfoCard } from "../../../../../components/CippCards/CippEx
16
16
import { useEffect , useState } from "react" ;
17
17
import CippExchangeSettingsForm from "../../../../../components/CippFormPages/CippExchangeSettingsForm" ;
18
18
import { useForm } from "react-hook-form" ;
19
- import { Alert , Button , Collapse , CircularProgress , Typography } from "@mui/material" ;
19
+ import { Alert , Button , Collapse , CircularProgress , Typography , TextField , Dialog , DialogTitle , DialogContent , DialogActions , IconButton } from "@mui/material" ;
20
20
import { CippApiResults } from "../../../../../components/CippComponents/CippApiResults" ;
21
- import { Block , PlayArrow , DeleteForever } from "@mui/icons-material" ;
21
+ import { Block , PlayArrow } from "@mui/icons-material" ;
22
22
import { CippPropertyListCard } from "../../../../../components/CippCards/CippPropertyListCard" ;
23
23
import { getCippTranslation } from "../../../../../utils/get-cipp-translation" ;
24
24
import { getCippFormatting } from "../../../../../utils/get-cipp-formatting" ;
25
25
import CippExchangeActions from "../../../../../components/CippComponents/CippExchangeActions" ;
26
+ import { CippApiDialog } from "../../../../../components/CippComponents/CippApiDialog" ;
27
+ import { useDialog } from "../../../../../hooks/use-dialog" ;
26
28
27
29
const Page = ( ) => {
28
30
const userSettingsDefaults = useSettings ( ) ;
29
31
const [ waiting , setWaiting ] = useState ( false ) ;
30
32
const [ showDetails , setShowDetails ] = useState ( false ) ;
33
+ const [ actionData , setActionData ] = useState ( { ready : false } ) ;
34
+ const [ showAddAliasDialog , setShowAddAliasDialog ] = useState ( false ) ;
35
+ const [ newAliases , setNewAliases ] = useState ( '' ) ;
36
+ const [ isSubmitting , setIsSubmitting ] = useState ( false ) ;
37
+ const [ submitResult , setSubmitResult ] = useState ( null ) ;
38
+ const createDialog = useDialog ( ) ;
31
39
const router = useRouter ( ) ;
32
40
const { userId } = router . query ;
33
41
@@ -221,7 +229,7 @@ const Page = () => {
221
229
{
222
230
label : "Remove Mailbox Rule" ,
223
231
type : "POST" ,
224
- icon : < DeleteForever /> ,
232
+ icon : < Delete /> ,
225
233
url : "/api/ExecRemoveMailboxRule" ,
226
234
data : {
227
235
ruleId : "Identity" ,
@@ -287,6 +295,142 @@ const Page = () => {
287
295
} ,
288
296
] ;
289
297
298
+ const proxyAddressActions = [
299
+ {
300
+ label : "Make Primary" ,
301
+ type : "POST" ,
302
+ icon : < Star /> ,
303
+ url : "/api/SetUserAliases" ,
304
+ data : {
305
+ id : userId ,
306
+ tenantFilter : userSettingsDefaults . currentTenant ,
307
+ MakePrimary : "Address" ,
308
+ } ,
309
+ confirmText : "Are you sure you want to make this the primary proxy address?" ,
310
+ multiPost : false ,
311
+ relatedQueryKeys : `ListUsers-${ userId } ` ,
312
+ } ,
313
+ {
314
+ label : "Remove Proxy Address" ,
315
+ type : "POST" ,
316
+ icon : < Delete /> ,
317
+ url : "/api/SetUserAliases" ,
318
+ data : {
319
+ id : userId ,
320
+ tenantFilter : userSettingsDefaults . currentTenant ,
321
+ RemovedAliases : "Address" ,
322
+ } ,
323
+ confirmText : "Are you sure you want to remove this proxy address?" ,
324
+ multiPost : false ,
325
+ relatedQueryKeys : `ListUsers-${ userId } ` ,
326
+ } ,
327
+ ] ;
328
+
329
+ const handleAddAliases = ( ) => {
330
+ const aliases = newAliases
331
+ . split ( '\n' )
332
+ . map ( alias => alias . trim ( ) )
333
+ . filter ( alias => alias ) ;
334
+ if ( aliases . length > 0 ) {
335
+ setIsSubmitting ( true ) ;
336
+ setSubmitResult ( null ) ;
337
+ fetch ( '/api/SetUserAliases' , {
338
+ method : 'POST' ,
339
+ headers : {
340
+ 'Content-Type' : 'application/json' ,
341
+ } ,
342
+ body : JSON . stringify ( {
343
+ id : userId ,
344
+ tenantFilter : userSettingsDefaults . currentTenant ,
345
+ AddedAliases : aliases . join ( ',' ) ,
346
+ userPrincipalName : graphUserRequest . data ?. [ 0 ] ?. userPrincipalName ,
347
+ } ) ,
348
+ } )
349
+ . then ( response => response . json ( ) )
350
+ . then ( data => {
351
+ setSubmitResult ( { success : true , message : 'Aliases added successfully' } ) ;
352
+ graphUserRequest . refetch ( ) ;
353
+ setTimeout ( ( ) => {
354
+ setShowAddAliasDialog ( false ) ;
355
+ setNewAliases ( '' ) ;
356
+ setSubmitResult ( null ) ;
357
+ } , 1500 ) ;
358
+ } )
359
+ . catch ( error => {
360
+ setSubmitResult ( { success : false , message : 'Failed to add aliases' } ) ;
361
+ } )
362
+ . finally ( ( ) => {
363
+ setIsSubmitting ( false ) ;
364
+ } ) ;
365
+ }
366
+ } ;
367
+
368
+ const proxyAddressesCard = [
369
+ {
370
+ id : 1 ,
371
+ cardLabelBox : {
372
+ cardLabelBoxHeader : graphUserRequest . isFetching ? (
373
+ < CircularProgress size = "25px" color = "inherit" />
374
+ ) : graphUserRequest . data ?. [ 0 ] ?. proxyAddresses ?. length > 1 ? (
375
+ < Check />
376
+ ) : (
377
+ < Error />
378
+ ) ,
379
+ } ,
380
+ text : "Current Proxy Addresses" ,
381
+ subtext : graphUserRequest . data ?. [ 0 ] ?. proxyAddresses ?. length > 1
382
+ ? "Proxy addresses are configured for this user"
383
+ : "No proxy addresses configured for this user" ,
384
+ statusColor : "green.main" ,
385
+ table : {
386
+ title : "Proxy Addresses" ,
387
+ hideTitle : true ,
388
+ data : graphUserRequest . data ?. [ 0 ] ?. proxyAddresses ?. map ( address => ( {
389
+ Address : address ,
390
+ Type : address . startsWith ( 'SMTP:' ) ? 'Primary' : 'Alias' ,
391
+ } ) ) || [ ] ,
392
+ refreshFunction : ( ) => graphUserRequest . refetch ( ) ,
393
+ isFetching : graphUserRequest . isFetching ,
394
+ simpleColumns : [ "Address" , "Type" ] ,
395
+ actions : proxyAddressActions ,
396
+ offCanvas : {
397
+ children : ( data ) => {
398
+ return (
399
+ < CippPropertyListCard
400
+ cardSx = { { p : 0 , m : - 2 } }
401
+ title = "Address Details"
402
+ propertyItems = { [
403
+ {
404
+ label : "Address" ,
405
+ value : data . Address ,
406
+ } ,
407
+ {
408
+ label : "Type" ,
409
+ value : data . Type ,
410
+ } ,
411
+ ] }
412
+ actionItems = { proxyAddressActions }
413
+ />
414
+ ) ;
415
+ } ,
416
+ } ,
417
+ } ,
418
+ children : (
419
+ < Box sx = { { display : 'flex' , justifyContent : 'flex-end' , alignItems : 'center' , mb : 2 , px : 2 } } >
420
+ < Button
421
+ startIcon = { < Mail /> }
422
+ onClick = { ( ) => setShowAddAliasDialog ( true ) }
423
+ variant = "contained"
424
+ color = "primary"
425
+ size = "small"
426
+ >
427
+ Add Alias
428
+ </ Button >
429
+ </ Box >
430
+ ) ,
431
+ } ,
432
+ ] ;
433
+
290
434
return (
291
435
< HeaderedTabbedLayout
292
436
tabOptions = { tabOptions }
@@ -345,6 +489,11 @@ const Page = () => {
345
489
</ Grid >
346
490
< Grid item size = { 8 } >
347
491
< Stack spacing = { 3 } >
492
+ < CippBannerListCard
493
+ isFetching = { graphUserRequest . isLoading }
494
+ items = { proxyAddressesCard }
495
+ isCollapsible = { graphUserRequest . data ?. [ 0 ] ?. proxyAddresses ?. length !== 0 }
496
+ />
348
497
< CippBannerListCard
349
498
isFetching = { userRequest . isLoading }
350
499
items = { permissions }
@@ -374,6 +523,69 @@ const Page = () => {
374
523
</ Grid >
375
524
</ Box >
376
525
) }
526
+ { actionData . ready && (
527
+ < CippApiDialog
528
+ createDialog = { createDialog }
529
+ title = "Confirmation"
530
+ api = { actionData . action }
531
+ row = { actionData . data }
532
+ />
533
+ ) }
534
+ < Dialog
535
+ open = { showAddAliasDialog }
536
+ onClose = { ( ) => setShowAddAliasDialog ( false ) }
537
+ maxWidth = "sm"
538
+ fullWidth
539
+ >
540
+ < DialogTitle >
541
+ < Box sx = { { display : 'flex' , justifyContent : 'space-between' , alignItems : 'center' } } >
542
+ Add Proxy Addresses
543
+ < IconButton onClick = { ( ) => setShowAddAliasDialog ( false ) } size = "small" >
544
+ < Close />
545
+ </ IconButton >
546
+ </ Box >
547
+ </ DialogTitle >
548
+ < DialogContent >
549
+ < Box sx = { { mt : 2 } } >
550
+ < TextField
551
+ autoFocus
552
+ fullWidth
553
+ multiline
554
+ rows = { 6 }
555
+ value = { newAliases }
556
+ onChange = { ( e ) => setNewAliases ( e . target . value ) }
557
+ placeholder = "One alias per line"
558
+ variant = "outlined"
559
+ disabled = { isSubmitting }
560
+ />
561
+ { submitResult && (
562
+ < Alert
563
+ severity = { submitResult . success ? "success" : "error" }
564
+ sx = { { mt : 2 } }
565
+ >
566
+ { submitResult . message }
567
+ </ Alert >
568
+ ) }
569
+ </ Box >
570
+ </ DialogContent >
571
+ < DialogActions sx = { { px : 3 , pb : 2 } } >
572
+ < Button
573
+ onClick = { ( ) => setShowAddAliasDialog ( false ) }
574
+ disabled = { isSubmitting }
575
+ >
576
+ Cancel
577
+ </ Button >
578
+ < Button
579
+ onClick = { handleAddAliases }
580
+ variant = "contained"
581
+ color = "primary"
582
+ disabled = { ! newAliases . trim ( ) || isSubmitting }
583
+ startIcon = { isSubmitting ? < CircularProgress size = { 20 } color = "inherit" /> : null }
584
+ >
585
+ { isSubmitting ? 'Adding...' : 'Add Aliases' }
586
+ </ Button >
587
+ </ DialogActions >
588
+ </ Dialog >
377
589
</ HeaderedTabbedLayout >
378
590
) ;
379
591
} ;
0 commit comments