@@ -4,7 +4,7 @@ import { useRouter } from "next/router";
44import { ApiGetCall } from "/src/api/ApiCall" ;
55import CippFormSkeleton from "/src/components/CippFormPages/CippFormSkeleton" ;
66import 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" ;
88import { HeaderedTabbedLayout } from "../../../../../layouts/HeaderedTabbedLayout" ;
99import tabOptions from "./tabOptions" ;
1010import { CippTimeAgo } from "../../../../../components/CippComponents/CippTimeAgo" ;
@@ -16,18 +16,26 @@ import { CippExchangeInfoCard } from "../../../../../components/CippCards/CippEx
1616import { useEffect , useState } from "react" ;
1717import CippExchangeSettingsForm from "../../../../../components/CippFormPages/CippExchangeSettingsForm" ;
1818import { 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" ;
2020import { CippApiResults } from "../../../../../components/CippComponents/CippApiResults" ;
21- import { Block , PlayArrow , DeleteForever } from "@mui/icons-material" ;
21+ import { Block , PlayArrow } from "@mui/icons-material" ;
2222import { CippPropertyListCard } from "../../../../../components/CippCards/CippPropertyListCard" ;
2323import { getCippTranslation } from "../../../../../utils/get-cipp-translation" ;
2424import { getCippFormatting } from "../../../../../utils/get-cipp-formatting" ;
2525import CippExchangeActions from "../../../../../components/CippComponents/CippExchangeActions" ;
26+ import { CippApiDialog } from "../../../../../components/CippComponents/CippApiDialog" ;
27+ import { useDialog } from "../../../../../hooks/use-dialog" ;
2628
2729const Page = ( ) => {
2830 const userSettingsDefaults = useSettings ( ) ;
2931 const [ waiting , setWaiting ] = useState ( false ) ;
3032 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 ( ) ;
3139 const router = useRouter ( ) ;
3240 const { userId } = router . query ;
3341
@@ -221,7 +229,7 @@ const Page = () => {
221229 {
222230 label : "Remove Mailbox Rule" ,
223231 type : "POST" ,
224- icon : < DeleteForever /> ,
232+ icon : < Delete /> ,
225233 url : "/api/ExecRemoveMailboxRule" ,
226234 data : {
227235 ruleId : "Identity" ,
@@ -287,6 +295,142 @@ const Page = () => {
287295 } ,
288296 ] ;
289297
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+
290434 return (
291435 < HeaderedTabbedLayout
292436 tabOptions = { tabOptions }
@@ -345,6 +489,11 @@ const Page = () => {
345489 </ Grid >
346490 < Grid item size = { 8 } >
347491 < Stack spacing = { 3 } >
492+ < CippBannerListCard
493+ isFetching = { graphUserRequest . isLoading }
494+ items = { proxyAddressesCard }
495+ isCollapsible = { graphUserRequest . data ?. [ 0 ] ?. proxyAddresses ?. length !== 0 }
496+ />
348497 < CippBannerListCard
349498 isFetching = { userRequest . isLoading }
350499 items = { permissions }
@@ -374,6 +523,69 @@ const Page = () => {
374523 </ Grid >
375524 </ Box >
376525 ) }
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 >
377589 </ HeaderedTabbedLayout >
378590 ) ;
379591} ;
0 commit comments