Hi,
I am trying to fetch the current logged in user name,employee id,email address and phone number.
But I am not able to get the current logged in user phone number with the below source code.
Please let me know if I am making any mistake
//UserInfoWebPart.ts
import * as React from 'react';
import * as ReactDom from 'react-dom';
import { Version } from '@microsoft/sp-core-library';
import {
type IPropertyPaneConfiguration,
PropertyPaneTextField
} from '@microsoft/sp-property-pane';
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
import { IReadonlyTheme } from '@microsoft/sp-component-base';
import * as strings from 'UserInfoWebPartStrings';
import UserInfo from './components/UserInfo';
import { IUserInfoProps } from './components/IUserInfoProps';
export interface IUserInfoWebPartProps {
description: string;
}
export default class UserInfoWebPart extends BaseClientSideWebPart<IUserInfoWebPartProps> {
private _isDarkTheme: boolean = false;
private _environmentMessage: string = '';
public render(): void {
const element: React.ReactElement<IUserInfoProps> = React.createElement(
UserInfo,
{
description: this.properties.description,
isDarkTheme: this._isDarkTheme,
environmentMessage: this._environmentMessage,
hasTeamsContext: !!this.context.sdks.microsoftTeams,
userDisplayName: this.context.pageContext.user.displayName,
context:this.context
}
);
ReactDom.render(element, this.domElement);
}
protected onInit(): Promise<void> {
return this._getEnvironmentMessage().then(message => {
this._environmentMessage = message;
});
}
private _getEnvironmentMessage(): Promise<string> {
if (!!this.context.sdks.microsoftTeams) { // running in Teams, office.com or Outlook
return this.context.sdks.microsoftTeams.teamsJs.app.getContext()
.then(context => {
let environmentMessage: string = '';
switch (context.app.host.name) {
case 'Office': // running in Office
environmentMessage = this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentOffice : strings.AppOfficeEnvironment;
break;
case 'Outlook': // running in Outlook
environmentMessage = this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentOutlook : strings.AppOutlookEnvironment;
break;
case 'Teams': // running in Teams
case 'TeamsModern':
environmentMessage = this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentTeams : strings.AppTeamsTabEnvironment;
break;
default:
environmentMessage = strings.UnknownEnvironment;
}
return environmentMessage;
});
}
return Promise.resolve(this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentSharePoint : strings.AppSharePointEnvironment);
}
protected onThemeChanged(currentTheme: IReadonlyTheme | undefined): void {
if (!currentTheme) {
return;
}
this._isDarkTheme = !!currentTheme.isInverted;
const {
semanticColors
} = currentTheme;
if (semanticColors) {
this.domElement.style.setProperty('--bodyText', semanticColors.bodyText || null);
this.domElement.style.setProperty('--link', semanticColors.link || null);
this.domElement.style.setProperty('--linkHovered', semanticColors.linkHovered || null);
}
}
protected onDispose(): void {
ReactDom.unmountComponentAtNode(this.domElement);
}
protected get dataVersion(): Version {
return Version.parse('1.0');
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneTextField('description', {
label: strings.DescriptionFieldLabel
})
]
}
]
}
]
};
}
}
//UserInfo.tsx
import * as React from 'react';
import {
DocumentCard,
DocumentCardDetails,
Stack,
TextField,
DefaultButton,
MessageBar,
MessageBarType,
Spinner,
SpinnerSize,
IconButton,
Text,
Persona,
PersonaSize,
mergeStyles,
ITextStyles,
Label,
IPersonaProps,
} from '@fluentui/react';
import { IUserInfoProps } from './IUserInfoProps';
interface IUserInfoState {
userDetails: {
name: string;
city: string;
number: string;
about: string;
id: string;
};
profilePhotoUrl: string | null;
isEditing: boolean;
error: string | null;
isLoading: boolean;
}
const stackTokens = { childrenGap: 12 };
// Styles
const headerStyles = mergeStyles({
backgroundColor: '#f5f5f5',
padding: '20px',
borderTopLeftRadius: '4px',
borderTopRightRadius: '4px',
borderBottom: '1px solid #eee'
});
const detailRowClass = mergeStyles({
padding: '16px',
backgroundColor: '#f8f8f8',
marginBottom: '8px',
borderRadius: '4px',
transition: 'all 0.2s ease',
selectors: {
':hover': {
backgroundColor: '#f0f0f0',
}
}
});
const labelStyles: ITextStyles = {
root: {
fontSize: '14px',
fontWeight: '600',
color: '#333',
minWidth: '120px',
marginRight: '16px'
}
};
const valueStyles: ITextStyles = {
root: {
fontSize: '14px',
color: '#666',
flex: 1
}
};
const iconButtonStyles = {
root: {
color: '#0078d4',
},
rootHovered: {
color: '#106ebe',
}
};
export default class UserInfo extends React.Component<IUserInfoProps, IUserInfoState> {
constructor(props: IUserInfoProps) {
super(props);
this.state = {
userDetails: {
name: '',
city: '',
number: '',
id: '',
about: ''
},
profilePhotoUrl: null,
isEditing: false,
error: null,
isLoading: true
};
}
componentDidMount() {
this.fetchUserDetails();
}
private handleInputChange = (field: keyof typeof this.state.userDetails, value: string) => {
this.setState(prevState => ({
userDetails: {
...prevState.userDetails,
[field]: value
}
}));
};
private fetchProfilePhoto = async (client: any): Promise<string | null> => {
try {
// First, check if user has a photo
const photoResponse = await client.api('/me/photo/$value')
.get();
// Convert blob to base64
const reader = new FileReader();
return new Promise((resolve, reject) => {
reader.onloadend = () => resolve(reader.result as string);
reader.onerror = reject;
reader.readAsDataURL(photoResponse);
});
} catch (error) {
console.log('No profile photo found or error fetching photo');
return null;
}
};
private fetchUserDetails = async () => {
this.setState({ isLoading: true, error: null });
try {
const client = await this.props.context.msGraphClientFactory.getClient('3');
// Fetch user details and photo in parallel
const [user, photoUrl] = await Promise.all([
client.api('/me')
.select('displayName,mail,city,mobilephone,country,id,aboutMe')
.get(),
this.fetchProfilePhoto(client)
]);
console.log("Fetched user data:", user); // Debug log
this.setState({
userDetails: {
name: user.displayName || '',
city: user.city || '',
number: user.mobilePhone || '',
about: user.aboutMe || '',
id: user.id
},
profilePhotoUrl: photoUrl,
isLoading: false
});
} catch (error: any) {
console.error('Error fetching user details:', error);
let errorMessage = 'Failed to fetch user details. ';
if (error.statusCode === 403) {
errorMessage += 'Access denied. Please contact your administrator.';
} else if (error.statusCode === 401) {
errorMessage += 'Your session has expired. Please refresh the page.';
} else {
errorMessage += 'Please try again later.';
}
this.setState({
error: errorMessage,
isLoading: false
});
}
};
// private updateUserDetails = async () => {
// const { userDetails } = this.state;
// this.setState({ isLoading: true, error: null });
// try {
// const client = await this.props.context.msGraphClientFactory.getClient('3');
// // Log the update payload for debugging
// console.log('Updating user with payload:', {
// displayName: userDetails.name,
// city: userDetails.city,
// businessPhones: [userDetails.number],
// aboutMe: userDetails.about
// });
// // Prepare the update payload
// const updatePayload: any = {
// aboutMe: userDetails.about || null
// };
// // Only include businessPhones if a number is provided
// if (userDetails.number.trim()) {
// updatePayload.businessPhones = [userDetails.number.trim()];
// } else {
// updatePayload.businessPhones = []; // Empty array if no phone number
// }
// console.log('Updating user with payload:', updatePayload);
// // Update user profile
// await client.api('/me')
// .version('beta')
// .update(updatePayload);
// this.setState({ isEditing: false });
// await this.fetchUserDetails(); // Refresh the data after update
// } catch (error: any) {
// console.error('Error updating profile:', error);
// let errorMessage = 'Failed to update profile. ';
// // Add specific error handling
// if (error.statusCode === 403) {
// errorMessage += 'You don\'t have permission to update the profile. Please contact your administrator.';
// } else if (error.statusCode === 401) {
// errorMessage += 'Your session has expired. Please refresh the page.';
// } else if (error.statusCode === 400) {
// errorMessage += 'Invalid input provided. Please check your entries.';
// } else {
// errorMessage += 'Please try again later.';
// }
// this.setState({
// error: errorMessage,
// isLoading: false
// });
// }
// };
private validatePhoneNumber = (phone: string): boolean => {
// Basic phone number validation - adjust according to your requirements
return phone === '' || /^[+]?[\d\s-()]+$/.test(phone);
};
private updateUserDetails = async () => {
const { userDetails } = this.state;
this.setState({ isLoading: true, error: null });
// Validate phone number before updating
if (!this.validatePhoneNumber(userDetails.number)) {
this.setState({
error: 'Invalid phone number format. Please enter a valid phone number or leave it empty.',
isLoading: false
});
return;
}
try {
const client = await this.props.context.msGraphClientFactory.getClient('3');
// Prepare the update payload
const updatePayload: any = {
aboutMe: userDetails.about || null,
// mobilePhone: userDetails.number || null
};
// Only include businessPhones if a number is provided
// if (userDetails.number.trim()) {
// updatePayload.mobilePhone = [userDetails.number.trim()];
// } else {
// updatePayload.businessPhones = []; // Empty array if no phone number
// }
console.log('Updating user with payload:', updatePayload);
// Update user profile
await client.api('/me')
.version('beta').header('Content-Type', 'application/json')
.update(updatePayload);
this.setState({ isEditing: false });
await this.fetchUserDetails(); // Refresh the data after update
} catch (error: any) {
console.error('Error updating profile:', error);
let errorMessage = 'Failed to update profile. ';
if (error.statusCode === 403) {
errorMessage += 'You don\'t have permission to update the profile. Please contact your administrator.';
} else if (error.statusCode === 401) {
errorMessage += 'Your session has expired. Please refresh the page.';
} else if (error.statusCode === 400) {
errorMessage += 'Invalid input format. Please check your entries.';
} else {
errorMessage += 'Please try again later.';
}
this.setState({
error: errorMessage,
isLoading: false
});
}
};
private toggleEdit = () => {
this.setState(prevState => ({ isEditing: !prevState.isEditing }));
};
private renderDetailRow = (label: string, value: string, field: keyof typeof this.state.userDetails) => {
const { isEditing, userDetails } = this.state;
return (
<Stack horizontal className={detailRowClass} verticalAlign="center">
<Text styles={labelStyles}>{label}</Text>
{isEditing ? (
<TextField
value={userDetails[field]}
onChange={(_, newValue) => this.handleInputChange(field, newValue || '')}
styles={{
root: { flex: 1 },
fieldGroup: { backgroundColor: 'white' }
}}
/>
) : (
<Text styles={valueStyles}>{value || 'Not specified'}</Text>
)}
</Stack>
);
};
public render(): React.ReactElement<IUserInfoProps> {
const { userDetails, isEditing, error, profilePhotoUrl, isLoading } = this.state;
// Determine if the field should be disabled
if (isLoading && !isEditing) {
return (
<Stack horizontalAlign="center" verticalAlign="center" styles={{ root: { minHeight: 300 } }}>
<Spinner size={SpinnerSize.large} label="Loading user details..." />
</Stack>
);
}
const personaProps: IPersonaProps = {
// text: userDetails.name,
size: PersonaSize.size48,
imageUrl: profilePhotoUrl || undefined,
styles: {
root: {
'.ms-Persona-coin': {
// backgroundColor: '#8b1919'
}
}
}
};
return (
<DocumentCard styles={{ root: { maxWidth: 800, margin: '0 auto', boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)' } }}>
{/* Header Section */}
{/* Header Section */}
<Stack horizontal className={headerStyles} horizontalAlign="space-between" verticalAlign="center">
<Stack horizontal tokens={{ childrenGap: 16 }} verticalAlign="center">
<Persona {...personaProps} />
<Text variant="xLarge" styles={{ root: { fontWeight: 600 } }}>
{userDetails.name}'s Profile
</Text>
</Stack>
<IconButton
iconProps={{ iconName: isEditing ? 'Cancel' : 'Edit' }}
onClick={this.toggleEdit}
styles={iconButtonStyles}
/>
</Stack>
<DocumentCardDetails>
<Stack tokens={stackTokens} styles={{ root: { padding: 20 } }}>
{/* Error Message */}
{error && (
<MessageBar messageBarType={MessageBarType.error}>
{error}
</MessageBar>
)}
{/* User Details Form */}
<Stack tokens={stackTokens}>
{this.renderDetailRow('Name', userDetails.name, 'name')}
{this.renderDetailRow('City', userDetails.city, 'city')}
{this.renderDetailRow('Phone Number', userDetails.number, 'number')}
{this.renderDetailRow('About', userDetails.about, 'about')}
{isEditing && (
<Stack horizontal horizontalAlign="end" tokens={{ childrenGap: 10 }}>
<DefaultButton
text="Save"
onClick={this.updateUserDetails}
disabled={isLoading}
/>
</Stack>
)}
</Stack>
</Stack>
</DocumentCardDetails>
</DocumentCard>
);
}
}
//IUserInfoProp.ts
import { WebPartContext } from "@microsoft/sp-webpart-base";
export interface IUserInfoProps {
description: string;
isDarkTheme: boolean;
environmentMessage: string;
hasTeamsContext: boolean;
userDisplayName: string;
context:WebPartContext
}