Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions admin-ui/app/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,8 @@
"modality": "Modality",
"dateAdded": "Date Added",
"givenName": "Given Name",
"sn": "Last Name",
"mail": "Email",
"resources": "Resources",
"resourceId": "Resource id",
"scopeSelection": "Scope Selection",
Expand Down
2 changes: 2 additions & 0 deletions admin-ui/app/locales/es/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,8 @@
"modality": "Modalidad",
"dateAdded": "Fecha de Alta",
"givenName": "Nombre",
"sn": "Apellido",
"mail": "Correo",
"resources": "Recursos",
"resourceId": "ID de Recurso",
"scopeSelection": "Selección de Ámbito",
Expand Down
5 changes: 4 additions & 1 deletion admin-ui/app/locales/fr/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -569,7 +569,6 @@
"ssl_trust_store_format": "Format de magasin de confiance SSL",
"ssl_trust_store_pin": "NIP du magasin de confiance SSL",
"state": "État",
"status": "StatutStatut",
"subject_type": "Type de sujet",
"test_config": "Tester la configuration",
"test_connection": "Tester la connexion",
Expand Down Expand Up @@ -605,6 +604,10 @@
"nickName": "Surnom",
"modality": "Modalité",
"dateAdded": "date ajoutée",
"givenName": "Prénom",
"sn": "Nom de famille",
"mail": "Courriel",
"status": "Statut",
"resources": "Ressources",
"resourceId": "Identifiant de la ressource",
"iconUrl": "URL de l'icône",
Expand Down
3 changes: 3 additions & 0 deletions admin-ui/app/locales/pt/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -615,6 +615,9 @@
"userName": "Nome do usuário",
"nickName": "Apelido",
"modality": "modalidade",
"givenName": "Nome Próprio",
"sn": "Sobrenome",
"mail": "E-mail",
"dateAdded": "data adicionada",
"resources": "Recursos",
"resourceId": "ID do recurso",
Expand Down
212 changes: 98 additions & 114 deletions admin-ui/app/routes/Apps/Profile/ProfilePage.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useContext, useEffect, useCallback, useMemo } from 'react'
import React, { useContext, useEffect, useCallback, useMemo, memo } from 'react'
import { Container, Row, Col, Card, CardBody, Button, Badge, AvatarImage } from 'Components'
import { ErrorBoundary } from 'react-error-boundary'
import GluuErrorFallBack from '../Gluu/GluuErrorFallBack'
Expand All @@ -10,31 +10,47 @@ import styles from './styles'
import { Box, Divider, Skeleton } from '@mui/material'
import { getProfileDetails } from 'Redux/features/ProfileDetailsSlice'
import { randomAvatar } from '../../../utilities'
import getThemeColor from '../../../context/theme/config'
import { useCedarling } from '@/cedarling'
import { ADMIN_UI_RESOURCES } from '@/cedarling/utility'
import { CEDAR_RESOURCE_SCOPES } from '@/cedarling/constants/resourceScopes'
import { useAppNavigation, ROUTES } from '@/helpers/navigation'
import customColors from '@/customColors'
import type { AppDispatch, ProfileRootState, ThemeContextValue, CustomAttribute } from './types'

const JANS_ADMIN_UI_ROLE_ATTR = 'jansAdminUIRole'
const SKELETON_WIDTH = '45%'
const BADGE_PADDING = '4px 6px'
const SKELETON_HEIGHT = 40

const skeletonCenterStyle = {
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
} as const

