From e4328db4c129fde1129928c1dbcab0f741c56ef2 Mon Sep 17 00:00:00 2001 From: DonOmalVindula Date: Mon, 9 Feb 2026 14:48:16 +0530 Subject: [PATCH] Fix UserProfile component in AsgardeoV2 platform --- .changeset/cyan-spiders-fix.md | 5 ++ .../UserProfile/BaseUserProfile.styles.ts | 33 ++++++++++ .../UserProfile/BaseUserProfile.tsx | 61 ++++++++++++++----- .../contexts/Asgardeo/AsgardeoProvider.tsx | 8 ++- packages/react/src/utils/getDisplayName.ts | 38 +++++++----- 5 files changed, 114 insertions(+), 31 deletions(-) create mode 100644 .changeset/cyan-spiders-fix.md diff --git a/.changeset/cyan-spiders-fix.md b/.changeset/cyan-spiders-fix.md new file mode 100644 index 00000000..4adbec7a --- /dev/null +++ b/.changeset/cyan-spiders-fix.md @@ -0,0 +1,5 @@ +--- +'@asgardeo/react': patch +--- + +Fix UserProfile component in AsgardeoV2 platform diff --git a/packages/react/src/components/presentation/UserProfile/BaseUserProfile.styles.ts b/packages/react/src/components/presentation/UserProfile/BaseUserProfile.styles.ts index a5c69385..04ce968a 100644 --- a/packages/react/src/components/presentation/UserProfile/BaseUserProfile.styles.ts +++ b/packages/react/src/components/presentation/UserProfile/BaseUserProfile.styles.ts @@ -107,6 +107,35 @@ const useStyles = (theme: Theme, colorScheme: string) => { color: ${theme.vars.colors.text.primary}; `; + const profileSummary = css` + display: flex; + flex-direction: column; + align-items: flex-start; + `; + + const sectionRow = css` + display: flex; + align-items: center; + padding: calc(${theme.vars.spacing.unit} * 1) 0; + `; + + const sectionLabel = css` + font-size: 0.875rem; + font-weight: 600; + color: ${theme.vars.colors.text.primary}; + width: 160px; + flex-shrink: 0; + `; + + const sectionValue = css` + flex: 1; + display: flex; + align-items: center; + gap: calc(${theme.vars.spacing.unit} * 1.5); + font-size: 0.875rem; + color: ${theme.vars.colors.text.primary}; + `; + const infoContainer = css` display: flex; flex-direction: column; @@ -186,6 +215,7 @@ const useStyles = (theme: Theme, colorScheme: string) => { card, header, profileInfo, + profileSummary, name, infoContainer, info, @@ -201,6 +231,9 @@ const useStyles = (theme: Theme, colorScheme: string) => { complexTextarea, objectKey, objectValue, + sectionRow, + sectionLabel, + sectionValue, }; }, [ theme.vars.colors.background.surface, diff --git a/packages/react/src/components/presentation/UserProfile/BaseUserProfile.tsx b/packages/react/src/components/presentation/UserProfile/BaseUserProfile.tsx index 4fd94831..193d2d31 100644 --- a/packages/react/src/components/presentation/UserProfile/BaseUserProfile.tsx +++ b/packages/react/src/components/presentation/UserProfile/BaseUserProfile.tsx @@ -32,6 +32,8 @@ import Card from '../../primitives/Card/Card'; import useStyles from './BaseUserProfile.styles'; import useTranslation from '../../../hooks/useTranslation'; import Alert from '../../primitives/Alert/Alert'; +import Divider from '../../primitives/Divider/Divider'; +import Typography from '../../primitives/Typography/Typography'; import getDisplayName from '../../../utils/getDisplayName'; interface ExtendedFlatSchema { @@ -79,6 +81,7 @@ export interface BaseUserProfileProps { title?: string; error?: string | null; isLoading?: boolean; + displayNameAttributes?: string[]; } // Fields to skip based on schema.name @@ -124,6 +127,7 @@ const BaseUserProfile: FC = ({ isLoading = false, showFields = [], hideFields = [], + displayNameAttributes = [], }): ReactElement => { const {theme, colorScheme} = useTheme(); const [editedUser, setEditedUser] = useState(flattenedProfile || profile); @@ -308,7 +312,7 @@ const BaseUserProfile: FC = ({ picture: ['profile', 'profileUrl', 'picture', 'URL'], firstName: ['name.givenName', 'given_name'], lastName: ['name.familyName', 'family_name'], - email: ['emails'], + email: ['emails', 'email'], username: ['userName', 'username', 'user_name'], }; @@ -596,22 +600,45 @@ const BaseUserProfile: FC = ({ const renderProfileWithoutSchemas = () => { if (!currentUser) return null; + const displayName = getDisplayName(mergedMappings, profile, displayNameAttributes); + const profileEntries = Object.entries(currentUser) .filter(([key, value]) => { if (!shouldShowField(key)) return false; return value !== undefined && value !== '' && value !== null; }) - .sort(([a], [b]) => a.localeCompare(b)); // Sort alphabetically + .sort(([a], [b]) => a.localeCompare(b)); return ( <> - {profileEntries.map(([key, value]) => ( -
- {formatLabel(key)} -
- {typeof value === 'object' ? JSON.stringify(value, null, 2) : String(value)} +
+ + + {displayName} + + {getMappedUserProfileValue('email', mergedMappings, currentUser) && ( + + {getMappedUserProfileValue('email', mergedMappings, currentUser)} + + )} +
+ + {profileEntries.map(([key, value], index) => ( +
+
+
{formatLabel(key)}
+
+ {typeof value === 'object' ? : String(value)} +
+ {index < profileEntries.length - 1 && }
))} @@ -626,15 +653,17 @@ const BaseUserProfile: FC = ({ {error} )} -
- -
+ {schemas && schemas.length > 0 && ( +
+ +
+ )}
{schemas && schemas.length > 0 ? schemas diff --git a/packages/react/src/contexts/Asgardeo/AsgardeoProvider.tsx b/packages/react/src/contexts/Asgardeo/AsgardeoProvider.tsx index 3d209161..1d5e1b88 100644 --- a/packages/react/src/contexts/Asgardeo/AsgardeoProvider.tsx +++ b/packages/react/src/contexts/Asgardeo/AsgardeoProvider.tsx @@ -288,7 +288,13 @@ const AsgardeoProvider: FC> = ({ // TEMPORARY: Asgardeo V2 platform does not support SCIM2, Organizations endpoints yet. // Tracker: https://github.com/asgardeo/javascript/issues/212 if (config.platform === Platform.AsgardeoV2) { - setUser(extractUserClaimsFromIdToken(decodedToken)); + const claims = extractUserClaimsFromIdToken(decodedToken); + setUser(claims); + setUserProfile({ + profile: claims as User, + flattenedProfile: claims as User, + schemas: [], + }); } else { try { const user: User = await asgardeo.getUser({baseUrl: _baseUrl}); diff --git a/packages/react/src/utils/getDisplayName.ts b/packages/react/src/utils/getDisplayName.ts index c07f60ee..fe3e5c0d 100644 --- a/packages/react/src/utils/getDisplayName.ts +++ b/packages/react/src/utils/getDisplayName.ts @@ -24,29 +24,39 @@ import getMappedUserProfileValue from './getMappedUserProfileValue'; * * @param mergedMappings - The merged attribute mappings. * @param user - The user object containing profile information. + * @param displayAttributes - Optional array of attribute keys or paths to try first. + * Each entry is resolved via `getMappedUserProfileValue`. The first non-empty + * value found is returned. If none resolve, the default fallback chain is used. * * @example * ```ts - * const mergedMappings = { - * firstName: ['name.givenName', 'given_name'], - * lastName: ['name.familyName', 'family_name'], - * username: ['userName', 'username', 'user_name'], - * email: ['emails[0].value', 'email'], - * name: ['name', 'fullName'], - * }; + * // Default behavior — tries firstName+lastName, then username, email, name + * const displayName = getDisplayName(mergedMappings, user); * - * const user: User = { - * id: '1', - * name: 'John Doe', - * email: 'john.doe@example.com', - * }; + * // Custom attributes — try 'nickname' first, then fall back to defaults + * const displayName = getDisplayName(mergedMappings, user, ['nickname']); * - * const displayName = getDisplayName(mergedMappings, user); + * // Multiple custom attributes + * const displayName = getDisplayName(mergedMappings, user, ['preferred_username', 'nickname']); * ``` * * @returns The display name of the user. */ -const getDisplayName = (mergedMappings: {[key: string]: string | string[] | undefined}, user: User): string => { +const getDisplayName = ( + mergedMappings: {[key: string]: string | string[] | undefined}, + user: User, + displayAttributes?: string[], +): string => { + if (displayAttributes && displayAttributes.length > 0) { + for (const attr of displayAttributes) { + const value = getMappedUserProfileValue(attr, mergedMappings, user); + + if (value !== undefined && value !== null && value !== '') { + return String(value); + } + } + } + const firstName = getMappedUserProfileValue('firstName', mergedMappings, user); const lastName = getMappedUserProfileValue('lastName', mergedMappings, user);