Skip to content

Commit

Permalink
Merge pull request Expensify#27778 from software-mansion-labs/ts-migr…
Browse files Browse the repository at this point in the history
…ation/UserUtils

[No QA][TS migration] Migrate 'UserUtils.js' lib to TypeScript
  • Loading branch information
aldo-expensify authored Oct 2, 2023
2 parents 09e3d0d + d6b6fe2 commit 2d07520
Showing 1 changed file with 45 additions and 70 deletions.
115 changes: 45 additions & 70 deletions src/libs/UserUtils.js → src/libs/UserUtils.ts
Original file line number Diff line number Diff line change
@@ -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<typeof CONST.BRICK_ROAD_INDICATOR_STATUS> | '';

/**
* Searches through given loginList for any contact method / login with an error.
Expand All @@ -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;
}
Expand All @@ -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<SvgProps> {
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;
}
Expand All @@ -115,64 +103,57 @@ 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;
}

/**
* 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<SvgProps> | string {
return isDefaultAvatar(avatarURL) ? getDefaultAvatar(accountID) : avatarURL;
}

/**
* 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<SvgProps> | string {
const source = getAvatar(avatarURL, accountID);
if (!_.isString(source)) {
if (typeof source !== 'string') {
return source;
}
return source.replace('_128', '');
Expand All @@ -181,14 +162,10 @@ function getFullSizeAvatar(avatarURL, accountID) {
/**
* Small sized avatars end with _128.<file-type>. 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<SvgProps> | string {
const source = getAvatar(avatarURL, accountID);
if (!_.isString(source)) {
if (typeof source !== 'string') {
return source;
}

Expand All @@ -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);
}

Expand Down

0 comments on commit 2d07520

Please sign in to comment.