diff --git a/src/components/Avatar.tsx b/src/components/Avatar.tsx index 99183a1e6ba7..bf48894beaab 100644 --- a/src/components/Avatar.tsx +++ b/src/components/Avatar.tsx @@ -7,7 +7,6 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import * as ReportUtils from '@libs/ReportUtils'; import type {AvatarSource} from '@libs/UserUtils'; -import * as UserUtils from '@libs/UserUtils'; import type {AvatarSizeName} from '@styles/utils'; import CONST from '@src/CONST'; import type {AvatarType} from '@src/types/onyx/OnyxCommon'; @@ -50,13 +49,10 @@ type AvatarProps = { /** Owner of the avatar. If user, displayName. If workspace, policy name */ name?: string; - - /** Optional account id if it's user avatar */ - accountID?: number; }; function Avatar({ - source: originalSource, + source, imageStyles, iconAdditionalStyles, containerStyles, @@ -66,7 +62,6 @@ function Avatar({ fallbackIconTestID = '', type = CONST.ICON_TYPE_AVATAR, name = '', - accountID, }: AvatarProps) { const theme = useTheme(); const styles = useThemeStyles(); @@ -77,17 +72,16 @@ function Avatar({ useEffect(() => { setImageError(false); - }, [originalSource]); + }, [source]); const isWorkspace = type === CONST.ICON_TYPE_WORKSPACE; - const iconSize = StyleUtils.getAvatarSize(size); + const imageStyle: StyleProp = [StyleUtils.getAvatarStyle(size), imageStyles, styles.noBorderRadius]; const iconStyle = imageStyles ? [StyleUtils.getAvatarStyle(size), styles.bgTransparent, imageStyles] : undefined; // We pass the color styles down to the SVG for the workspace and fallback avatar. - const source = isWorkspace ? originalSource : UserUtils.getAvatar(originalSource, accountID); - const useFallBackAvatar = imageError || !source || source === Expensicons.FallbackAvatar; + const useFallBackAvatar = imageError || source === Expensicons.FallbackAvatar || !source; const fallbackAvatar = isWorkspace ? ReportUtils.getDefaultWorkspaceAvatar(name) : fallbackIcon || Expensicons.FallbackAvatar; const fallbackAvatarTestID = isWorkspace ? ReportUtils.getDefaultWorkspaceAvatarTestID(name) : fallbackIconTestID || 'SvgFallbackAvatar Icon'; const avatarSource = useFallBackAvatar ? fallbackAvatar : source; diff --git a/src/components/AvatarWithIndicator.tsx b/src/components/AvatarWithIndicator.tsx index 1bf18afb70ff..42b91b3d2d71 100644 --- a/src/components/AvatarWithIndicator.tsx +++ b/src/components/AvatarWithIndicator.tsx @@ -11,10 +11,7 @@ import Tooltip from './Tooltip'; type AvatarWithIndicatorProps = { /** URL for the avatar */ - source?: UserUtils.AvatarSource; - - /** account id if it's user avatar */ - accountID?: number; + source: UserUtils.AvatarSource; /** To show a tooltip on hover */ tooltipText?: string; @@ -26,7 +23,7 @@ type AvatarWithIndicatorProps = { isLoading?: boolean; }; -function AvatarWithIndicator({source, accountID, tooltipText = '', fallbackIcon = Expensicons.FallbackAvatar, isLoading = true}: AvatarWithIndicatorProps) { +function AvatarWithIndicator({source, tooltipText = '', fallbackIcon = Expensicons.FallbackAvatar, isLoading = true}: AvatarWithIndicatorProps) { const styles = useThemeStyles(); return ( @@ -38,7 +35,7 @@ function AvatarWithIndicator({source, accountID, tooltipText = '', fallbackIcon <> diff --git a/src/components/MultipleAvatars.tsx b/src/components/MultipleAvatars.tsx index 31d3d35af58d..dedaba500a9c 100644 --- a/src/components/MultipleAvatars.tsx +++ b/src/components/MultipleAvatars.tsx @@ -158,7 +158,6 @@ function MultipleAvatars({ name={icons[0].name} type={icons[0].type} fallbackIcon={icons[0].fallbackIcon} - accountID={icons[0].id} /> @@ -208,7 +207,6 @@ function MultipleAvatars({ name={icon.name} type={icon.type} fallbackIcon={icon.fallbackIcon} - accountID={icon.id} /> diff --git a/src/components/ReportActionItem/TaskView.tsx b/src/components/ReportActionItem/TaskView.tsx index e3e07ab0d7ad..9711e126907f 100644 --- a/src/components/ReportActionItem/TaskView.tsx +++ b/src/components/ReportActionItem/TaskView.tsx @@ -156,7 +156,7 @@ function TaskView({report, shouldShowHorizontalRule, ...props}: TaskViewProps) { {title} diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index e9f0fc9dc451..f2d299cf9250 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -7,7 +7,6 @@ import lodashSet from 'lodash/set'; import lodashSortBy from 'lodash/sortBy'; import Onyx from 'react-native-onyx'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; -import {FallbackAvatar} from '@components/Icon/Expensicons'; import type {SelectedTagOption} from '@components/TagPicker'; import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; @@ -305,14 +304,13 @@ function getAvatarsForAccountIDs(accountIDs: number[], personalDetails: OnyxEntr Object.entries(defaultValues).forEach((item) => { reversedDefaultValues[item[1]] = item[0]; }); - return accountIDs.map((accountID) => { const login = reversedDefaultValues[accountID] ?? ''; - const userPersonalDetail = personalDetails?.[accountID] ?? {login, accountID}; + const userPersonalDetail = personalDetails?.[accountID] ?? {login, accountID, avatar: ''}; return { id: accountID, - source: userPersonalDetail.avatar ?? FallbackAvatar, + source: UserUtils.getAvatar(userPersonalDetail.avatar, userPersonalDetail.accountID), type: CONST.ICON_TYPE_AVATAR, name: userPersonalDetail.login ?? '', }; @@ -335,7 +333,9 @@ function getPersonalDetailsForAccountIDs(accountIDs: number[] | undefined, perso } let personalDetail: OnyxEntry = personalDetails[accountID]; if (!personalDetail) { - personalDetail = {} as PersonalDetails; + personalDetail = { + avatar: UserUtils.getDefaultAvatar(cleanAccountID), + } as PersonalDetails; } if (cleanAccountID === CONST.ACCOUNT_ID.CONCIERGE) { @@ -364,7 +364,6 @@ function getParticipantsOption(participant: ReportUtils.OptionData | Participant // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const login = detail?.login || participant.login || ''; const displayName = PersonalDetailsUtils.getDisplayNameOrDefault(detail, LocalePhoneNumber.formatPhoneNumber(login)); - return { keyForList: String(detail?.accountID), login, @@ -375,7 +374,7 @@ function getParticipantsOption(participant: ReportUtils.OptionData | Participant alternateText: LocalePhoneNumber.formatPhoneNumber(login) || displayName, icons: [ { - source: detail?.avatar ?? FallbackAvatar, + source: UserUtils.getAvatar(detail?.avatar ?? '', detail?.accountID ?? -1), name: login, type: CONST.ICON_TYPE_AVATAR, id: detail?.accountID, @@ -759,7 +758,13 @@ function createOption( // Disabling this line for safeness as nullish coalescing works only if the value is undefined or null // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing result.searchText = getSearchText(report, reportName, personalDetailList, !!result.isChatRoom || !!result.isPolicyExpenseChat, !!result.isThread); - result.icons = ReportUtils.getIcons(report, personalDetails, personalDetail?.avatar, personalDetail?.login, personalDetail?.accountID); + result.icons = ReportUtils.getIcons( + report, + personalDetails, + UserUtils.getAvatar(personalDetail?.avatar ?? '', personalDetail?.accountID), + personalDetail?.login, + personalDetail?.accountID, + ); result.subtitle = subtitle; return result; @@ -1860,6 +1865,7 @@ function getOptions( [optimisticAccountID]: { accountID: optimisticAccountID, login: searchValue, + avatar: UserUtils.getDefaultAvatar(optimisticAccountID), }, }; userToInvite = createOption([optimisticAccountID], personalDetailsExtended, null, reportActions, { @@ -1872,10 +1878,10 @@ function getOptions( // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing userToInvite.alternateText = userToInvite.alternateText || searchValue; - // If user doesn't exist, use a fallback avatar + // If user doesn't exist, use a default avatar userToInvite.icons = [ { - source: FallbackAvatar, + source: UserUtils.getAvatar('', optimisticAccountID), name: searchValue, type: CONST.ICON_TYPE_AVATAR, }, @@ -1949,12 +1955,17 @@ function getShareLogOptions(options: OptionList, searchValue = '', betas: Beta[] */ function getIOUConfirmationOptionsFromPayeePersonalDetail(personalDetail: PersonalDetails | EmptyObject, amountText?: string): PayeePersonalDetails { const formattedLogin = LocalePhoneNumber.formatPhoneNumber(personalDetail.login ?? ''); - const icons = [{source: personalDetail.avatar ?? FallbackAvatar, name: personalDetail.login ?? '', type: CONST.ICON_TYPE_AVATAR, id: personalDetail.accountID}]; - return { text: PersonalDetailsUtils.getDisplayNameOrDefault(personalDetail, formattedLogin), alternateText: formattedLogin || PersonalDetailsUtils.getDisplayNameOrDefault(personalDetail, '', false), - icons, + icons: [ + { + source: UserUtils.getAvatar(personalDetail.avatar, personalDetail.accountID), + name: personalDetail.login ?? '', + type: CONST.ICON_TYPE_AVATAR, + id: personalDetail.accountID, + }, + ], descriptiveText: amountText ?? '', login: personalDetail.login ?? '', accountID: personalDetail.accountID, diff --git a/src/libs/PersonalDetailsUtils.ts b/src/libs/PersonalDetailsUtils.ts index 961b3db13487..ffa0605f1eba 100644 --- a/src/libs/PersonalDetailsUtils.ts +++ b/src/libs/PersonalDetailsUtils.ts @@ -153,6 +153,7 @@ function getPersonalDetailsOnyxDataForOptimisticUsers(newLogins: string[], newAc personalDetailsNew[accountID] = { login, accountID, + avatar: UserUtils.getDefaultAvatarURL(accountID), displayName: LocalePhoneNumber.formatPhoneNumber(login), }; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index f92106d40b6e..9705bef841d5 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -10,7 +10,7 @@ import type {OnyxCollection, OnyxEntry, OnyxUpdate} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import type {FileObject} from '@components/AttachmentModal'; -import {FallbackAvatar} from '@components/Icon/Expensicons'; +import * as Expensicons from '@components/Icon/Expensicons'; import * as defaultGroupAvatars from '@components/Icon/GroupDefaultAvatars'; import * as defaultWorkspaceAvatars from '@components/Icon/WorkspaceDefaultAvatars'; import type {IOUAction, IOUType} from '@src/CONST'; @@ -1630,7 +1630,7 @@ function getIconsForParticipants(participants: number[], personalDetails: OnyxCo const participantsList = participants || []; for (const accountID of participantsList) { - const avatarSource = personalDetails?.[accountID]?.avatar ?? FallbackAvatar; + const avatarSource = UserUtils.getAvatar(personalDetails?.[accountID]?.avatar ?? '', accountID); const displayNameLogin = personalDetails?.[accountID]?.displayName ? personalDetails?.[accountID]?.displayName : personalDetails?.[accountID]?.login; participantDetails.push([accountID, displayNameLogin ?? '', avatarSource, personalDetails?.[accountID]?.fallbackIcon ?? '']); } @@ -1691,12 +1691,12 @@ function getPersonalDetailsForAccountID(accountID: number): Partial, iouReportID: /** * Used from expense actions to decide if we need to build an optimistic expense report. - Create a new report if: - - we don't have an iouReport set in the chatReport - - we have one, but it's waiting on the payee adding a bank account - - we have one but we can't add more transactions to it due to: report is approved or settled, or report is processing and policy isn't on Instant submit reporting frequency + Create a new report if: + - we don't have an iouReport set in the chatReport + - we have one, but it's waiting on the payee adding a bank account + - we have one but we can't add more transactions to it due to: report is approved or settled, or report is processing and policy isn't on Instant submit reporting frequency */ function shouldCreateNewMoneyRequestReport(existingIOUReport: OnyxEntry | undefined | null, chatReport: OnyxEntry | null): boolean { return !existingIOUReport || hasIOUWaitingOnCurrentUserBankAccount(chatReport) || !canAddOrDeleteTransactions(existingIOUReport); diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 0d85c7a3c313..c86d91c44ff9 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -20,6 +20,7 @@ import * as OptionsListUtils from './OptionsListUtils'; import * as ReportActionsUtils from './ReportActionsUtils'; import * as ReportUtils from './ReportUtils'; import * as TaskUtils from './TaskUtils'; +import * as UserUtils from './UserUtils'; const visibleReportActionItems: ReportActions = {}; Onyx.connect({ @@ -414,7 +415,7 @@ function getOptionData({ result.subtitle = subtitle; result.participantsList = participantPersonalDetailList; - result.icons = ReportUtils.getIcons(report, personalDetails, personalDetail?.avatar, '', -1, policy); + result.icons = ReportUtils.getIcons(report, personalDetails, UserUtils.getAvatar(personalDetail?.avatar ?? {}, personalDetail?.accountID), '', -1, policy); result.searchText = OptionsListUtils.getSearchText(report, reportName, participantPersonalDetailList, result.isChatRoom || result.isPolicyExpenseChat, result.isThread); result.displayNamesWithTooltips = displayNamesWithTooltips; diff --git a/src/libs/UserUtils.ts b/src/libs/UserUtils.ts index 03066b21fa71..ce7e4963afc7 100644 --- a/src/libs/UserUtils.ts +++ b/src/libs/UserUtils.ts @@ -2,7 +2,7 @@ import Str from 'expensify-common/lib/str'; import type {OnyxEntry} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import * as defaultAvatars from '@components/Icon/DefaultAvatars'; -import {ConciergeAvatar, NotificationsAvatar} from '@components/Icon/Expensicons'; +import {ConciergeAvatar, FallbackAvatar, NotificationsAvatar} from '@components/Icon/Expensicons'; import CONST from '@src/CONST'; import type {LoginList} from '@src/types/onyx'; import type Login from '@src/types/onyx/Login'; @@ -82,7 +82,10 @@ function generateAccountID(searchValue: string): number { * @param [accountID] * @returns */ -function getDefaultAvatar(accountID = -1, avatarURL?: string): IconAsset | undefined { +function getDefaultAvatar(accountID = -1, avatarURL?: string): IconAsset { + if (accountID <= 0) { + return FallbackAvatar; + } if (Number(accountID) === CONST.ACCOUNT_ID.CONCIERGE) { return ConciergeAvatar; } @@ -122,7 +125,7 @@ function getDefaultAvatarURL(accountID: string | number = ''): string { } /** - * Given a user's avatar path, returns true if URL points to a default avatar, false otherwise + * Given a user's avatar path, returns true if user doesn't have an avatar or if URL points to a default avatar * @param avatarSource - the avatar source from user's personalDetails */ function isDefaultAvatar(avatarSource?: AvatarSource): avatarSource is string | undefined { @@ -137,6 +140,11 @@ function isDefaultAvatar(avatarSource?: AvatarSource): avatarSource is string | } } + if (!avatarSource) { + // If source is undefined, we should also use a default avatar + return true; + } + return false; } @@ -147,7 +155,7 @@ function isDefaultAvatar(avatarSource?: AvatarSource): avatarSource is string | * @param avatarSource - the avatar source from user's personalDetails * @param accountID - the accountID of the user */ -function getAvatar(avatarSource?: AvatarSource, accountID?: number): AvatarSource | undefined { +function getAvatar(avatarSource?: AvatarSource, accountID?: number): AvatarSource { return isDefaultAvatar(avatarSource) ? getDefaultAvatar(accountID, avatarSource) : avatarSource; } @@ -155,7 +163,7 @@ function getAvatar(avatarSource?: AvatarSource, accountID?: number): AvatarSourc * Provided an avatar URL, if avatar is a default avatar, return NewDot default avatar URL. * Otherwise, return the URL pointing to a user-uploaded avatar. * - * @param avatarSource - the avatar source from user's personalDetails + * @param avatarURL - the avatar source from user's personalDetails * @param accountID - the accountID of the user */ function getAvatarUrl(avatarSource: AvatarSource | undefined, accountID: number): AvatarSource { @@ -166,7 +174,7 @@ function getAvatarUrl(avatarSource: AvatarSource | undefined, accountID: number) * Avatars uploaded by users will have a _128 appended so that the asset server returns a small version. * This removes that part of the URL so the full version of the image can load. */ -function getFullSizeAvatar(avatarSource: AvatarSource | undefined, accountID?: number): AvatarSource | undefined { +function getFullSizeAvatar(avatarSource: AvatarSource | undefined, accountID?: number): AvatarSource { const source = getAvatar(avatarSource, accountID); if (typeof source !== 'string') { return source; @@ -178,7 +186,7 @@ function getFullSizeAvatar(avatarSource: AvatarSource | undefined, accountID?: n * Small sized avatars end with _128.. This adds the _128 at the end of the * source URL (before the file type) if it doesn't exist there already. */ -function getSmallSizeAvatar(avatarSource?: AvatarSource, accountID?: number): AvatarSource | undefined { +function getSmallSizeAvatar(avatarSource: AvatarSource, accountID?: number): AvatarSource { const source = getAvatar(avatarSource, accountID); if (typeof source !== 'string') { return source; diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 30ab2688b593..e4546d8deef8 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -42,6 +42,7 @@ import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import type {OptimisticChatReport, OptimisticCreatedReportAction, OptimisticIOUReportAction, TransactionDetails} from '@libs/ReportUtils'; import * as ReportUtils from '@libs/ReportUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; +import * as UserUtils from '@libs/UserUtils'; import ViolationsUtils from '@libs/Violations/ViolationsUtils'; import type {IOUAction, IOUType} from '@src/CONST'; import CONST from '@src/CONST'; @@ -1438,6 +1439,7 @@ function getMoneyRequestInformation( ? { [payerAccountID]: { accountID: payerAccountID, + avatar: UserUtils.getDefaultAvatarURL(payerAccountID), // Disabling this line since participant.displayName can be an empty string // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing displayName: LocalePhoneNumber.formatPhoneNumber(participant.displayName || payerEmail), @@ -3383,6 +3385,7 @@ function createSplitsAndOnyxData( ? { [accountID]: { accountID, + avatar: UserUtils.getDefaultAvatarURL(accountID), // Disabling this line since participant.displayName can be an empty string // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing displayName: LocalePhoneNumber.formatPhoneNumber(participant.displayName || email), @@ -3824,6 +3827,7 @@ function startSplitBill({ value: { [accountID]: { accountID, + avatar: UserUtils.getDefaultAvatarURL(accountID), // Disabling this line since participant.displayName can be an empty string // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing displayName: LocalePhoneNumber.formatPhoneNumber(participant.displayName || email), @@ -5007,6 +5011,7 @@ function getSendMoneyParams( value: { [recipientAccountID]: { accountID: recipientAccountID, + avatar: UserUtils.getDefaultAvatarURL(recipient.accountID), // Disabling this line since participant.displayName can be an empty string // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing displayName: recipient.displayName || recipient.login, diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 422f501bedf4..32adafebd940 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -71,6 +71,7 @@ import * as ReportUtils from '@libs/ReportUtils'; import {doesReportBelongToWorkspace} from '@libs/ReportUtils'; import type {OptimisticAddCommentReportAction} from '@libs/ReportUtils'; import shouldSkipDeepLinkNavigation from '@libs/shouldSkipDeepLinkNavigation'; +import * as UserUtils from '@libs/UserUtils'; import Visibility from '@libs/Visibility'; import CONFIG from '@src/CONFIG'; import type {OnboardingPurposeType} from '@src/CONST'; @@ -820,6 +821,7 @@ function openReport( optimisticPersonalDetails[accountID] = allPersonalDetails?.[accountID] ?? { login, accountID, + avatar: UserUtils.getDefaultAvatarURL(accountID), displayName: login, isOptimisticPersonalDetail: true, }; diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts index 752cd9df0325..2a37b900ddd4 100644 --- a/src/libs/actions/Task.ts +++ b/src/libs/actions/Task.ts @@ -14,6 +14,7 @@ import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import playSound, {SOUNDS} from '@libs/Sound'; +import * as UserUtils from '@libs/UserUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -684,7 +685,7 @@ function setAssigneeValue( // If this is an optimistic report, we likely don't have their personal details yet so we set it here optimistically as well const optimisticPersonalDetailsListAction = { accountID: assigneeAccountID, - avatar: allPersonalDetails?.[assigneeAccountID]?.avatar, + avatar: allPersonalDetails?.[assigneeAccountID]?.avatar ?? UserUtils.getDefaultAvatarURL(assigneeAccountID), displayName: allPersonalDetails?.[assigneeAccountID]?.displayName ?? assigneeEmail, login: assigneeEmail, }; diff --git a/src/pages/DetailsPage.tsx b/src/pages/DetailsPage.tsx index 338e51cb408e..49b3e856c65d 100755 --- a/src/pages/DetailsPage.tsx +++ b/src/pages/DetailsPage.tsx @@ -70,6 +70,7 @@ function DetailsPage({personalDetails, route, session}: DetailsPageProps) { accountID: optimisticAccountID, login, displayName: login, + avatar: UserUtils.getDefaultAvatar(optimisticAccountID), }; } @@ -114,10 +115,9 @@ function DetailsPage({personalDetails, route, session}: DetailsPageProps) { diff --git a/src/pages/ProfilePage.tsx b/src/pages/ProfilePage.tsx index 310d77cf8391..a8e4223c0180 100755 --- a/src/pages/ProfilePage.tsx +++ b/src/pages/ProfilePage.tsx @@ -25,6 +25,7 @@ import Navigation from '@libs/Navigation/Navigation'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import {parsePhoneNumber} from '@libs/PhoneNumber'; import * as ReportUtils from '@libs/ReportUtils'; +import * as UserUtils from '@libs/UserUtils'; import * as ValidationUtils from '@libs/ValidationUtils'; import type {ProfileNavigatorParamList} from '@navigation/types'; import * as PersonalDetailsActions from '@userActions/PersonalDetails'; @@ -95,6 +96,8 @@ function ProfilePage({route}: ProfilePageProps) { const details: PersonalDetails | EmptyObject = personalDetails?.[accountID] ?? (ValidationUtils.isValidAccountRoute(accountID) ? {} : {accountID: 0, avatar: ''}); const displayName = PersonalDetailsUtils.getDisplayNameOrDefault(details, undefined, undefined, isCurrentUser); + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + const avatar = details?.avatar || UserUtils.getDefaultAvatar(); // we can have an empty string and in this case, we need to show the default avatar const fallbackIcon = details?.fallbackIcon ?? ''; const login = details?.login ?? ''; const timezone = details?.timezone; @@ -161,10 +164,9 @@ function ProfilePage({route}: ProfilePageProps) { diff --git a/src/pages/ReportParticipantDetailsPage.tsx b/src/pages/ReportParticipantDetailsPage.tsx index 563f24635759..2b1411641faa 100644 --- a/src/pages/ReportParticipantDetailsPage.tsx +++ b/src/pages/ReportParticipantDetailsPage.tsx @@ -18,6 +18,7 @@ import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import * as Report from '@libs/actions/Report'; import * as ReportUtils from '@libs/ReportUtils'; +import * as UserUtils from '@libs/UserUtils'; import Navigation from '@navigation/Navigation'; import type {ParticipantsNavigatorParamList} from '@navigation/types'; import CONST from '@src/CONST'; @@ -50,7 +51,7 @@ function ReportParticipantDetails({personalDetails, report, route}: ReportPartic const member = report?.participants?.[accountID]; const details = personalDetails?.[accountID] ?? ({} as PersonalDetails); - const avatar = details.avatar; + const avatar = details.avatar ?? UserUtils.getDefaultAvatar(); const fallbackIcon = details.fallbackIcon ?? ''; const displayName = details.displayName ?? ''; const isCurrentUserAdmin = ReportUtils.isGroupChatAdmin(report, currentUserPersonalDetails?.accountID); @@ -80,8 +81,7 @@ function ReportParticipantDetails({personalDetails, report, route}: ReportPartic diff --git a/src/pages/ReportParticipantsPage.tsx b/src/pages/ReportParticipantsPage.tsx index 995dc3045bda..9c0e19e85ee4 100755 --- a/src/pages/ReportParticipantsPage.tsx +++ b/src/pages/ReportParticipantsPage.tsx @@ -13,7 +13,6 @@ import type {DropdownOption, WorkspaceMemberBulkActionType} from '@components/Bu import ConfirmModal from '@components/ConfirmModal'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import * as Expensicons from '@components/Icon/Expensicons'; -import {FallbackAvatar} from '@components/Icon/Expensicons'; import ScreenWrapper from '@components/ScreenWrapper'; import SelectionList from '@components/SelectionList'; import TableListItem from '@components/SelectionList/TableListItem'; @@ -28,6 +27,7 @@ import Log from '@libs/Log'; import Navigation from '@libs/Navigation/Navigation'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import * as ReportUtils from '@libs/ReportUtils'; +import * as UserUtils from '@libs/UserUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -99,7 +99,7 @@ function ReportParticipantsPage({report, personalDetails, session}: ReportPartic pendingAction: pendingChatMember?.pendingAction, icons: [ { - source: details.avatar ?? FallbackAvatar, + source: UserUtils.getAvatar(details?.avatar, accountID), name: formatPhoneNumber(details?.login ?? ''), type: CONST.ICON_TYPE_AVATAR, id: accountID, diff --git a/src/pages/RoomMembersPage.tsx b/src/pages/RoomMembersPage.tsx index abd99774d9e0..488bde658c3f 100644 --- a/src/pages/RoomMembersPage.tsx +++ b/src/pages/RoomMembersPage.tsx @@ -8,7 +8,6 @@ import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView import Button from '@components/Button'; import ConfirmModal from '@components/ConfirmModal'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; -import {FallbackAvatar} from '@components/Icon/Expensicons'; import {usePersonalDetails} from '@components/OnyxProvider'; import ScreenWrapper from '@components/ScreenWrapper'; import SelectionList from '@components/SelectionList'; @@ -27,6 +26,7 @@ import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import * as PolicyUtils from '@libs/PolicyUtils'; import * as ReportUtils from '@libs/ReportUtils'; +import * as UserUtils from '@libs/UserUtils'; import * as Report from '@userActions/Report'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -209,7 +209,7 @@ function RoomMembersPage({report, session, policies}: RoomMembersPageProps) { alternateText: details?.login ? formatPhoneNumber(details.login) : '', icons: [ { - source: details.avatar ?? FallbackAvatar, + source: UserUtils.getAvatar(details.avatar, accountID), name: details.login ?? '', type: CONST.ICON_TYPE_AVATAR, id: Number(accountID), diff --git a/src/pages/home/report/ReactionList/BaseReactionList.tsx b/src/pages/home/report/ReactionList/BaseReactionList.tsx index 23417c1395df..6f56f8f09632 100755 --- a/src/pages/home/report/ReactionList/BaseReactionList.tsx +++ b/src/pages/home/report/ReactionList/BaseReactionList.tsx @@ -2,11 +2,11 @@ import Str from 'expensify-common/lib/str'; import React from 'react'; import {FlatList} from 'react-native'; import type {FlatListProps} from 'react-native'; -import {FallbackAvatar} from '@components/Icon/Expensicons'; import OptionRow from '@components/OptionRow'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import Navigation from '@libs/Navigation/Navigation'; +import * as UserUtils from '@libs/UserUtils'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; @@ -71,7 +71,7 @@ function BaseReactionList({hasUserReacted = false, users, isVisible = false, emo icons: [ { id: item.accountID, - source: item.avatar ?? FallbackAvatar, + source: UserUtils.getAvatar(item.avatar, item.accountID), name: item.login ?? '', type: CONST.ICON_TYPE_AVATAR, }, diff --git a/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx b/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx index f6c7d91b4efd..05e1163da200 100644 --- a/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx +++ b/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx @@ -5,7 +5,6 @@ import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useRef, import {useOnyx} from 'react-native-onyx'; import type {OnyxCollection} from 'react-native-onyx'; import * as Expensicons from '@components/Icon/Expensicons'; -import {FallbackAvatar} from '@components/Icon/Expensicons'; import type {Mention} from '@components/MentionSuggestions'; import MentionSuggestions from '@components/MentionSuggestions'; import {usePersonalDetails} from '@components/OnyxProvider'; @@ -17,6 +16,7 @@ import * as LoginUtils from '@libs/LoginUtils'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import * as SuggestionsUtils from '@libs/SuggestionUtils'; +import * as UserUtils from '@libs/UserUtils'; import {isValidRoomName} from '@libs/ValidationUtils'; import * as ReportUserActions from '@userActions/Report'; import CONST from '@src/CONST'; @@ -242,10 +242,9 @@ function SuggestionMention( icons: [ { name: detail?.login, - source: detail?.avatar ?? FallbackAvatar, + source: UserUtils.getAvatar(detail?.avatar, detail?.accountID), type: CONST.ICON_TYPE_AVATAR, fallbackIcon: detail?.fallbackIcon, - id: detail?.accountID, }, ], }); diff --git a/src/pages/home/report/ReportActionItemSingle.tsx b/src/pages/home/report/ReportActionItemSingle.tsx index 54b6775cfe13..dda17e1e83d3 100644 --- a/src/pages/home/report/ReportActionItemSingle.tsx +++ b/src/pages/home/report/ReportActionItemSingle.tsx @@ -3,7 +3,6 @@ import type {StyleProp, ViewStyle} from 'react-native'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import Avatar from '@components/Avatar'; -import {FallbackAvatar} from '@components/Icon/Expensicons'; import MultipleAvatars from '@components/MultipleAvatars'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import {usePersonalDetails} from '@components/OnyxProvider'; @@ -20,6 +19,7 @@ import ControlSelection from '@libs/ControlSelection'; import DateUtils from '@libs/DateUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as ReportUtils from '@libs/ReportUtils'; +import * as UserUtils from '@libs/UserUtils'; import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; import type {Report, ReportAction} from '@src/types/onyx'; @@ -86,14 +86,12 @@ function ReportActionItemSingle({ let actorHint = (login || (displayName ?? '')).replace(CONST.REGEX.MERGED_ACCOUNT_PREFIX, ''); const displayAllActors = useMemo(() => action?.actionName === CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW && iouReport, [action?.actionName, iouReport]); const isWorkspaceActor = ReportUtils.isPolicyExpenseChat(report) && (!actorAccountID || displayAllActors); - let avatarSource = avatar; - let avatarAccountId = actorAccountID; + let avatarSource = UserUtils.getAvatar(avatar ?? '', actorAccountID); if (isWorkspaceActor) { displayName = ReportUtils.getPolicyName(report); actorHint = displayName; avatarSource = ReportUtils.getWorkspaceAvatar(report); - avatarAccountId = undefined; } else if (action?.delegateAccountID && personalDetails[action?.delegateAccountID]) { // We replace the actor's email, name, and avatar with the Copilot manually for now. And only if we have their // details. This will be improved upon when the Copilot feature is implemented. @@ -101,8 +99,7 @@ function ReportActionItemSingle({ const delegateDisplayName = delegateDetails?.displayName; actorHint = `${delegateDisplayName} (${translate('reportAction.asCopilot')} ${displayName})`; displayName = actorHint; - avatarSource = delegateDetails?.avatar; - avatarAccountId = action.delegateAccountID; + avatarSource = UserUtils.getAvatar(delegateDetails?.avatar ?? '', Number(action.delegateAccountID)); } // If this is a report preview, display names and avatars of both people involved @@ -115,7 +112,7 @@ function ReportActionItemSingle({ const secondaryDisplayName = ReportUtils.getDisplayNameForParticipant(secondaryAccountId); displayName = `${primaryDisplayName} & ${secondaryDisplayName}`; secondaryAvatar = { - source: secondaryUserAvatar, + source: UserUtils.getAvatar(secondaryUserAvatar, secondaryAccountId), type: CONST.ICON_TYPE_AVATAR, name: secondaryDisplayName ?? '', id: secondaryAccountId, @@ -129,12 +126,11 @@ function ReportActionItemSingle({ } else { secondaryAvatar = {name: '', source: '', type: 'avatar'}; } - const icon = { - source: avatarSource ?? FallbackAvatar, + source: avatarSource, type: isWorkspaceActor ? CONST.ICON_TYPE_WORKSPACE : CONST.ICON_TYPE_AVATAR, name: primaryDisplayName ?? '', - id: avatarAccountId, + id: isWorkspaceActor ? '' : actorAccountID, }; // Since the display name for a report action message is delivered with the report history as an array of fragments @@ -205,7 +201,6 @@ function ReportActionItemSingle({ type={icon.type} name={icon.name} fallbackIcon={fallbackIcon} - accountID={icon.id} /> diff --git a/src/pages/home/sidebar/ProfileAvatarWithIndicator.tsx b/src/pages/home/sidebar/ProfileAvatarWithIndicator.tsx index b0287efb8990..e7726fb89537 100644 --- a/src/pages/home/sidebar/ProfileAvatarWithIndicator.tsx +++ b/src/pages/home/sidebar/ProfileAvatarWithIndicator.tsx @@ -5,6 +5,7 @@ import AvatarWithIndicator from '@components/AvatarWithIndicator'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useThemeStyles from '@hooks/useThemeStyles'; +import * as UserUtils from '@libs/UserUtils'; import ONYXKEYS from '@src/ONYXKEYS'; type ProfileAvatarWithIndicatorProps = { @@ -22,8 +23,7 @@ function ProfileAvatarWithIndicator({isSelected = false}: ProfileAvatarWithIndic diff --git a/src/pages/workspace/WorkspaceMembersPage.tsx b/src/pages/workspace/WorkspaceMembersPage.tsx index b18522662dde..30d92363afcd 100644 --- a/src/pages/workspace/WorkspaceMembersPage.tsx +++ b/src/pages/workspace/WorkspaceMembersPage.tsx @@ -14,7 +14,6 @@ import type {DropdownOption, WorkspaceMemberBulkActionType} from '@components/Bu import ConfirmModal from '@components/ConfirmModal'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import * as Expensicons from '@components/Icon/Expensicons'; -import {FallbackAvatar} from '@components/Icon/Expensicons'; import * as Illustrations from '@components/Icon/Illustrations'; import MessagesRow from '@components/MessagesRow'; import ScreenWrapper from '@components/ScreenWrapper'; @@ -37,6 +36,7 @@ import type {WorkspacesCentralPaneNavigatorParamList} from '@libs/Navigation/typ import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import * as PolicyUtils from '@libs/PolicyUtils'; +import * as UserUtils from '@libs/UserUtils'; import * as Policy from '@userActions/Policy'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -353,7 +353,7 @@ function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, rightElement: roleBadge, icons: [ { - source: details.avatar ?? FallbackAvatar, + source: UserUtils.getAvatar(details.avatar, accountID), name: formatPhoneNumber(details?.login ?? ''), type: CONST.ICON_TYPE_AVATAR, id: accountID, diff --git a/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx b/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx index 17d5f56d1dbf..32b43a230619 100644 --- a/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx +++ b/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx @@ -18,6 +18,7 @@ import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; +import * as UserUtils from '@libs/UserUtils'; import Navigation from '@navigation/Navigation'; import type {SettingsNavigatorParamList} from '@navigation/types'; import AdminPolicyAccessOrNotFoundWrapper from '@pages/workspace/AdminPolicyAccessOrNotFoundWrapper'; @@ -57,6 +58,7 @@ function WorkspaceMemberDetailsPage({personalDetails, policy, route}: WorkspaceM const memberLogin = personalDetails?.[accountID]?.login ?? ''; const member = policy?.employeeList?.[memberLogin]; const details = personalDetails?.[accountID] ?? ({} as PersonalDetails); + const avatar = details.avatar ?? UserUtils.getDefaultAvatar(); const fallbackIcon = details.fallbackIcon ?? ''; const displayName = details.displayName ?? ''; const isSelectedMemberOwner = policy?.owner === details.login; @@ -140,10 +142,9 @@ function WorkspaceMemberDetailsPage({personalDetails, policy, route}: WorkspaceM {Boolean(details.displayName ?? '') && ( diff --git a/src/pages/workspace/workflows/WorkspaceWorkflowsApproverPage.tsx b/src/pages/workspace/workflows/WorkspaceWorkflowsApproverPage.tsx index 8be9afd790d8..74434fbdd1f8 100644 --- a/src/pages/workspace/workflows/WorkspaceWorkflowsApproverPage.tsx +++ b/src/pages/workspace/workflows/WorkspaceWorkflowsApproverPage.tsx @@ -5,7 +5,6 @@ import type {OnyxEntry} from 'react-native-onyx'; import Badge from '@components/Badge'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; -import {FallbackAvatar} from '@components/Icon/Expensicons'; import ScreenWrapper from '@components/ScreenWrapper'; import SelectionList from '@components/SelectionList'; import type {ListItem, Section} from '@components/SelectionList/types'; @@ -19,6 +18,7 @@ import type {WorkspacesCentralPaneNavigatorParamList} from '@libs/Navigation/typ import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import * as PolicyUtils from '@libs/PolicyUtils'; +import * as UserUtils from '@libs/UserUtils'; import FeatureEnabledAccessOrNotFoundWrapper from '@pages/workspace/FeatureEnabledAccessOrNotFoundWrapper'; import withPolicyAndFullscreenLoading from '@pages/workspace/withPolicyAndFullscreenLoading'; import type {WithPolicyAndFullscreenLoadingProps} from '@pages/workspace/withPolicyAndFullscreenLoading'; @@ -87,7 +87,7 @@ function WorkspaceWorkflowsApproverPage({policy, personalDetails, isLoadingRepor rightElement: roleBadge, icons: [ { - source: details.avatar ?? FallbackAvatar, + source: UserUtils.getAvatar(details.avatar, accountID), name: formatPhoneNumber(details?.login ?? ''), type: CONST.ICON_TYPE_AVATAR, id: accountID, diff --git a/src/pages/workspace/workflows/WorkspaceWorkflowsPayerPage.tsx b/src/pages/workspace/workflows/WorkspaceWorkflowsPayerPage.tsx index 6a6d64d51eb1..3bd4ab9003c5 100644 --- a/src/pages/workspace/workflows/WorkspaceWorkflowsPayerPage.tsx +++ b/src/pages/workspace/workflows/WorkspaceWorkflowsPayerPage.tsx @@ -5,7 +5,6 @@ import type {OnyxEntry} from 'react-native-onyx'; import Badge from '@components/Badge'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; -import {FallbackAvatar} from '@components/Icon/Expensicons'; import ScreenWrapper from '@components/ScreenWrapper'; import SelectionList from '@components/SelectionList'; import type {ListItem, Section} from '@components/SelectionList/types'; @@ -18,6 +17,7 @@ import Navigation from '@libs/Navigation/Navigation'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import * as PolicyUtils from '@libs/PolicyUtils'; +import * as UserUtils from '@libs/UserUtils'; import type {SettingsNavigatorParamList} from '@navigation/types'; import AdminPolicyAccessOrNotFoundWrapper from '@pages/workspace/AdminPolicyAccessOrNotFoundWrapper'; import PaidPolicyAccessOrNotFoundWrapper from '@pages/workspace/PaidPolicyAccessOrNotFoundWrapper'; @@ -88,7 +88,7 @@ function WorkspaceWorkflowsPayerPage({route, policy, personalDetails, isLoadingR rightElement: roleBadge, icons: [ { - source: details?.avatar ?? FallbackAvatar, + source: UserUtils.getAvatar(details?.avatar, accountID), name: formatPhoneNumber(details?.login ?? ''), type: CONST.ICON_TYPE_AVATAR, id: accountID, diff --git a/src/types/onyx/OnyxCommon.ts b/src/types/onyx/OnyxCommon.ts index 9ac596a85777..8b96a89a2a1b 100644 --- a/src/types/onyx/OnyxCommon.ts +++ b/src/types/onyx/OnyxCommon.ts @@ -34,7 +34,7 @@ type Icon = { name?: string; /** Avatar id */ - id?: number; + id?: number | string; /** A fallback avatar icon to display when there is an error on loading avatar from remote URL. */ fallbackIcon?: AvatarSource;