const ProfileDetails: React.FC = () => {
const { t } = useTranslation()
const dispatch = useDispatch<AppDispatch>()
const theme = useContext(ThemeContext) as ThemeContextValue
const selectedTheme = useMemo(() => theme?.state?.theme ?? 'light', [theme?.state?.theme])
const themeColors = useMemo(() => getThemeColor(selectedTheme), [selectedTheme])
const { classes } = styles()
const { navigateToRoute } = useAppNavigation()

// Set page title
const selectedTheme = useMemo(() => theme?.state?.theme ?? 'light', [theme?.state?.theme])
const buttonColor = useMemo(() => `primary-${selectedTheme}`, [selectedTheme])

SetTitle(t('titles.profile_detail'))

const { loading, profileDetails } = useSelector(
(state: ProfileRootState) => state.profileDetailsReducer,
)
const { userinfo, token: authToken } = useSelector((state: ProfileRootState) => state.authReducer)
const userInum = useMemo(() => userinfo?.inum, [userinfo?.inum])
const authState = useSelector((state: ProfileRootState) => state.authReducer)
const { userinfo, token: authToken } = authState
const stateUserInum = (authState as { userInum?: string | null; hasSession?: boolean }).userInum
const hasSession = (authState as { hasSession?: boolean }).hasSession ?? false

const userInum = useMemo(() => stateUserInum || userinfo?.inum, [stateUserInum, userinfo?.inum])
const apiAccessToken = authToken?.access_token ?? null
const canMakeApiCall = hasSession || !!apiAccessToken

const { authorizeHelper, hasCedarWritePermission } = useCedarling()
const usersResourceId = useMemo(() => ADMIN_UI_RESOURCES.Users, [])
Expand All @@ -47,19 +63,19 @@ const ProfileDetails: React.FC = () => {
const jansAdminUIRole = useMemo(
() =>
profileDetails?.customAttributes?.find(
(att: CustomAttribute): boolean => att?.name === 'jansAdminUIRole',
(att: CustomAttribute): boolean => att?.name === JANS_ADMIN_UI_ROLE_ATTR,
),
[profileDetails?.customAttributes],
)

const avatarSrc = useMemo(() => randomAvatar(), [])

useEffect(() => {
if (!apiAccessToken || !userInum) {
if (!canMakeApiCall || !userInum) {
return
}
dispatch(getProfileDetails({ pattern: userInum }))
}, [apiAccessToken, dispatch, userInum])
}, [canMakeApiCall, dispatch, userInum])

useEffect(() => {
if (usersScopes && usersScopes.length > 0) {
Expand All @@ -79,14 +95,66 @@ const ProfileDetails: React.FC = () => {
return jansAdminUIRole.values.map((role: string, index: number) => (
<Badge
key={`${role}-${index}`}
style={{ padding: '4px 6px' }}
color={`primary-${selectedTheme}`}
style={{ padding: BADGE_PADDING, color: customColors.white }}
color={buttonColor}
className="me-1"
>
<span style={{ color: themeColors.fontColor }}>{role}</span>
{role}
</Badge>
))
}, [jansAdminUIRole?.values, selectedTheme, themeColors.fontColor])
}, [jansAdminUIRole?.values, buttonColor])

const renderField = useCallback(
(labelKey: string, value: string | undefined, isLoading: boolean) => {
if (isLoading) {
return <Skeleton animation="wave" />
}
return (
<Box display={'flex'} justifyContent={'space-between'} alignItems={'center'} mb={1}>
<Box fontWeight={700}>{t(labelKey)}</Box>
<Box>{value || '-'}</Box>
</Box>
)
},
[t],
)

const renderDisplayName = useMemo(() => {
if (loading) {
return (
<Box display={'flex'} justifyContent={'center'} alignItems={'center'}>
<Skeleton width={SKELETON_WIDTH} sx={skeletonCenterStyle} animation="wave" />
</Box>
)
}
return (
<Box fontWeight={700} fontSize={'16px'} className="text-center mb-4">
{profileDetails?.displayName}
</Box>
)
}, [loading, profileDetails?.displayName])

const renderUserRolesField = useMemo(() => {
if (loading) {
return <Skeleton animation="wave" />
}
return (
<Box display={'flex'} justifyContent={'space-between'} alignItems={'center'} mb={1} gap={3}>
<Box fontWeight={700}>{t('titles.roles')}</Box>
{roleBadges && (
<Box
display={'flex'}
gap={'2px'}
flexWrap={'wrap'}
alignItems={'end'}
justifyContent={'end'}
>
{roleBadges}
</Box>
)}
</Box>
)
}, [loading, roleBadges, t])

return (
<ErrorBoundary FallbackComponent={GluuErrorFallBack}>
Expand All @@ -101,120 +169,36 @@ const ProfileDetails: React.FC = () => {
</Box>
<Box display={'flex'} flexDirection={'column'} gap={2}>
<Box display={'flex'} flexDirection={'column'} gap={1}>
{loading ? (
<Box display={'flex'} justifyContent={'center'} alignItems={'center'}>
<Skeleton
width={'45%'}
sx={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}}
animation="wave"
/>
</Box>
) : (
<Box fontWeight={700} fontSize={'16px'} className="text-center mb-4">
{profileDetails?.displayName}
</Box>
)}
{loading ? (
<Skeleton animation="wave" />
) : (
<Box
display={'flex'}
justifyContent={'space-between'}
alignItems={'center'}
mb={1}
>
<Box fontWeight={700}>First Name</Box>
<Box>{profileDetails?.givenName}</Box>
</Box>
)}
{renderDisplayName}
{renderField('fields.givenName', profileDetails?.givenName, loading)}
<Divider />
{loading ? (
<Skeleton animation="wave" />
) : (
<Box
display={'flex'}
justifyContent={'space-between'}
alignItems={'center'}
mb={1}
>
<Box fontWeight={700}>Last Name</Box>
<Box>{userinfo?.family_name}</Box>
</Box>
{renderField(
'fields.sn',
profileDetails?.customAttributes?.find(
(att: CustomAttribute) => att?.name === 'sn',
)?.values?.[0],
loading,
)}
<Divider />
{loading ? (
<Skeleton animation="wave" />
) : (
<Box
display={'flex'}
justifyContent={'space-between'}
alignItems={'center'}
mb={1}
>
<Box fontWeight={700}>Email</Box>
<Box>{profileDetails?.mail}</Box>
</Box>
)}
{renderField('fields.mail', profileDetails?.mail, loading)}
<Divider />
{loading ? (
<Skeleton animation="wave" />
) : (
<Box
display={'flex'}
justifyContent={'space-between'}
alignItems={'center'}
mb={1}
gap={3}
>
<Box fontWeight={700}>User Roles</Box>
{roleBadges && (
<Box
display={'flex'}
gap={'2px'}
flexWrap={'wrap'}
alignItems={'end'}
justifyContent={'end'}
>
{roleBadges}
</Box>
)}
</Box>
)}
{renderUserRolesField}
<Divider />
{loading ? (
<Skeleton animation="wave" />
) : (
<Box
display={'flex'}
justifyContent={'space-between'}
alignItems={'center'}
mb={1}
>
<Box fontWeight={700}>Status</Box>
<Box>{profileDetails?.status || '-'}</Box>
</Box>
)}
{renderField('fields.status', profileDetails?.status, loading)}
<Divider />
</Box>
{canEditProfile ? (
{canEditProfile && (
<>
{loading ? (
<Skeleton animation="wave" height={40} />
<Skeleton animation="wave" height={SKELETON_HEIGHT} />
) : (
<Button
color={`primary-${selectedTheme}`}
onClick={navigateToUserManagement}
>
<Button color={buttonColor} onClick={navigateToUserManagement}>
<i className="fa fa-pencil me-2" />
{t('actions.edit')}
</Button>
)}
</>
) : null}
)}
</Box>
</React.Fragment>
</CardBody>
Expand All @@ -226,4 +210,4 @@ const ProfileDetails: React.FC = () => {
)
}

export default React.memo(ProfileDetails)
export default memo(ProfileDetails)
4 changes: 4 additions & 0 deletions admin-ui/app/routes/Apps/Profile/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ export interface ProfileDetails {
mail?: string
status?: string
inum?: string
sn?: string
surname?: string
customAttributes?: CustomAttribute[]
[key: string]: unknown // Allow additional properties from API
}

export interface UserInfo {
Expand All @@ -34,6 +37,7 @@ export interface AuthState {
userinfo?: UserInfo
token?: AuthToken | null
issuer?: string | null
userInum?: string | null
}

export interface ProfileRootState {
Expand Down
4 changes: 2 additions & 2 deletions admin-ui/plugins/user-management/components/UserAddPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import type { CustomUser } from '../types/UserApiTypes'

function UserAddPage() {
const dispatch = useDispatch()
const { navigateToRoute } = useAppNavigation()
const { navigateBack } = useAppNavigation()
const queryClient = useQueryClient()
const { t } = useTranslation()
const { data: attributesData, isLoading: loadingAttributes } = useGetAttributes({
Expand All @@ -35,7 +35,7 @@ function UserAddPage() {
await logUserCreation(data, variables.data)
triggerUserWebhook(data)
queryClient.invalidateQueries({ queryKey: getGetUserQueryKey() })
navigateToRoute(ROUTES.USER_MANAGEMENT)
navigateBack(ROUTES.USER_MANAGEMENT)
},
onError: (error: unknown) => {
const errMsg = getErrorMessage(error)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import { AXIOS_INSTANCE } from '../../../api-client'

function UserEditPage() {
const dispatch = useDispatch()
const { navigateToRoute } = useAppNavigation()
const { navigateToRoute, navigateBack } = useAppNavigation()
const location = useLocation()
const queryClient = useQueryClient()
const { t } = useTranslation()
Expand Down Expand Up @@ -81,7 +81,7 @@ function UserEditPage() {
await logUserUpdate(data, variables.data)
triggerUserWebhook(data)
queryClient.invalidateQueries({ queryKey: getGetUserQueryKey() })
navigateToRoute(ROUTES.USER_MANAGEMENT)
navigateBack(ROUTES.USER_MANAGEMENT)
},
onError: (error: unknown) => {
const errMsg = getErrorMessage(error)
Expand Down
Loading