Not Able to fetch the Mobile number using spfx webpart

Sudheer Madduru, Naga 0 Reputation points
2025-01-26T16:26:50.3733333+00:00

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
}

SharePoint Development
SharePoint Development
SharePoint: A group of Microsoft Products and technologies used for sharing and managing content, knowledge, and applications.Development: The process of researching, productizing, and refining new or existing technologies.
3,225 questions
0 comments No comments
{count} votes

1 answer

Sort by: Most helpful
  1. Emily Du-MSFT 49,156 Reputation points Microsoft Vendor
    2025-01-27T02:47:55.51+00:00

    Please follow below tips to troubleshoot the issue.

    1.Make sure your app has the correct Microsoft Graph API permissions: User.Read, User.ReadBasic.All and Directory.Read.All.

    2.Check whether the mobilePhone field is actually available for the logged-in user. Sometimes the value could be null if the user hasn't set a phone number, or it might be empty if the field is not populated in Azure AD.

    3.Check Field Names. Mobilphone field corresponds to the personal mobile number. BusinessPhones field corresponds to work phone numbers and is sometimes used instead of mobilePhone in corporate environments.

    4.You could add some debug logs to check returned user datas.

    const user = await client.api('/me')
        .select('displayName,mail,city,mobilePhone,country,id,aboutMe')
        .get();
    
    console.log('User data:', user); // Log the full user data for debugging
    
    this.setState({
      userDetails: {
        name: user.displayName || '',
        city: user.city || '',
        number: user.mobilePhone || '', // This should map the phone number correctly
        about: user.aboutMe || '',
        id: user.id
      },
      profilePhotoUrl: photoUrl,
      isLoading: false
    });
    
    

    If the answer is helpful, please click "Accept Answer" and kindly upvote it. If you have extra questions about this answer, please click "Comment".

    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.

    0 comments No comments

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.