diff --git a/src/libs/UserUtils.js b/src/libs/UserUtils.ts similarity index 63% rename from src/libs/UserUtils.js rename to src/libs/UserUtils.ts index 2d5930b0dfd8..751ca5b69609 100644 --- a/src/libs/UserUtils.js +++ b/src/libs/UserUtils.ts @@ -1,9 +1,14 @@ -import _ from 'underscore'; -import lodashGet from 'lodash/get'; +import {SvgProps} from 'react-native-svg'; +import {ValueOf} from 'type-fest'; import CONST from '../CONST'; import hashCode from './hashCode'; -import * as Expensicons from '../components/Icon/Expensicons'; +import {ConciergeAvatar, FallbackAvatar} from '../components/Icon/Expensicons'; import * as defaultAvatars from '../components/Icon/DefaultAvatars'; +import Login from '../types/onyx/Login'; + +type AvatarRange = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24; + +type LoginListIndicator = ValueOf | ''; /** * Searches through given loginList for any contact method / login with an error. @@ -25,36 +30,26 @@ import * as defaultAvatars from '../components/Icon/DefaultAvatars'; * } * } * }} - * - * @param {Object} loginList - * @param {Object} loginList.errorFields - * @returns {Boolean} */ -function hasLoginListError(loginList) { - return _.some(loginList, (login) => _.some(lodashGet(login, 'errorFields', {}), (field) => !_.isEmpty(field))); +function hasLoginListError(loginList: Login): boolean { + const errorFields = loginList?.errorFields ?? {}; + return Object.values(errorFields).some((field) => Object.keys(field ?? {}).length > 0); } /** * Searches through given loginList for any contact method / login that requires * an Info brick road status indicator. Currently this only applies if the user * has an unvalidated contact method. - * - * @param {Object} loginList - * @param {String} loginList.validatedDate - * @returns {Boolean} */ -function hasLoginListInfo(loginList) { - return _.some(loginList, (login) => _.isEmpty(login.validatedDate)); +function hasLoginListInfo(loginList: Login): boolean { + return !loginList.validatedDate; } /** * Gets the appropriate brick road indicator status for a given loginList. * Error status is higher priority, so we check for that first. - * - * @param {Object} loginList - * @returns {String} */ -function getLoginListBrickRoadIndicator(loginList) { +function getLoginListBrickRoadIndicator(loginList: Login): LoginListIndicator { if (hasLoginListError(loginList)) { return CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR; } @@ -66,42 +61,35 @@ function getLoginListBrickRoadIndicator(loginList) { /** * Hashes provided string and returns a value between [0, range) - * @param {String} text - * @param {Number} range - * @returns {Number} */ -function hashText(text, range) { +function hashText(text: string, range: number): number { return Math.abs(hashCode(text.toLowerCase())) % range; } /** * Helper method to return the default avatar associated with the given accountID - * @param {Number} [accountID] - * @returns {String} + * @param [accountID] + * @returns */ -function getDefaultAvatar(accountID = -1) { +function getDefaultAvatar(accountID = -1): React.FC { if (accountID <= 0) { - return Expensicons.FallbackAvatar; + return FallbackAvatar; } if (Number(accountID) === CONST.ACCOUNT_ID.CONCIERGE) { - return Expensicons.ConciergeAvatar; + return ConciergeAvatar; } // There are 24 possible default avatars, so we choose which one this user has based // on a simple modulo operation of their login number. Note that Avatar count starts at 1. - const accountIDHashBucket = (accountID % CONST.DEFAULT_AVATAR_COUNT) + 1; + const accountIDHashBucket = ((accountID % CONST.DEFAULT_AVATAR_COUNT) + 1) as AvatarRange; return defaultAvatars[`Avatar${accountIDHashBucket}`]; } /** * Helper method to return default avatar URL associated with login - * - * @param {Number} [accountID] - * @param {Boolean} [isNewDot] - * @returns {String} */ -function getDefaultAvatarURL(accountID = '', isNewDot = false) { +function getDefaultAvatarURL(accountID: string | number = '', isNewDot = false): string { if (Number(accountID) === CONST.ACCOUNT_ID.CONCIERGE) { return CONST.CONCIERGE_ICON_URL; } @@ -115,26 +103,25 @@ function getDefaultAvatarURL(accountID = '', isNewDot = false) { /** * Given a user's avatar path, returns true if user doesn't have an avatar or if URL points to a default avatar - * @param {String} [avatarURL] - the avatar source from user's personalDetails - * @returns {Boolean} + * @param [avatarURL] - the avatar source from user's personalDetails */ -function isDefaultAvatar(avatarURL) { - if ( - _.isString(avatarURL) && - (avatarURL.includes('images/avatars/avatar_') || avatarURL.includes('images/avatars/default-avatar_') || avatarURL.includes('images/avatars/user/default')) - ) { - return true; - } - - // We use a hardcoded "default" Concierge avatar - if (_.isString(avatarURL) && (avatarURL === CONST.CONCIERGE_ICON_URL_2021 || avatarURL === CONST.CONCIERGE_ICON_URL)) { - return true; +function isDefaultAvatar(avatarURL?: string): boolean { + if (typeof avatarURL === 'string') { + if (avatarURL.includes('images/avatars/avatar_') || avatarURL.includes('images/avatars/default-avatar_') || avatarURL.includes('images/avatars/user/default')) { + return true; + } + + // We use a hardcoded "default" Concierge avatar + if (avatarURL === CONST.CONCIERGE_ICON_URL_2021 || avatarURL === CONST.CONCIERGE_ICON_URL) { + return true; + } } if (!avatarURL) { // If null URL, we should also use a default avatar return true; } + return false; } @@ -142,11 +129,10 @@ function isDefaultAvatar(avatarURL) { * Provided a source URL, if source is a default avatar, return the associated SVG. * Otherwise, return the URL pointing to a user-uploaded avatar. * - * @param {String} avatarURL - the avatar source from user's personalDetails - * @param {Number} accountID - the accountID of the user - * @returns {String|Function} + * @param avatarURL - the avatar source from user's personalDetails + * @param accountID - the accountID of the user */ -function getAvatar(avatarURL, accountID) { +function getAvatar(avatarURL: string, accountID: number): React.FC | string { return isDefaultAvatar(avatarURL) ? getDefaultAvatar(accountID) : avatarURL; } @@ -154,25 +140,20 @@ function getAvatar(avatarURL, accountID) { * 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 {String} avatarURL - the avatar source from user's personalDetails - * @param {Number} accountID - the accountID of the user - * @returns {String} + * @param avatarURL - the avatar source from user's personalDetails + * @param accountID - the accountID of the user */ -function getAvatarUrl(avatarURL, accountID) { +function getAvatarUrl(avatarURL: string, accountID: number): string { return isDefaultAvatar(avatarURL) ? getDefaultAvatarURL(accountID, true) : avatarURL; } /** * 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. - * - * @param {String} [avatarURL] - * @param {Number} [accountID] - * @returns {String|Function} */ -function getFullSizeAvatar(avatarURL, accountID) { +function getFullSizeAvatar(avatarURL: string, accountID: number): React.FC | string { const source = getAvatar(avatarURL, accountID); - if (!_.isString(source)) { + if (typeof source !== 'string') { return source; } return source.replace('_128', ''); @@ -181,14 +162,10 @@ function getFullSizeAvatar(avatarURL, accountID) { /** * 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. - * - * @param {String} avatarURL - * @param {Number} accountID - * @returns {String|Function} */ -function getSmallSizeAvatar(avatarURL, accountID) { +function getSmallSizeAvatar(avatarURL: string, accountID: number): React.FC | string { const source = getAvatar(avatarURL, accountID); - if (!_.isString(source)) { + if (typeof source !== 'string') { return source; } @@ -207,10 +184,8 @@ function getSmallSizeAvatar(avatarURL, accountID) { /** * Generate a random accountID base on searchValue. - * @param {String} searchValue - * @returns {Number} */ -function generateAccountID(searchValue) { +function generateAccountID(searchValue: string): number { return hashText(searchValue, 2 ** 32); }