1- import React , { useContext , useEffect , useCallback , useMemo } from 'react'
1+ import React , { useContext , useEffect , useCallback , useMemo , memo } from 'react'
22import { Container , Row , Col , Card , CardBody , Button , Badge , AvatarImage } from 'Components'
33import { ErrorBoundary } from 'react-error-boundary'
44import GluuErrorFallBack from '../Gluu/GluuErrorFallBack'
@@ -10,31 +10,47 @@ import styles from './styles'
1010import { Box , Divider , Skeleton } from '@mui/material'
1111import { getProfileDetails } from 'Redux/features/ProfileDetailsSlice'
1212import { randomAvatar } from '../../../utilities'
13- import getThemeColor from '../../../context/theme/config'
1413import { useCedarling } from '@/cedarling'
1514import { ADMIN_UI_RESOURCES } from '@/cedarling/utility'
1615import { CEDAR_RESOURCE_SCOPES } from '@/cedarling/constants/resourceScopes'
1716import { useAppNavigation , ROUTES } from '@/helpers/navigation'
17+ import customColors from '@/customColors'
1818import type { AppDispatch , ProfileRootState , ThemeContextValue , CustomAttribute } from './types'
1919
20+ const JANS_ADMIN_UI_ROLE_ATTR = 'jansAdminUIRole'
21+ const SKELETON_WIDTH = '45%'
22+ const BADGE_PADDING = '4px 6px'
23+ const SKELETON_HEIGHT = 40
24+
25+ const skeletonCenterStyle = {
26+ display : 'flex' ,
27+ justifyContent : 'center' ,
28+ alignItems : 'center' ,
29+ } as const
30+
2031const ProfileDetails : React . FC = ( ) => {
2132 const { t } = useTranslation ( )
2233 const dispatch = useDispatch < AppDispatch > ( )
2334 const theme = useContext ( ThemeContext ) as ThemeContextValue
24- const selectedTheme = useMemo ( ( ) => theme ?. state ?. theme ?? 'light' , [ theme ?. state ?. theme ] )
25- const themeColors = useMemo ( ( ) => getThemeColor ( selectedTheme ) , [ selectedTheme ] )
2635 const { classes } = styles ( )
2736 const { navigateToRoute } = useAppNavigation ( )
2837
29- // Set page title
38+ const selectedTheme = useMemo ( ( ) => theme ?. state ?. theme ?? 'light' , [ theme ?. state ?. theme ] )
39+ const buttonColor = useMemo ( ( ) => `primary-${ selectedTheme } ` , [ selectedTheme ] )
40+
3041 SetTitle ( t ( 'titles.profile_detail' ) )
3142
3243 const { loading, profileDetails } = useSelector (
3344 ( state : ProfileRootState ) => state . profileDetailsReducer ,
3445 )
35- const { userinfo, token : authToken } = useSelector ( ( state : ProfileRootState ) => state . authReducer )
36- const userInum = useMemo ( ( ) => userinfo ?. inum , [ userinfo ?. inum ] )
46+ const authState = useSelector ( ( state : ProfileRootState ) => state . authReducer )
47+ const { userinfo, token : authToken } = authState
48+ const stateUserInum = ( authState as { userInum ?: string | null ; hasSession ?: boolean } ) . userInum
49+ const hasSession = ( authState as { hasSession ?: boolean } ) . hasSession ?? false
50+
51+ const userInum = useMemo ( ( ) => stateUserInum || userinfo ?. inum , [ stateUserInum , userinfo ?. inum ] )
3752 const apiAccessToken = authToken ?. access_token ?? null
53+ const canMakeApiCall = hasSession || ! ! apiAccessToken
3854
3955 const { authorizeHelper, hasCedarWritePermission } = useCedarling ( )
4056 const usersResourceId = useMemo ( ( ) => ADMIN_UI_RESOURCES . Users , [ ] )
@@ -47,19 +63,19 @@ const ProfileDetails: React.FC = () => {
4763 const jansAdminUIRole = useMemo (
4864 ( ) =>
4965 profileDetails ?. customAttributes ?. find (
50- ( att : CustomAttribute ) : boolean => att ?. name === 'jansAdminUIRole' ,
66+ ( att : CustomAttribute ) : boolean => att ?. name === JANS_ADMIN_UI_ROLE_ATTR ,
5167 ) ,
5268 [ profileDetails ?. customAttributes ] ,
5369 )
5470
5571 const avatarSrc = useMemo ( ( ) => randomAvatar ( ) , [ ] )
5672
5773 useEffect ( ( ) => {
58- if ( ! apiAccessToken || ! userInum ) {
74+ if ( ! canMakeApiCall || ! userInum ) {
5975 return
6076 }
6177 dispatch ( getProfileDetails ( { pattern : userInum } ) )
62- } , [ apiAccessToken , dispatch , userInum ] )
78+ } , [ canMakeApiCall , dispatch , userInum ] )
6379
6480 useEffect ( ( ) => {
6581 if ( usersScopes && usersScopes . length > 0 ) {
@@ -79,14 +95,66 @@ const ProfileDetails: React.FC = () => {
7995 return jansAdminUIRole . values . map ( ( role : string , index : number ) => (
8096 < Badge
8197 key = { `${ role } -${ index } ` }
82- style = { { padding : '4px 6px' } }
83- color = { `primary- ${ selectedTheme } ` }
98+ style = { { padding : BADGE_PADDING , color : customColors . white } }
99+ color = { buttonColor }
84100 className = "me-1"
85101 >
86- < span style = { { color : themeColors . fontColor } } > { role } </ span >
102+ { role }
87103 </ Badge >
88104 ) )
89- } , [ jansAdminUIRole ?. values , selectedTheme , themeColors . fontColor ] )
105+ } , [ jansAdminUIRole ?. values , buttonColor ] )
106+
107+ const renderField = useCallback (
108+ ( labelKey : string , value : string | undefined , isLoading : boolean ) => {
109+ if ( isLoading ) {
110+ return < Skeleton animation = "wave" />
111+ }
112+ return (
113+ < Box display = { 'flex' } justifyContent = { 'space-between' } alignItems = { 'center' } mb = { 1 } >
114+ < Box fontWeight = { 700 } > { t ( labelKey ) } </ Box >
115+ < Box > { value || '-' } </ Box >
116+ </ Box >
117+ )
118+ } ,
119+ [ t ] ,
120+ )
121+
122+ const renderDisplayName = useMemo ( ( ) => {
123+ if ( loading ) {
124+ return (
125+ < Box display = { 'flex' } justifyContent = { 'center' } alignItems = { 'center' } >
126+ < Skeleton width = { SKELETON_WIDTH } sx = { skeletonCenterStyle } animation = "wave" />
127+ </ Box >
128+ )
129+ }
130+ return (
131+ < Box fontWeight = { 700 } fontSize = { '16px' } className = "text-center mb-4" >
132+ { profileDetails ?. displayName }
133+ </ Box >
134+ )
135+ } , [ loading , profileDetails ?. displayName ] )
136+
137+ const renderUserRolesField = useMemo ( ( ) => {
138+ if ( loading ) {
139+ return < Skeleton animation = "wave" />
140+ }
141+ return (
142+ < Box display = { 'flex' } justifyContent = { 'space-between' } alignItems = { 'center' } mb = { 1 } gap = { 3 } >
143+ < Box fontWeight = { 700 } > { t ( 'titles.roles' ) } </ Box >
144+ { roleBadges && (
145+ < Box
146+ display = { 'flex' }
147+ gap = { '2px' }
148+ flexWrap = { 'wrap' }
149+ alignItems = { 'end' }
150+ justifyContent = { 'end' }
151+ >
152+ { roleBadges }
153+ </ Box >
154+ ) }
155+ </ Box >
156+ )
157+ } , [ loading , roleBadges , t ] )
90158
91159 return (
92160 < ErrorBoundary FallbackComponent = { GluuErrorFallBack } >
@@ -101,120 +169,36 @@ const ProfileDetails: React.FC = () => {
101169 </ Box >
102170 < Box display = { 'flex' } flexDirection = { 'column' } gap = { 2 } >
103171 < Box display = { 'flex' } flexDirection = { 'column' } gap = { 1 } >
104- { loading ? (
105- < Box display = { 'flex' } justifyContent = { 'center' } alignItems = { 'center' } >
106- < Skeleton
107- width = { '45%' }
108- sx = { {
109- display : 'flex' ,
110- justifyContent : 'center' ,
111- alignItems : 'center' ,
112- } }
113- animation = "wave"
114- />
115- </ Box >
116- ) : (
117- < Box fontWeight = { 700 } fontSize = { '16px' } className = "text-center mb-4" >
118- { profileDetails ?. displayName }
119- </ Box >
120- ) }
121- { loading ? (
122- < Skeleton animation = "wave" />
123- ) : (
124- < Box
125- display = { 'flex' }
126- justifyContent = { 'space-between' }
127- alignItems = { 'center' }
128- mb = { 1 }
129- >
130- < Box fontWeight = { 700 } > First Name</ Box >
131- < Box > { profileDetails ?. givenName } </ Box >
132- </ Box >
133- ) }
172+ { renderDisplayName }
173+ { renderField ( 'fields.givenName' , profileDetails ?. givenName , loading ) }
134174 < Divider />
135- { loading ? (
136- < Skeleton animation = "wave" />
137- ) : (
138- < Box
139- display = { 'flex' }
140- justifyContent = { 'space-between' }
141- alignItems = { 'center' }
142- mb = { 1 }
143- >
144- < Box fontWeight = { 700 } > Last Name</ Box >
145- < Box > { userinfo ?. family_name } </ Box >
146- </ Box >
175+ { renderField (
176+ 'fields.sn' ,
177+ profileDetails ?. customAttributes ?. find (
178+ ( att : CustomAttribute ) => att ?. name === 'sn' ,
179+ ) ?. values ?. [ 0 ] ,
180+ loading ,
147181 ) }
148182 < Divider />
149- { loading ? (
150- < Skeleton animation = "wave" />
151- ) : (
152- < Box
153- display = { 'flex' }
154- justifyContent = { 'space-between' }
155- alignItems = { 'center' }
156- mb = { 1 }
157- >
158- < Box fontWeight = { 700 } > Email</ Box >
159- < Box > { profileDetails ?. mail } </ Box >
160- </ Box >
161- ) }
183+ { renderField ( 'fields.mail' , profileDetails ?. mail , loading ) }
162184 < Divider />
163- { loading ? (
164- < Skeleton animation = "wave" />
165- ) : (
166- < Box
167- display = { 'flex' }
168- justifyContent = { 'space-between' }
169- alignItems = { 'center' }
170- mb = { 1 }
171- gap = { 3 }
172- >
173- < Box fontWeight = { 700 } > User Roles</ Box >
174- { roleBadges && (
175- < Box
176- display = { 'flex' }
177- gap = { '2px' }
178- flexWrap = { 'wrap' }
179- alignItems = { 'end' }
180- justifyContent = { 'end' }
181- >
182- { roleBadges }
183- </ Box >
184- ) }
185- </ Box >
186- ) }
185+ { renderUserRolesField }
187186 < Divider />
188- { loading ? (
189- < Skeleton animation = "wave" />
190- ) : (
191- < Box
192- display = { 'flex' }
193- justifyContent = { 'space-between' }
194- alignItems = { 'center' }
195- mb = { 1 }
196- >
197- < Box fontWeight = { 700 } > Status</ Box >
198- < Box > { profileDetails ?. status || '-' } </ Box >
199- </ Box >
200- ) }
187+ { renderField ( 'fields.status' , profileDetails ?. status , loading ) }
201188 < Divider />
202189 </ Box >
203- { canEditProfile ? (
190+ { canEditProfile && (
204191 < >
205192 { loading ? (
206- < Skeleton animation = "wave" height = { 40 } />
193+ < Skeleton animation = "wave" height = { SKELETON_HEIGHT } />
207194 ) : (
208- < Button
209- color = { `primary-${ selectedTheme } ` }
210- onClick = { navigateToUserManagement }
211- >
195+ < Button color = { buttonColor } onClick = { navigateToUserManagement } >
212196 < i className = "fa fa-pencil me-2" />
213197 { t ( 'actions.edit' ) }
214198 </ Button >
215199 ) }
216200 </ >
217- ) : null }
201+ ) }
218202 </ Box >
219203 </ React . Fragment >
220204 </ CardBody >
@@ -226,4 +210,4 @@ const ProfileDetails: React.FC = () => {
226210 )
227211}
228212
229- export default React . memo ( ProfileDetails )
213+ export default memo ( ProfileDetails )
0 commit comments