From 306063bb6782308a6442b2fb50dba3a2648b210a Mon Sep 17 00:00:00 2001 From: Situ Chandra Shil <108292595+situchan@users.noreply.github.com> Date: Sun, 2 Nov 2025 18:53:59 +1100 Subject: [PATCH] Revert "Feat/custom avatar polish" --- src/components/Avatar.tsx | 11 +---- src/hooks/useAvatarMenu.ts | 33 ++++++-------- src/libs/Navigation/types.ts | 2 - src/libs/UserUtils.ts | 30 ++----------- src/libs/actions/PersonalDetails.ts | 1 - .../routes/ProfileAvatarModalContent.tsx | 6 +-- .../settings/Profile/Avatar/AvatarPage.tsx | 43 +++++++++++-------- 7 files changed, 47 insertions(+), 79 deletions(-) diff --git a/src/components/Avatar.tsx b/src/components/Avatar.tsx index 48dc1e1ce5ae1..657d433066279 100644 --- a/src/components/Avatar.tsx +++ b/src/components/Avatar.tsx @@ -5,10 +5,9 @@ import useNetwork from '@hooks/useNetwork'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; -import {getAvatarLocal} from '@libs/Avatars/CustomAvatarCatalog'; import {getDefaultWorkspaceAvatar, getDefaultWorkspaceAvatarTestID} from '@libs/ReportUtils'; import type {AvatarSource} from '@libs/UserUtils'; -import {getAvatar, getDefaultAvatarNameFromURL} from '@libs/UserUtils'; +import {getAvatar} from '@libs/UserUtils'; import type {AvatarSizeName} from '@styles/utils'; import CONST from '@src/CONST'; import type {AvatarType} from '@src/types/onyx/OnyxCommon'; @@ -88,16 +87,10 @@ function Avatar({ const userAccountID = isWorkspace ? undefined : (avatarID as number); const source = isWorkspace ? originalSource : getAvatar(originalSource, userAccountID); - let optimizedSource = source; - const maybeDefaultAvatarName = getDefaultAvatarNameFromURL(source); - - if (maybeDefaultAvatarName) { - optimizedSource = getAvatarLocal(maybeDefaultAvatarName); - } const useFallBackAvatar = imageError || !source || source === Expensicons.FallbackAvatar; const fallbackAvatar = isWorkspace ? getDefaultWorkspaceAvatar(name) : fallbackIcon || Expensicons.FallbackAvatar; const fallbackAvatarTestID = isWorkspace ? getDefaultWorkspaceAvatarTestID(name) : fallbackIconTestID || 'SvgFallbackAvatar Icon'; - const avatarSource = useFallBackAvatar ? fallbackAvatar : optimizedSource; + const avatarSource = useFallBackAvatar ? fallbackAvatar : source; // We pass the color styles down to the SVG for the workspace and fallback avatar. const iconSize = StyleUtils.getAvatarSize(size); diff --git a/src/hooks/useAvatarMenu.ts b/src/hooks/useAvatarMenu.ts index c82cde00f2dc2..fa53973385c04 100644 --- a/src/hooks/useAvatarMenu.ts +++ b/src/hooks/useAvatarMenu.ts @@ -1,8 +1,7 @@ -import {useCallback, useContext} from 'react'; +import {useCallback} from 'react'; import type {DropdownOption} from '@components/ButtonWithDropdownMenu/types'; import * as Expensicons from '@components/Icon/Expensicons'; import Navigation from '@libs/Navigation/Navigation'; -import AttachmentModalContext from '@pages/media/AttachmentModalScreen/AttachmentModalContext'; import ROUTES from '@src/ROUTES'; import type {FileObject} from '@src/types/utils/Attachment'; import useLocalize from './useLocalize'; @@ -12,10 +11,8 @@ type OpenPicker = (options: {onPicked: (files: FileObject[]) => void}) => void; type UseAvatarMenuParams = { /** Whether the user is using a default avatar */ isUsingDefaultAvatar: boolean; - /** Source of newly uploaded avatar */ - source?: string; - /** File name of newly uploaded avatar */ - originalFileName?: string; + /** Whether the user has chosen a new avatar in the form but hasn't uploaded it yet */ + isAvatarSelected: boolean; /** Account ID for navigation */ accountID: number; /** Callback when avatar is removed */ @@ -29,9 +26,8 @@ type UseAvatarMenuParams = { /** * Custom hook to create avatar menu items */ -function useAvatarMenu({isUsingDefaultAvatar, accountID, onImageRemoved, showAvatarCropModal, clearError, source, originalFileName}: UseAvatarMenuParams) { +function useAvatarMenu({isUsingDefaultAvatar, isAvatarSelected, accountID, onImageRemoved, showAvatarCropModal, clearError}: UseAvatarMenuParams) { const {translate} = useLocalize(); - const attachmentContext = useContext(AttachmentModalContext); /** * Create menu items list for avatar menu @@ -51,11 +47,13 @@ function useAvatarMenu({isUsingDefaultAvatar, accountID, onImageRemoved, showAva }, ]; // If current avatar is a default avatar and for avatar is selected in the form, only show upload option - if (isUsingDefaultAvatar) { + if (isUsingDefaultAvatar || isAvatarSelected) { return menuItems; } - if (!source) { - menuItems.push({ + + return [ + ...menuItems, + { icon: Expensicons.Trashcan, text: translate('avatarWithImagePicker.removePhoto'), value: null, @@ -63,23 +61,16 @@ function useAvatarMenu({isUsingDefaultAvatar, accountID, onImageRemoved, showAva clearError(); onImageRemoved(); }, - }); - } - - return [ - ...menuItems, + }, { value: null, icon: Expensicons.Eye, text: translate('avatarWithImagePicker.viewPhoto'), - onSelected: () => { - attachmentContext.setCurrentAttachment({source, originalFileName}); - Navigation.navigate(ROUTES.PROFILE_AVATAR.getRoute(accountID)); - }, + onSelected: () => Navigation.navigate(ROUTES.PROFILE_AVATAR.getRoute(accountID)), }, ]; }, - [translate, isUsingDefaultAvatar, source, showAvatarCropModal, clearError, onImageRemoved, attachmentContext, originalFileName, accountID], + [accountID, isUsingDefaultAvatar, onImageRemoved, showAvatarCropModal, translate, clearError, isAvatarSelected], ); return {createMenuItems}; diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 60348b78e4eaf..8e41ba3e4f303 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -2467,8 +2467,6 @@ type AttachmentModalScreensParamList = { }; [SCREENS.PROFILE_AVATAR]: AttachmentModalContainerModalProps & { accountID: number; - source?: AvatarSource; - originalFileName?: string; // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md backTo?: Routes; }; diff --git a/src/libs/UserUtils.ts b/src/libs/UserUtils.ts index be773bcfd5d90..28e3df4d7686d 100644 --- a/src/libs/UserUtils.ts +++ b/src/libs/UserUtils.ts @@ -180,14 +180,14 @@ function getDefaultAvatarURL(accountID: number = CONST.DEFAULT_NUMBER_ID, accoun * @param avatarURL - the URL returned by getDefaultAvatarURL * @returns the avatar name (e.g., 'default-avatar_5', 'concierge') or undefined if not a valid default avatar URL */ -function getDefaultAvatarNameFromURL(avatarURL?: AvatarSource): CustomAvatarID | undefined { +function getDefaultAvatarNameFromURL(avatarURL?: AvatarSource): string | undefined { if (!avatarURL || typeof avatarURL !== 'string' || avatarURL === CONST.CONCIERGE_ICON_URL) { return undefined; } // Extract avatar name from CloudFront URL and make sure it's one of defaults - const match = (avatarURL.split('/').at(-1)?.split('.')?.[0] ?? '') as CustomAvatarID; - if (ALL_CUSTOM_AVATARS[match]) { + const match = avatarURL.split('/').at(-1)?.split('.')?.[0] ?? ''; + if (ALL_CUSTOM_AVATARS[match as CustomAvatarID]) { return match; } } @@ -211,25 +211,6 @@ function isDefaultAvatar(avatarSource?: AvatarSource): avatarSource is string | return false; } -/** - * * Given a user's avatar path and originalFileName, returns true if URL points to a default avatar, false otherwise - * @param avatarSource - the avatar source from user's personalDetails - * @param originalFileName - the avatar original file name from user's personalDetails - */ -function isDefaultOrCustomDefaultAvatar(avatarSource?: AvatarSource, originalFileName?: string): boolean { - if ( - (typeof avatarSource === 'string' && avatarSource.includes('images/avatars/custom-avatars')) || // F1 avatars - (originalFileName && /^letter-avatar-#[0-9A-F]{6}-#[0-9A-F]{6}-[A-Z]\.png$/.test(originalFileName)) // Letter avatars - ) { - return true; - } - if (isDefaultAvatar(avatarSource)) { - return true; - } - - return false; -} - /** * Provided an avatar source, if source is a default avatar, return the associated SVG. * Otherwise, return the URL or SVG pointing to the user-uploaded avatar. @@ -276,7 +257,7 @@ function getSmallSizeAvatar(avatarSource?: AvatarSource, accountID?: number, acc } const maybeDefaultAvatarName = getDefaultAvatarNameFromURL(avatarSource); if (maybeDefaultAvatarName) { - return getAvatarLocal(maybeDefaultAvatarName); + return getAvatarLocal(maybeDefaultAvatarName as CustomAvatarID); } // Because other urls than CloudFront do not support dynamic image sizing (_SIZE suffix), the current source is already what we want to use here. @@ -363,8 +344,6 @@ export { generateAccountID, getAvatar, getAvatarUrl, - getDefaultAvatarName, - getDefaultAvatarNameFromURL, getDefaultAvatarURL, getFullSizeAvatar, getLoginListBrickRoadIndicator, @@ -374,7 +353,6 @@ export { hasLoginListError, hasLoginListInfo, hashText, - isDefaultOrCustomDefaultAvatar, isDefaultAvatar, getContactMethod, isCurrentUserValidated, diff --git a/src/libs/actions/PersonalDetails.ts b/src/libs/actions/PersonalDetails.ts index 3a13dd35dc73c..e7db6df6ae02e 100644 --- a/src/libs/actions/PersonalDetails.ts +++ b/src/libs/actions/PersonalDetails.ts @@ -432,7 +432,6 @@ function updateAvatar( API.write(WRITE_COMMANDS.UPDATE_USER_AVATAR, parameters, {optimisticData, successData, failureData}); } -// TODO remove when no longer needed /** * Replaces the user's avatar image with a default avatar */ diff --git a/src/pages/media/AttachmentModalScreen/routes/ProfileAvatarModalContent.tsx b/src/pages/media/AttachmentModalScreen/routes/ProfileAvatarModalContent.tsx index fe24e9f2438ac..36f89298ddbd7 100644 --- a/src/pages/media/AttachmentModalScreen/routes/ProfileAvatarModalContent.tsx +++ b/src/pages/media/AttachmentModalScreen/routes/ProfileAvatarModalContent.tsx @@ -14,7 +14,7 @@ import type SCREENS from '@src/SCREENS'; import useDownloadAttachment from './hooks/useDownloadAttachment'; function ProfileAvatarModalContent({navigation, route}: AttachmentModalScreenProps) { - const {accountID = CONST.DEFAULT_NUMBER_ID, source: tempSource, originalFileName: tempOriginalFileName} = route.params; + const {accountID = CONST.DEFAULT_NUMBER_ID} = route.params; const {formatPhoneNumber} = useLocalize(); @@ -34,8 +34,8 @@ function ProfileAvatarModalContent({navigation, route}: AttachmentModalScreenPro openPublicProfilePage(accountID); }, [accountID]); - const source = tempSource ?? getFullSizeAvatar(avatarURL, accountID); - const originalFileName = tempOriginalFileName ?? personalDetail?.originalFileName ?? ''; + const source = getFullSizeAvatar(avatarURL, accountID); + const originalFileName = personalDetail?.originalFileName ?? ''; const headerTitle = formatPhoneNumber(displayName); // eslint-disable-next-line rulesdir/no-negated-variables const shouldShowNotFoundPage = !avatarURL; diff --git a/src/pages/settings/Profile/Avatar/AvatarPage.tsx b/src/pages/settings/Profile/Avatar/AvatarPage.tsx index 571f746decd11..67a5ffa5dc42c 100644 --- a/src/pages/settings/Profile/Avatar/AvatarPage.tsx +++ b/src/pages/settings/Profile/Avatar/AvatarPage.tsx @@ -22,9 +22,9 @@ import {validateAvatarImage} from '@libs/AvatarUtils'; import type {CustomRNImageManipulatorResult} from '@libs/cropOrRotateImage/types'; import Navigation from '@libs/Navigation/Navigation'; import type {AvatarSource} from '@libs/UserUtils'; -import {getDefaultAvatarName, isDefaultOrCustomDefaultAvatar} from '@libs/UserUtils'; +import {isDefaultAvatar} from '@libs/UserUtils'; import DiscardChangesConfirmation from '@pages/iou/request/step/DiscardChangesConfirmation'; -import {updateAvatar} from '@userActions/PersonalDetails'; +import {deleteAvatar, updateAvatar} from '@userActions/PersonalDetails'; import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; import type {FileObject} from '@src/types/utils/Attachment'; @@ -77,8 +77,7 @@ function ProfileAvatar() { } else { avatarURL = currentUserPersonalDetails?.avatar ?? ''; } - - const isUsingDefaultAvatar = (!imageData.uri && isDefaultOrCustomDefaultAvatar(currentUserPersonalDetails?.avatar, currentUserPersonalDetails?.originalFileName)) || !!selected; + const isUsingDefaultAvatar = isDefaultAvatar(currentUserPersonalDetails?.avatar ?? ''); const setError = (error: TranslationPaths | null, phraseParam: Record) => { setErrorData({ @@ -124,22 +123,33 @@ function ProfileAvatar() { }, []); const onImageRemoved = useCallback(() => { - setSelected(getDefaultAvatarName(currentUserPersonalDetails?.accountID, currentUserPersonalDetails?.email)); + if (isDirty) { + setSelected(undefined); + setImageData({...EMPTY_FILE}); + return; + } + deleteAvatar({ + avatar: currentUserPersonalDetails?.avatar, + fallbackIcon: currentUserPersonalDetails?.fallbackIcon, + accountID: currentUserPersonalDetails?.accountID, + email: currentUserPersonalDetails?.email, + }); + setSelected(undefined); setImageData({...EMPTY_FILE}); - }, [currentUserPersonalDetails?.accountID, currentUserPersonalDetails?.email]); + Navigation.dismissModal(); + }, [currentUserPersonalDetails, isDirty]); const clearError = useCallback(() => { setError(null, {}); }, []); const {createMenuItems} = useAvatarMenu({ + isAvatarSelected: isDirty, isUsingDefaultAvatar, accountID, onImageRemoved, showAvatarCropModal, clearError, - source: imageData.uri, - originalFileName: imageData.name, }); const onPress = useCallback(() => { @@ -187,7 +197,6 @@ function ProfileAvatar() { accountID: currentUserPersonalDetails?.accountID, }); setSelected(undefined); - setImageData({...EMPTY_FILE}); Navigation.dismissModal(); isSavingRef.current = false; }); @@ -272,17 +281,17 @@ function ProfileAvatar() { setSelected(id); }} /> + {!!errorData.validationError && ( + + )} - {!!errorData.validationError && ( - - )}