From fd857e72213f76804c836e1a8901f705de907b5d Mon Sep 17 00:00:00 2001 From: Getabalew Tesfaye Date: Wed, 11 Oct 2023 20:11:05 +0300 Subject: [PATCH 001/130] Feat: update the report when a payment is refunded --- src/CONST.ts | 4 ++++ src/components/ReportActionItem/MoneyRequestPreview.js | 2 ++ src/languages/en.ts | 3 +++ src/languages/es.ts | 3 +++ src/languages/types.ts | 3 +++ src/pages/home/report/ReportActionItem.js | 6 ++++++ 6 files changed, 21 insertions(+) diff --git a/src/CONST.ts b/src/CONST.ts index 23957827d140..91942313fa47 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -486,6 +486,7 @@ const CONST = { IOU: 'IOU', MODIFIEDEXPENSE: 'MODIFIEDEXPENSE', REIMBURSEMENTQUEUED: 'REIMBURSEMENTQUEUED', + REIMBURSEMENTDEQUEUED: 'REIMBURSEMENTDEQUEUED', RENAMED: 'RENAMED', REPORTPREVIEW: 'REPORTPREVIEW', SUBMITTED: 'SUBMITTED', @@ -1118,6 +1119,9 @@ const CONST = { DOCX: 'docx', SVG: 'svg', }, + CANCEL_REASON: { + PAYMENT_EXPIRED: 'CANCEL_REASON_PAYMENT_EXPIRED' + }, }, GROWL: { diff --git a/src/components/ReportActionItem/MoneyRequestPreview.js b/src/components/ReportActionItem/MoneyRequestPreview.js index 35215cadd15d..783c52c0553b 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview.js +++ b/src/components/ReportActionItem/MoneyRequestPreview.js @@ -206,6 +206,8 @@ function MoneyRequestPreview(props) { message += ` • ${props.translate('iou.pending')}`; } else if (ReportUtils.isSettled(props.iouReport.reportID)) { message += ` • ${props.translate('iou.settledExpensify')}`; + } else if(props.iouReport.originalMessage.reason === CONST.IOU.CANCEL_REASON.PAYMENT_EXPIRED) { + message += ` • ${props.translate('iou.canceled')}`; } return message; }; diff --git a/src/languages/en.ts b/src/languages/en.ts index 7133ed88579e..3f47db2ecffd 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -76,6 +76,7 @@ import type { TagSelectionParams, TranslationBase, WalletProgramParams, + CanceledRequestParams, } from './types'; import * as ReportActionsUtils from '../libs/ReportActionsUtils'; @@ -522,6 +523,7 @@ export default { pay: 'Pay', viewDetails: 'View details', pending: 'Pending', + canceled: 'Canceled', deleteReceipt: 'Delete receipt', receiptScanning: 'Receipt scan in progress…', receiptMissingDetails: 'Receipt missing details', @@ -545,6 +547,7 @@ export default { managerApproved: ({manager}: ManagerApprovedParams) => `${manager} approved:`, payerSettled: ({amount}: PayerSettledParams) => `paid ${amount}`, waitingOnBankAccount: ({submitterDisplayName}: WaitingOnBankAccountParams) => `started settling up, payment is held until ${submitterDisplayName} adds a bank account`, + canceledRequest: ({amount, submitterDisplayName}: CanceledRequestParams) => `Canceled the ${amount} payment, because ${submitterDisplayName} did not enable their Expensify Wallet within 30 days`, settledAfterAddedBankAccount: ({submitterDisplayName, amount}: SettledAfterAddedBankAccountParams) => `${submitterDisplayName} added a bank account. The ${amount} payment has been made.`, paidElsewhereWithAmount: ({payer, amount}: PaidElsewhereWithAmountParams) => `${payer} paid ${amount} elsewhere`, diff --git a/src/languages/es.ts b/src/languages/es.ts index a98ddfaff7d0..db8e0d7c43c7 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -76,6 +76,7 @@ import type { TagSelectionParams, EnglishTranslation, WalletProgramParams, + CanceledRequestParams, } from './types'; /* eslint-disable max-len */ @@ -514,6 +515,7 @@ export default { pay: 'Pagar', viewDetails: 'Ver detalles', pending: 'Pendiente', + canceled: 'Canceled', deleteReceipt: 'Eliminar recibo', receiptScanning: 'Escaneo de recibo en curso…', receiptMissingDetails: 'Recibo con campos vacíos', @@ -537,6 +539,7 @@ export default { managerApproved: ({manager}: ManagerApprovedParams) => `${manager} aprobó:`, payerSettled: ({amount}: PayerSettledParams) => `pagó ${amount}`, waitingOnBankAccount: ({submitterDisplayName}: WaitingOnBankAccountParams) => `inicio el pago, pero no se procesará hasta que ${submitterDisplayName} añada una cuenta bancaria`, + canceledRequest: ({amount, submitterDisplayName}: CanceledRequestParams) => `Canceled the ${amount} payment, because ${submitterDisplayName} did not enable their Expensify Wallet within 30 days`, settledAfterAddedBankAccount: ({submitterDisplayName, amount}: SettledAfterAddedBankAccountParams) => `${submitterDisplayName} añadió una cuenta bancaria. El pago de ${amount} se ha realizado.`, paidElsewhereWithAmount: ({payer, amount}: PaidElsewhereWithAmountParams) => `${payer} pagó ${amount} de otra forma`, diff --git a/src/languages/types.ts b/src/languages/types.ts index 3ee504ccddd7..b3ce20500f85 100644 --- a/src/languages/types.ts +++ b/src/languages/types.ts @@ -122,6 +122,8 @@ type PayerSettledParams = {amount: number}; type WaitingOnBankAccountParams = {submitterDisplayName: string}; +type CanceledRequestParams = {amount: string, submitterDisplayName: string} + type SettledAfterAddedBankAccountParams = {submitterDisplayName: string; amount: string}; type PaidElsewhereWithAmountParams = {payer: string; amount: string}; @@ -277,6 +279,7 @@ export type { ManagerApprovedParams, PayerSettledParams, WaitingOnBankAccountParams, + CanceledRequestParams, SettledAfterAddedBankAccountParams, PaidElsewhereWithAmountParams, PaidWithExpensifyWithAmountParams, diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index d0e84499a443..36c8476df976 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -70,6 +70,7 @@ import themeColors from '../../../styles/themes/default'; import ReportActionItemBasicMessage from './ReportActionItemBasicMessage'; import RenderHTML from '../../../components/RenderHTML'; import ReportAttachmentsContext from './ReportAttachmentsContext'; +import * as CurrencyUtils from "../../../libs/CurrencyUtils"; const propTypes = { ...windowDimensionsPropTypes, @@ -361,6 +362,11 @@ function ReportActionItem(props) { ) : null} ); + } else if(props.action.actionName === CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENTDEQUEUED) { + const submitterDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(props.personalDetailsList, [props.iouReport.ownerAccountID, 'displayName'], props.iouReport.ownerEmail); + const amount = CurrencyUtils.convertToDisplayString(props.iouReport.total, props.iouReport.currency); + + children = ; } else if (props.action.actionName === CONST.REPORT.ACTIONS.TYPE.MODIFIEDEXPENSE) { children = ; } else { From 2b33b90d6da6399626bcaafac99dbaee4592a1b3 Mon Sep 17 00:00:00 2001 From: Getabalew Tesfaye Date: Wed, 11 Oct 2023 20:49:27 +0300 Subject: [PATCH 002/130] run prettier --- src/CONST.ts | 2 +- src/components/ReportActionItem/MoneyRequestPreview.js | 2 +- src/languages/en.ts | 3 ++- src/languages/es.ts | 3 ++- src/languages/types.ts | 2 +- src/pages/home/report/ReportActionItem.js | 4 ++-- 6 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 91942313fa47..308d83766d93 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1120,7 +1120,7 @@ const CONST = { SVG: 'svg', }, CANCEL_REASON: { - PAYMENT_EXPIRED: 'CANCEL_REASON_PAYMENT_EXPIRED' + PAYMENT_EXPIRED: 'CANCEL_REASON_PAYMENT_EXPIRED', }, }, diff --git a/src/components/ReportActionItem/MoneyRequestPreview.js b/src/components/ReportActionItem/MoneyRequestPreview.js index 783c52c0553b..a408041dbf5c 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview.js +++ b/src/components/ReportActionItem/MoneyRequestPreview.js @@ -206,7 +206,7 @@ function MoneyRequestPreview(props) { message += ` • ${props.translate('iou.pending')}`; } else if (ReportUtils.isSettled(props.iouReport.reportID)) { message += ` • ${props.translate('iou.settledExpensify')}`; - } else if(props.iouReport.originalMessage.reason === CONST.IOU.CANCEL_REASON.PAYMENT_EXPIRED) { + } else if (props.iouReport.originalMessage.reason === CONST.IOU.CANCEL_REASON.PAYMENT_EXPIRED) { message += ` • ${props.translate('iou.canceled')}`; } return message; diff --git a/src/languages/en.ts b/src/languages/en.ts index 3f47db2ecffd..e5ab33014b4a 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -547,7 +547,8 @@ export default { managerApproved: ({manager}: ManagerApprovedParams) => `${manager} approved:`, payerSettled: ({amount}: PayerSettledParams) => `paid ${amount}`, waitingOnBankAccount: ({submitterDisplayName}: WaitingOnBankAccountParams) => `started settling up, payment is held until ${submitterDisplayName} adds a bank account`, - canceledRequest: ({amount, submitterDisplayName}: CanceledRequestParams) => `Canceled the ${amount} payment, because ${submitterDisplayName} did not enable their Expensify Wallet within 30 days`, + canceledRequest: ({amount, submitterDisplayName}: CanceledRequestParams) => + `Canceled the ${amount} payment, because ${submitterDisplayName} did not enable their Expensify Wallet within 30 days`, settledAfterAddedBankAccount: ({submitterDisplayName, amount}: SettledAfterAddedBankAccountParams) => `${submitterDisplayName} added a bank account. The ${amount} payment has been made.`, paidElsewhereWithAmount: ({payer, amount}: PaidElsewhereWithAmountParams) => `${payer} paid ${amount} elsewhere`, diff --git a/src/languages/es.ts b/src/languages/es.ts index db8e0d7c43c7..c88d6fb290ac 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -539,7 +539,8 @@ export default { managerApproved: ({manager}: ManagerApprovedParams) => `${manager} aprobó:`, payerSettled: ({amount}: PayerSettledParams) => `pagó ${amount}`, waitingOnBankAccount: ({submitterDisplayName}: WaitingOnBankAccountParams) => `inicio el pago, pero no se procesará hasta que ${submitterDisplayName} añada una cuenta bancaria`, - canceledRequest: ({amount, submitterDisplayName}: CanceledRequestParams) => `Canceled the ${amount} payment, because ${submitterDisplayName} did not enable their Expensify Wallet within 30 days`, + canceledRequest: ({amount, submitterDisplayName}: CanceledRequestParams) => + `Canceled the ${amount} payment, because ${submitterDisplayName} did not enable their Expensify Wallet within 30 days`, settledAfterAddedBankAccount: ({submitterDisplayName, amount}: SettledAfterAddedBankAccountParams) => `${submitterDisplayName} añadió una cuenta bancaria. El pago de ${amount} se ha realizado.`, paidElsewhereWithAmount: ({payer, amount}: PaidElsewhereWithAmountParams) => `${payer} pagó ${amount} de otra forma`, diff --git a/src/languages/types.ts b/src/languages/types.ts index b3ce20500f85..4ba148a7869d 100644 --- a/src/languages/types.ts +++ b/src/languages/types.ts @@ -122,7 +122,7 @@ type PayerSettledParams = {amount: number}; type WaitingOnBankAccountParams = {submitterDisplayName: string}; -type CanceledRequestParams = {amount: string, submitterDisplayName: string} +type CanceledRequestParams = {amount: string; submitterDisplayName: string}; type SettledAfterAddedBankAccountParams = {submitterDisplayName: string; amount: string}; diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 36c8476df976..5c52670fd37e 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -70,7 +70,7 @@ import themeColors from '../../../styles/themes/default'; import ReportActionItemBasicMessage from './ReportActionItemBasicMessage'; import RenderHTML from '../../../components/RenderHTML'; import ReportAttachmentsContext from './ReportAttachmentsContext'; -import * as CurrencyUtils from "../../../libs/CurrencyUtils"; +import * as CurrencyUtils from '../../../libs/CurrencyUtils'; const propTypes = { ...windowDimensionsPropTypes, @@ -362,7 +362,7 @@ function ReportActionItem(props) { ) : null} ); - } else if(props.action.actionName === CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENTDEQUEUED) { + } else if (props.action.actionName === CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENTDEQUEUED) { const submitterDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(props.personalDetailsList, [props.iouReport.ownerAccountID, 'displayName'], props.iouReport.ownerEmail); const amount = CurrencyUtils.convertToDisplayString(props.iouReport.total, props.iouReport.currency); From 0aac629c63aede00570c19eef26e3275812c0b35 Mon Sep 17 00:00:00 2001 From: Getabalew Tesfaye Date: Wed, 11 Oct 2023 23:44:42 +0300 Subject: [PATCH 003/130] prevent crash by checking key existence --- src/components/ReportActionItem/MoneyRequestPreview.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestPreview.js b/src/components/ReportActionItem/MoneyRequestPreview.js index a408041dbf5c..fc294c3ffeda 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview.js +++ b/src/components/ReportActionItem/MoneyRequestPreview.js @@ -206,7 +206,7 @@ function MoneyRequestPreview(props) { message += ` • ${props.translate('iou.pending')}`; } else if (ReportUtils.isSettled(props.iouReport.reportID)) { message += ` • ${props.translate('iou.settledExpensify')}`; - } else if (props.iouReport.originalMessage.reason === CONST.IOU.CANCEL_REASON.PAYMENT_EXPIRED) { + } else if (props.iouReport.originalMessage && props.iouReport.originalMessage.reason === CONST.IOU.CANCEL_REASON.PAYMENT_EXPIRED) { message += ` • ${props.translate('iou.canceled')}`; } return message; From b7986d4975e834a3642655634bf9884b66285fcb Mon Sep 17 00:00:00 2001 From: Getabalew Tesfaye Date: Wed, 11 Oct 2023 23:57:24 +0300 Subject: [PATCH 004/130] Add es translations --- src/languages/es.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/languages/es.ts b/src/languages/es.ts index 500cc529304c..d0e5efa00c12 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -519,7 +519,7 @@ export default { pay: 'Pagar', viewDetails: 'Ver detalles', pending: 'Pendiente', - canceled: 'Canceled', + canceled: 'Canceló', posted: 'Contabilizado', deleteReceipt: 'Eliminar recibo', receiptScanning: 'Escaneo de recibo en curso…', @@ -545,7 +545,7 @@ export default { payerSettled: ({amount}: PayerSettledParams) => `pagó ${amount}`, waitingOnBankAccount: ({submitterDisplayName}: WaitingOnBankAccountParams) => `inicio el pago, pero no se procesará hasta que ${submitterDisplayName} añada una cuenta bancaria`, canceledRequest: ({amount, submitterDisplayName}: CanceledRequestParams) => - `Canceled the ${amount} payment, because ${submitterDisplayName} did not enable their Expensify Wallet within 30 days`, + `Canceló el pago ${amount}, porque ${submitterDisplayName} no habilitó su billetera Expensify en un plazo de 30 días.`, settledAfterAddedBankAccount: ({submitterDisplayName, amount}: SettledAfterAddedBankAccountParams) => `${submitterDisplayName} añadió una cuenta bancaria. El pago de ${amount} se ha realizado.`, paidElsewhereWithAmount: ({payer, amount}: PaidElsewhereWithAmountParams) => `${payer} pagó ${amount} de otra forma`, From 444b732a645942f6fbc91d94ea2ec58f83d49f5a Mon Sep 17 00:00:00 2001 From: Getabalew Tesfaye Date: Thu, 12 Oct 2023 00:17:24 +0300 Subject: [PATCH 005/130] use report action value --- src/components/ReportActionItem/MoneyRequestPreview.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestPreview.js b/src/components/ReportActionItem/MoneyRequestPreview.js index aa19c8536446..b557325e8505 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview.js +++ b/src/components/ReportActionItem/MoneyRequestPreview.js @@ -216,7 +216,7 @@ function MoneyRequestPreview(props) { message += ` • ${props.translate('iou.approved')}`; } else if (props.iouReport.isWaitingOnBankAccount) { message += ` • ${props.translate('iou.pending')}`; - } else if (props.iouReport.originalMessage && props.iouReport.originalMessage.reason === CONST.IOU.CANCEL_REASON.PAYMENT_EXPIRED) { + } else if (props.action.originalMessage && props.action.originalMessage.reason === CONST.IOU.CANCEL_REASON.PAYMENT_EXPIRED) { message += ` • ${props.translate('iou.canceled')}`; } return message; From 99d4202ea5fd271146a47f77b4459b1498a9a885 Mon Sep 17 00:00:00 2001 From: Getabalew Tesfaye Date: Tue, 17 Oct 2023 17:44:47 +0300 Subject: [PATCH 006/130] use props.report value for amount and name --- src/pages/home/report/ReportActionItem.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index f51b0645cd7f..45621297ed98 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -363,8 +363,8 @@ function ReportActionItem(props) { ); } else if (props.action.actionName === CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENTDEQUEUED) { - const submitterDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(props.personalDetailsList, [props.iouReport.ownerAccountID, 'displayName'], props.iouReport.ownerEmail); - const amount = CurrencyUtils.convertToDisplayString(props.iouReport.total, props.iouReport.currency); + const submitterDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(props.personalDetailsList, [props.report.ownerAccountID, 'displayName'], props.report.ownerEmail); + const amount = CurrencyUtils.convertToDisplayString(props.report.total, props.report.currency); children = ; } else if (props.action.actionName === CONST.REPORT.ACTIONS.TYPE.MODIFIEDEXPENSE) { From b389a83bf0ddf0379780722a5360afb4e64c4b89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Tue, 24 Oct 2023 08:43:03 -0300 Subject: [PATCH 007/130] Rename AvatarWithIndicator to TSX --- .../{AvatarWithIndicator.js => AvatarWithIndicator.tsx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/components/{AvatarWithIndicator.js => AvatarWithIndicator.tsx} (100%) diff --git a/src/components/AvatarWithIndicator.js b/src/components/AvatarWithIndicator.tsx similarity index 100% rename from src/components/AvatarWithIndicator.js rename to src/components/AvatarWithIndicator.tsx From 18b8449e060243355c22510694531a57cc64d0dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Tue, 24 Oct 2023 08:47:02 -0300 Subject: [PATCH 008/130] Migrate AvatarWithIndicator to TS --- src/components/AvatarWithIndicator.tsx | 36 ++++++++++++-------------- src/libs/UserUtils.ts | 6 ++--- 2 files changed, 19 insertions(+), 23 deletions(-) diff --git a/src/components/AvatarWithIndicator.tsx b/src/components/AvatarWithIndicator.tsx index 5e7b8d1ee632..c29507b9dcc5 100644 --- a/src/components/AvatarWithIndicator.tsx +++ b/src/components/AvatarWithIndicator.tsx @@ -1,36 +1,34 @@ import React from 'react'; import {View} from 'react-native'; -import PropTypes from 'prop-types'; -import Avatar from './Avatar'; -import styles from '../styles/styles'; -import Tooltip from './Tooltip'; +import {SvgProps} from 'react-native-svg'; import * as UserUtils from '../libs/UserUtils'; -import Indicator from './Indicator'; +import styles from '../styles/styles'; +import Avatar from './Avatar'; import * as Expensicons from './Icon/Expensicons'; +import Indicator from './Indicator'; +import Tooltip from './Tooltip'; -const propTypes = { +type AvatarWithIndicatorProps = { /** URL for the avatar */ - source: PropTypes.oneOfType([PropTypes.string, PropTypes.func]).isRequired, + source: string | React.FC; /** To show a tooltip on hover */ - tooltipText: PropTypes.string, + tooltipText?: string; /** A fallback avatar icon to display when there is an error on loading avatar from remote URL. */ - fallbackIcon: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), -}; - -const defaultProps = { - tooltipText: '', - fallbackIcon: Expensicons.FallbackAvatar, + fallbackIcon?: string | React.FC; }; -function AvatarWithIndicator(props) { +function AvatarWithIndicator({source, tooltipText = '', fallbackIcon = Expensicons.FallbackAvatar}: AvatarWithIndicatorProps) { return ( - + @@ -38,8 +36,6 @@ function AvatarWithIndicator(props) { ); } -AvatarWithIndicator.defaultProps = defaultProps; -AvatarWithIndicator.propTypes = propTypes; AvatarWithIndicator.displayName = 'AvatarWithIndicator'; export default AvatarWithIndicator; diff --git a/src/libs/UserUtils.ts b/src/libs/UserUtils.ts index 15bf3c0f1029..572c4597b514 100644 --- a/src/libs/UserUtils.ts +++ b/src/libs/UserUtils.ts @@ -104,7 +104,7 @@ function getDefaultAvatarURL(accountID: string | number = '', 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 [avatarURL] - the avatar source from user's personalDetails */ -function isDefaultAvatar(avatarURL?: string): boolean { +function isDefaultAvatar(avatarURL?: React.FC | 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; @@ -131,7 +131,7 @@ function isDefaultAvatar(avatarURL?: string): boolean { * @param avatarURL - the avatar source from user's personalDetails * @param accountID - the accountID of the user */ -function getAvatar(avatarURL: string, accountID: number): React.FC | string { +function getAvatar(avatarURL: React.FC | string, accountID?: number): React.FC | string { return isDefaultAvatar(avatarURL) ? getDefaultAvatar(accountID) : avatarURL; } @@ -162,7 +162,7 @@ function getFullSizeAvatar(avatarURL: string, accountID: number): React.FC. This adds the _128 at the end of the * source URL (before the file type) if it doesn't exist there already. */ -function getSmallSizeAvatar(avatarURL: string, accountID: number): React.FC | string { +function getSmallSizeAvatar(avatarURL: React.FC | string, accountID?: number): React.FC | string { const source = getAvatar(avatarURL, accountID); if (typeof source !== 'string') { return source; From cebcf98619b38ebeabcf3a75c23c171a8e75836f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Wed, 25 Oct 2023 06:23:36 -0300 Subject: [PATCH 009/130] Rename params to better name --- src/libs/UserUtils.ts | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/libs/UserUtils.ts b/src/libs/UserUtils.ts index 572c4597b514..50fc95c8416a 100644 --- a/src/libs/UserUtils.ts +++ b/src/libs/UserUtils.ts @@ -102,21 +102,21 @@ function getDefaultAvatarURL(accountID: string | number = '', 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 [avatarURL] - the avatar source from user's personalDetails + * @param [avatarSource] - the avatar source from user's personalDetails */ -function isDefaultAvatar(avatarURL?: React.FC | string): boolean { - if (typeof avatarURL === 'string') { - if (avatarURL.includes('images/avatars/avatar_') || avatarURL.includes('images/avatars/default-avatar_') || avatarURL.includes('images/avatars/user/default')) { +function isDefaultAvatar(avatarSource?: React.FC | string): boolean { + if (typeof avatarSource === 'string') { + if (avatarSource.includes('images/avatars/avatar_') || avatarSource.includes('images/avatars/default-avatar_') || avatarSource.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) { + if (avatarSource === CONST.CONCIERGE_ICON_URL_2021 || avatarSource === CONST.CONCIERGE_ICON_URL) { return true; } } - if (!avatarURL) { + if (!avatarSource) { // If null URL, we should also use a default avatar return true; } @@ -128,11 +128,11 @@ function isDefaultAvatar(avatarURL?: React.FC | string): boolean { * 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 avatarURL - the avatar source from user's personalDetails + * @param avatarSource - the avatar source from user's personalDetails * @param accountID - the accountID of the user */ -function getAvatar(avatarURL: React.FC | string, accountID?: number): React.FC | string { - return isDefaultAvatar(avatarURL) ? getDefaultAvatar(accountID) : avatarURL; +function getAvatar(avatarSource: React.FC | string, accountID?: number): React.FC | string { + return isDefaultAvatar(avatarSource) ? getDefaultAvatar(accountID) : avatarSource; } /** @@ -150,8 +150,8 @@ function getAvatarUrl(avatarURL: string, accountID: number): string { * 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(avatarURL: string, accountID: number): React.FC | string { - const source = getAvatar(avatarURL, accountID); +function getFullSizeAvatar(avatarSource: React.FC | string, accountID: number): React.FC | string { + const source = getAvatar(avatarSource, accountID); if (typeof source !== 'string') { return source; } @@ -162,8 +162,8 @@ function getFullSizeAvatar(avatarURL: string, accountID: number): React.FC. This adds the _128 at the end of the * source URL (before the file type) if it doesn't exist there already. */ -function getSmallSizeAvatar(avatarURL: React.FC | string, accountID?: number): React.FC | string { - const source = getAvatar(avatarURL, accountID); +function getSmallSizeAvatar(avatarSource: React.FC | string, accountID?: number): React.FC | string { + const source = getAvatar(avatarSource, accountID); if (typeof source !== 'string') { return source; } From 8e34982d458f2d6a7f115a1dc58c1960c6093ad2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Wed, 25 Oct 2023 10:38:52 -0300 Subject: [PATCH 010/130] Create AvatarSource type --- src/components/AvatarWithIndicator.tsx | 6 +++--- src/libs/UserUtils.ts | 11 +++++++---- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/components/AvatarWithIndicator.tsx b/src/components/AvatarWithIndicator.tsx index c29507b9dcc5..e5100f084bd0 100644 --- a/src/components/AvatarWithIndicator.tsx +++ b/src/components/AvatarWithIndicator.tsx @@ -1,7 +1,7 @@ import React from 'react'; import {View} from 'react-native'; -import {SvgProps} from 'react-native-svg'; import * as UserUtils from '../libs/UserUtils'; +import {AvatarSource} from '../libs/UserUtils'; import styles from '../styles/styles'; import Avatar from './Avatar'; import * as Expensicons from './Icon/Expensicons'; @@ -10,13 +10,13 @@ import Tooltip from './Tooltip'; type AvatarWithIndicatorProps = { /** URL for the avatar */ - source: string | React.FC; + source: AvatarSource; /** To show a tooltip on hover */ tooltipText?: string; /** A fallback avatar icon to display when there is an error on loading avatar from remote URL. */ - fallbackIcon?: string | React.FC; + fallbackIcon?: AvatarSource; }; function AvatarWithIndicator({source, tooltipText = '', fallbackIcon = Expensicons.FallbackAvatar}: AvatarWithIndicatorProps) { diff --git a/src/libs/UserUtils.ts b/src/libs/UserUtils.ts index 50fc95c8416a..6d068b89305f 100644 --- a/src/libs/UserUtils.ts +++ b/src/libs/UserUtils.ts @@ -8,6 +8,8 @@ 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 AvatarSource = React.FC | string; + type LoginListIndicator = ValueOf | ''; /** @@ -104,7 +106,7 @@ function getDefaultAvatarURL(accountID: string | number = '', 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 [avatarSource] - the avatar source from user's personalDetails */ -function isDefaultAvatar(avatarSource?: React.FC | string): boolean { +function isDefaultAvatar(avatarSource?: AvatarSource): boolean { if (typeof avatarSource === 'string') { if (avatarSource.includes('images/avatars/avatar_') || avatarSource.includes('images/avatars/default-avatar_') || avatarSource.includes('images/avatars/user/default')) { return true; @@ -131,7 +133,7 @@ function isDefaultAvatar(avatarSource?: React.FC | string): boolean { * @param avatarSource - the avatar source from user's personalDetails * @param accountID - the accountID of the user */ -function getAvatar(avatarSource: React.FC | string, accountID?: number): React.FC | string { +function getAvatar(avatarSource: AvatarSource, accountID?: number): AvatarSource { return isDefaultAvatar(avatarSource) ? getDefaultAvatar(accountID) : avatarSource; } @@ -150,7 +152,7 @@ function getAvatarUrl(avatarURL: string, accountID: number): string { * 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: React.FC | string, accountID: number): React.FC | string { +function getFullSizeAvatar(avatarSource: AvatarSource, accountID: number): AvatarSource { const source = getAvatar(avatarSource, accountID); if (typeof source !== 'string') { return source; @@ -162,7 +164,7 @@ function getFullSizeAvatar(avatarSource: React.FC | string, 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. */ -function getSmallSizeAvatar(avatarSource: React.FC | string, accountID?: number): React.FC | string { +function getSmallSizeAvatar(avatarSource: AvatarSource, accountID?: number): AvatarSource { const source = getAvatar(avatarSource, accountID); if (typeof source !== 'string') { return source; @@ -202,3 +204,4 @@ export { getFullSizeAvatar, generateAccountID, }; +export type {AvatarSource}; From bb077b6853ac71cb443529a0f5423477c91dcc99 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Wed, 25 Oct 2023 16:44:49 +0200 Subject: [PATCH 011/130] Rename files --- src/components/{TestToolMenu.js => TestToolMenu.tsx} | 0 src/components/{TestToolsModal.js => TestToolsModal.tsx} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename src/components/{TestToolMenu.js => TestToolMenu.tsx} (100%) rename src/components/{TestToolsModal.js => TestToolsModal.tsx} (100%) diff --git a/src/components/TestToolMenu.js b/src/components/TestToolMenu.tsx similarity index 100% rename from src/components/TestToolMenu.js rename to src/components/TestToolMenu.tsx diff --git a/src/components/TestToolsModal.js b/src/components/TestToolsModal.tsx similarity index 100% rename from src/components/TestToolsModal.js rename to src/components/TestToolsModal.tsx From 8bff041e052fa5fd5e449a3857b8c963b16d03e6 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Wed, 25 Oct 2023 17:11:12 +0200 Subject: [PATCH 012/130] Migrate TestToolMenu and TestToolsModal --- src/components/TestToolMenu.tsx | 55 ++++++++++++------------------- src/components/TestToolsModal.tsx | 29 ++++------------ 2 files changed, 28 insertions(+), 56 deletions(-) diff --git a/src/components/TestToolMenu.tsx b/src/components/TestToolMenu.tsx index 474e4c9bb10c..496c90fe9341 100644 --- a/src/components/TestToolMenu.tsx +++ b/src/components/TestToolMenu.tsx @@ -1,7 +1,5 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import {withOnyx} from 'react-native-onyx'; -import lodashGet from 'lodash/get'; +import {OnyxEntry, withOnyx} from 'react-native-onyx'; import styles from '../styles/styles'; import Switch from './Switch'; import Text from './Text'; @@ -11,40 +9,31 @@ import * as Session from '../libs/actions/Session'; import ONYXKEYS from '../ONYXKEYS'; import Button from './Button'; import TestToolRow from './TestToolRow'; -import networkPropTypes from './networkPropTypes'; import compose from '../libs/compose'; import {withNetwork} from './OnyxProvider'; import * as ApiUtils from '../libs/ApiUtils'; import CONFIG from '../CONFIG'; +import NetworkOnyx from '../types/onyx/Network'; +import UserOnyx from '../types/onyx/User'; -const propTypes = { +type TestToolMenuOnyxProps = { /** User object in Onyx */ - user: PropTypes.shape({ - /** Whether we should use the staging version of the secure API server */ - shouldUseStagingServer: PropTypes.bool, - }), + user: OnyxEntry; +}; +type TestToolMenuProps = TestToolMenuOnyxProps & { /** Network object in Onyx */ - network: networkPropTypes.isRequired, + network: OnyxEntry; }; -const defaultProps = { - user: { - // The default value is environment specific and can't be set with `defaultProps` (ENV is not resolved yet) - // When undefined (during render) STAGING defaults to `true`, other envs default to `false` - shouldUseStagingServer: undefined, - }, -}; +const USER_DEFAULT = {shouldUseStagingServer: undefined, isSubscribedToNewsletter: false, validated: false, isFromPublicDomain: false, isUsingExpensifyCard: false}; + +function TestToolMenu({user = USER_DEFAULT, network}: TestToolMenuProps) { + const shouldUseStagingServer = user?.shouldUseStagingServer ?? ApiUtils.isUsingStagingApi(); -function TestToolMenu(props) { return ( <> - - Test Preferences - + Test Preferences {/* Option to switch between staging and default api endpoints. This enables QA, internal testers and external devs to take advantage of sandbox environments for 3rd party services like Plaid and Onfido. @@ -53,8 +42,8 @@ function TestToolMenu(props) { User.setShouldUseStagingServer(!lodashGet(props, 'user.shouldUseStagingServer', ApiUtils.isUsingStagingApi()))} + isOn={shouldUseStagingServer} + onToggle={() => User.setShouldUseStagingServer(!shouldUseStagingServer)} /> )} @@ -63,8 +52,8 @@ function TestToolMenu(props) { Network.setShouldForceOffline(!props.network.shouldForceOffline)} + isOn={Boolean(network?.shouldForceOffline)} + onToggle={() => Network.setShouldForceOffline(!network?.shouldForceOffline)} /> @@ -72,8 +61,8 @@ function TestToolMenu(props) { Network.setShouldFailAllRequests(!props.network.shouldFailAllRequests)} + isOn={Boolean(network?.shouldFailAllRequests)} + onToggle={() => Network.setShouldFailAllRequests(!network?.shouldFailAllRequests)} /> @@ -98,15 +87,13 @@ function TestToolMenu(props) { ); } -TestToolMenu.propTypes = propTypes; -TestToolMenu.defaultProps = defaultProps; TestToolMenu.displayName = 'TestToolMenu'; export default compose( - withNetwork(), - withOnyx({ + withOnyx({ user: { key: ONYXKEYS.USER, }, }), + withNetwork(), )(TestToolMenu); diff --git a/src/components/TestToolsModal.tsx b/src/components/TestToolsModal.tsx index 43a74e48df5d..238db2379f01 100644 --- a/src/components/TestToolsModal.tsx +++ b/src/components/TestToolsModal.tsx @@ -1,6 +1,5 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import {withOnyx} from 'react-native-onyx'; +import {OnyxEntry, withOnyx} from 'react-native-onyx'; import {View} from 'react-native'; import ONYXKEYS from '../ONYXKEYS'; import Modal from './Modal'; @@ -9,26 +8,17 @@ import toggleTestToolsModal from '../libs/actions/TestTool'; import TestToolMenu from './TestToolMenu'; import styles from '../styles/styles'; -const propTypes = { - /** Details about modal */ - modal: PropTypes.shape({ - /** Indicates when an Alert modal is about to be visible */ - willAlertModalBecomeVisible: PropTypes.bool, - }), - +type TestToolsModalOnyxProps = { /** Whether the test tools modal is open */ - isTestToolsModalOpen: PropTypes.bool, + isTestToolsModalOpen: OnyxEntry; }; -const defaultProps = { - modal: {}, - isTestToolsModalOpen: false, -}; +type TestToolsModalProps = TestToolsModalOnyxProps; -function TestToolsModal(props) { +function TestToolsModal({isTestToolsModalOpen = false}: TestToolsModalProps) { return ( @@ -39,14 +29,9 @@ function TestToolsModal(props) { ); } -TestToolsModal.propTypes = propTypes; -TestToolsModal.defaultProps = defaultProps; TestToolsModal.displayName = 'TestToolsModal'; -export default withOnyx({ - modal: { - key: ONYXKEYS.MODAL, - }, +export default withOnyx({ isTestToolsModalOpen: { key: ONYXKEYS.IS_TEST_TOOLS_MODAL_OPEN, }, From 586c1d17d0496a0d0a3a34c6fc695b0f09ca0046 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Fri, 27 Oct 2023 12:31:18 +0200 Subject: [PATCH 013/130] Type Text more accurate --- src/components/TestToolMenu.tsx | 7 ++++++- src/components/Text.tsx | 19 +++---------------- 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/src/components/TestToolMenu.tsx b/src/components/TestToolMenu.tsx index 496c90fe9341..369ba5bb65b9 100644 --- a/src/components/TestToolMenu.tsx +++ b/src/components/TestToolMenu.tsx @@ -33,7 +33,12 @@ function TestToolMenu({user = USER_DEFAULT, network}: TestToolMenuProps) { return ( <> - Test Preferences + + Test Preferences + {/* Option to switch between staging and default api endpoints. This enables QA, internal testers and external devs to take advantage of sandbox environments for 3rd party services like Plaid and Onfido. diff --git a/src/components/Text.tsx b/src/components/Text.tsx index 60a59aae1520..598a01c6a46b 100644 --- a/src/components/Text.tsx +++ b/src/components/Text.tsx @@ -1,12 +1,12 @@ import React, {ForwardedRef} from 'react'; // eslint-disable-next-line no-restricted-imports -import {Text as RNText} from 'react-native'; +import {Text as RNText, TextProps as RNTextProps, StyleSheet} from 'react-native'; import type {TextStyle} from 'react-native'; import fontFamily from '../styles/fontFamily'; import themeColors from '../styles/themes/default'; import variables from '../styles/variables'; -type TextProps = { +type TextProps = RNTextProps & { /** The color of the text */ color?: string; @@ -21,31 +21,18 @@ type TextProps = { /** The family of the font to use */ family?: keyof typeof fontFamily; - - /** Any additional styles to apply */ - style?: TextStyle | TextStyle[]; }; function Text( {color = themeColors.text, fontSize = variables.fontSizeNormal, textAlign = 'left', children = null, family = 'EXP_NEUE', style = {}, ...props}: TextProps, ref: ForwardedRef, ) { - // If the style prop is an array of styles, we need to mix them all together - const mergedStyles = !Array.isArray(style) - ? style - : style.reduce( - (finalStyles, s) => ({ - ...finalStyles, - ...s, - }), - {}, - ); const componentStyle: TextStyle = { color, fontSize, textAlign, fontFamily: fontFamily[family], - ...mergedStyles, + ...StyleSheet.flatten(style), }; if (!componentStyle.lineHeight && componentStyle.fontSize === variables.fontSizeNormal) { From 98380131c8ef28ce6fff549cd370b6937209c2ba Mon Sep 17 00:00:00 2001 From: John Schuster Date: Fri, 27 Oct 2023 15:11:31 -0500 Subject: [PATCH 014/130] Delete docs/articles/new-expensify/getting-started/Expensify-Lounge.md --- .../getting-started/Expensify-Lounge.md | 67 ------------------- 1 file changed, 67 deletions(-) delete mode 100644 docs/articles/new-expensify/getting-started/Expensify-Lounge.md diff --git a/docs/articles/new-expensify/getting-started/Expensify-Lounge.md b/docs/articles/new-expensify/getting-started/Expensify-Lounge.md deleted file mode 100644 index bdccbe927769..000000000000 --- a/docs/articles/new-expensify/getting-started/Expensify-Lounge.md +++ /dev/null @@ -1,67 +0,0 @@ ---- -title: Welcome to the Expensify Lounge! -description: How to get the most out of the Expensify Lounge. -redirect_from: articles/other/Expensify-Lounge/ ---- - - -# What is the Expensify Lounge? -The Expensify Lounge is a place where people go to Get Shit Done. It's a beautiful environment with great coffee and a group of people to collaborate with. Check out this guide on how to best utilize the Expensify Lounge! - -# The Two Rules -### Rule #1 - Get Shit Done - -The Lounge is a space for people to get work done. It is optimized to be the perfect environment for you to focus on your work, collaborate with others, and advance your most wild and creative ideas. To make this a reality, we ask our members to keep the following in mind: - -- **#focus** - Use the space for how it was designed and do not distract from others' focus. The space is beautiful, social, and collaborative, but it was created to help our members work effectively. -- **#urgency** - Working remotely is great, but there's nothing like real-time collaboration with your colleagues. Use the lounge to meet with co-workers IRL to continue the progress on whatever it is you're working on. -- **#results** - Don't mistake time for effort or effort for output. Upon arrival, visualize what you want to accomplish, and don't leave until it's done. - -## Rule #2 - Don’t Ruin it for Everyone Else - -We want this place to be incredible, innovative, and always elvoving. To achieve that, we have some general guidelines: - -- **#writeitdown** - If you can help others learn from you, do so. Write a blog post, a document, or a post in Expensify Chat to share with others. This includes making the Expensify Lounge a better space. Feel free to write down any improvements so we can make it better. -- **#showup** - If you are in the lounge, be fully present. Meet others, and collaborate in social rooms. The point is to build a community of people who are focused on getting shit done; you’ll get out what you put in. -- **#oneteam** - Providing an inclusive community is our priority, and we do not tolerate any form of discrimination. Aim to go out of your way to include people who want to be included. -- **#nocreeps** - Do not make people feel uncomfortable with your words or actions. If you are made to feel uncomfortable or notice this happening to someone else, you can use the escalation process outlined in the FAQ section. - -# How to Use the Expensify Lounge -Keeping those two rules in mind, below is a guide on how our members can get the most out of the lounge. - -### Rule #1 - Getting Shit Done -- **Order drinks from Concierge** - [Write Concierge here](https://new.expensify.com/concierge) to ask lounge questions or order beverages. Concierge will bring your order directly to you! -- **Using an office** - Offices are first come, first serve. If an office is open, feel free to use it! Please keep office use to under an hour. We currently do not allow reserving offices. -- **Lounge hours** - The lounge will be open from 8am-6pm PT, Monday through Friday and closed on some major holidays. You can review our Google Maps profile to check our holiday hours. -- **Make the lounge better** - Make any suggestions to improve the lounge experience in [#announce - Expensify Lounge](https://new.expensify.com/r/8292963527436014). - -## Rule #2 - Not Ruining it for Everyone Else -- **Offices are for calls** - Please do not occupy an office unless you have a call or collaborative meeting happening, and don't stay in an office for longer than an hour. -- **Respect other people** - Please do not be too loud or distracting while others are trying to work. While collaborating in Expensify Chat, be respectful of others’ viewpoints and keep a positive environment. -- **Stay home if you’re sick** - If you feel sick, please do not visit the lounge, or consider wearing a mask in public areas. -- **If you see something, say something** - If you are made to feel uncomfortable or witness others being made uncomfortable, let Concierge know. If this is happening in Expensify Chat, use our moderation tools (outlined below in the FAQ) to apply the applicable level of moderation. - -We’re so happy you are here to live rich, have fun, and save the world with us. Now, go enjoy the Expensify Lounge, and let's Get Shit Done! - -# FAQs - -#### What is Concierge? - -Concierge is our automated system that answers member questions in real-time. Questions regarding the local lounge will be routed directly to the lounge's Concierge. You can send Concierge a message if you have a drink request or general questions. They’ll take care of everything for you! - -#### Who is invited to the Expensify Lounge? - -Everyone is invited to the Expensify Lounge! Whether you're an existing customer, or you're someone looking for a great space to Get Shit Done, we'd love to have you. - -#### How do I escalate something that's making me or someone else uncomfortable? - -If you see something in Expensify Chat that should be escalated, you can use the escalation feature to mark a chat as: -- **Spam or Inconsiderate**: This will send a whisper to the sender of the message warning them of the violation, and the message will have a flag applied to it which will be visible to all users. Concierge will not review these flags. -- **Intimidating or Bullying**: The message will be immediately hidden, and the content will be reviewed by our team. After reviewing the message, and it's confirmed intimidation or bullying, the message will be permanently hidden and we'll communicate the violation to the sender of the message. -- **Harassment or Assault**: The message will be immediately hidden and reviewed by our team. The user will be sent a message to warning them of the violation, and Concierge can block the user if that's deemed necessary. - -If you witness something in-person, please write to Concierge referencing which lounge you are in, and they will escalate the issue appropriately. - -#### Where are other Expensify Lounge locations? - -Right now, we only have the San Francisco Lounge, but be on the lookout for more coming soon! From 84f47089566e6961b6324bcb4d7293367996bd70 Mon Sep 17 00:00:00 2001 From: John Schuster Date: Fri, 27 Oct 2023 15:14:14 -0500 Subject: [PATCH 015/130] Update Expensify-Card-Perks.md --- .../expensify-card/Expensify-Card-Perks.md | 36 +------------------ 1 file changed, 1 insertion(+), 35 deletions(-) diff --git a/docs/articles/expensify-classic/expensify-card/Expensify-Card-Perks.md b/docs/articles/expensify-classic/expensify-card/Expensify-Card-Perks.md index 5c9761b7ff1d..8bcc11fbf167 100644 --- a/docs/articles/expensify-classic/expensify-card/Expensify-Card-Perks.md +++ b/docs/articles/expensify-classic/expensify-card/Expensify-Card-Perks.md @@ -6,22 +6,12 @@ description: Get the most out of your Expensify Card with exclusive perks! # Overview The Expensify Card is packed with perks, both native to our Card program and through exclusive discounts with partnering solutions. The Expensify Card’s primary perks include: -- Access to our premiere Expensify Lounge (with more locations coming soon) - Swipe to Win, where every swipe has a chance to win fun personalized gifts for you and your closest friends and family members -- And unbeatable cash back incentive with each swipe +- Unbeatable cash back incentive with each swipe Below, we’ll cover all of our exclusive offers in more detail and how to claim discounts with our partners. # Expensify Card Perks -## Access to the Expensify Lounge -Our [world-class lounge](https://use.expensify.com/lounge) is now open for Expensify members and guests to enjoy! - -We invite you to visit our sleek San Francisco lounge, where sweeping city views provide the perfect backdrop for a morning coffee to start your day. - -Enjoy complimentary cocktails and snacks in a vibrant atmosphere with blazing-fast WiFi. Whether you want a place to focus on work, socialize with other members, or simply kick back and relax – our lounge is ready and waiting to welcome you. - -You can sign up for free [here](https://use.expensify.com) if you’re not an Expensify member. If you have any questions, reach out to concierge@expensify.com and [check this out](https://use.expensify.com/lounge) for more info. - ## Swipe to Win Swipe to Win is a new [Expensify Card](https://use.expensify.com/company-credit-card) perk that gives cardholders the chance to send a gift to a friend, family member, or essential worker on the frontlines! @@ -221,27 +211,3 @@ Stripe Atlas helps removes obstacles typically associated with starting a busine **Receive $100 off Stripe Atlas and get access to a startup toolkit and special offers on additional Strip Atlas services.** **How to redeem:** Sign up with your Expensify Card. - -# FAQ - -## Where is the Expensify Lounge? -The Expensify Lounge is located on the 16th floor of 88 Kearny Street in San Francisco, California, 94108. This is currently our only lounge location, but keep an eye out for more work lounges popping up soon! - -## When is the Expensify Lounge open? -The lounge is open 8 a.m. to 6 p.m. from Monday through Friday, except for national holidays. Capacity is limited, and we are admitting loungers on a first-come, first-served basis, so make sure to get there early! - -## Who can use the lounge workplace? -Customers with an Expensify subscription can use Expensify’s lounge workplace, and any partner who has completed [ExpensifyApproved! University!](https://university.expensify.com/users/sign_in?next=%2Fdashboard) - - - - -# FAQ -This section covers the useful but not as vital information, it should capture commonly queried elements which do not organically form part of the About or How-to sections. - -- What's idiosyncratic or potentially confusing about this feature? -- Is there anything unique about how this feature relates to billing/activity? -- If this feature is released, are there any common confusions that can't be solved by improvements to the product itself? -- Similarly, if this feature hasn't been released, can you predict and pre-empt any potential confusion? -- Is there any general troubleshooting for this feature? - - Note: troubleshooting should generally go in the FAQ, but if there is extensive troubleshooting, such as with integrations, that will be housed in a separate page, stored with and linked from the main page for that feature. From 9a6375a8ed3a0cfabab3efaa7184ab0f50ad8bf8 Mon Sep 17 00:00:00 2001 From: tienifr Date: Sat, 28 Oct 2023 16:30:31 +0700 Subject: [PATCH 016/130] fix: 30254 --- .../ComposerWithSuggestions.js | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js index 3972b21b91a7..d01d91f206bd 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js @@ -124,6 +124,22 @@ function ComposerWithSuggestions({ const isEmptyChat = useMemo(() => _.size(reportActions) === 1, [reportActions]); const parentAction = ReportActionsUtils.getParentReportAction(report); const shouldAutoFocus = !modal.isVisible && (shouldFocusInputOnScreenFocus || (isEmptyChat && !ReportActionsUtils.isTransactionThread(parentAction))) && shouldShowComposeInput; + const textInputRef = useRef(null); + const shouldAutoFocusRef = useRef(false); + + shouldAutoFocusRef.current = shouldAutoFocus; + + const prevIsFocused = usePrevious(isFocused); + + useEffect(() => { + if (!prevIsFocused && isFocused) { + setTimeout(() => { + if (shouldAutoFocusRef.current) { + textInputRef.current.focus(); + } + }, CONST.ANIMATED_TRANSITION); + } + }, [isFocused, prevIsFocused]); const valueRef = useRef(value); valueRef.current = value; @@ -134,8 +150,7 @@ function ComposerWithSuggestions({ })); const [composerHeight, setComposerHeight] = useState(0); - - const textInputRef = useRef(null); + const insertedEmojisRef = useRef([]); // A flag to indicate whether the onScroll callback is likely triggered by a layout change (caused by text change) or not @@ -478,7 +493,7 @@ function ComposerWithSuggestions({ }, [focusComposerOnKeyPress, navigation, setUpComposeFocusManager]); const prevIsModalVisible = usePrevious(modal.isVisible); - const prevIsFocused = usePrevious(isFocused); + useEffect(() => { if (modal.isVisible && !prevIsModalVisible) { // eslint-disable-next-line no-param-reassign From f2302a6f962c32835d96d4632a82cb0fa98be8ea Mon Sep 17 00:00:00 2001 From: Kamil Owczarz Date: Mon, 6 Nov 2023 17:15:00 +0100 Subject: [PATCH 017/130] Revert "Revert "[Form Provider Refactor] RoomNameInput"" This reverts commit c01af10e --- src/components/Form/FormProvider.js | 24 ++++--- src/components/Form/InputWrapper.js | 2 + src/components/RoomNameInput/index.js | 50 ++++---------- src/components/RoomNameInput/index.native.js | 66 ------------------- .../RoomNameInput/roomNameInputPropTypes.js | 6 +- src/pages/settings/Report/RoomNamePage.js | 8 +-- src/pages/workspace/WorkspaceNewRoomPage.js | 21 +++--- 7 files changed, 49 insertions(+), 128 deletions(-) delete mode 100644 src/components/RoomNameInput/index.native.js diff --git a/src/components/Form/FormProvider.js b/src/components/Form/FormProvider.js index 92baa9727832..87bdd5b5c72a 100644 --- a/src/components/Form/FormProvider.js +++ b/src/components/Form/FormProvider.js @@ -229,6 +229,8 @@ function FormProvider({validate, formID, shouldValidateOnBlur, shouldValidateOnC .first() .value() || ''; + const value = !_.isUndefined(inputValues[`${inputID}ToDisplay`]) ? inputValues[`${inputID}ToDisplay`] : inputValues[inputID]; + return { ...propsToParse, ref: @@ -241,7 +243,7 @@ function FormProvider({validate, formID, shouldValidateOnBlur, shouldValidateOnC inputID, key: propsToParse.key || inputID, errorText: errors[inputID] || fieldErrorMessage, - value: inputValues[inputID], + value, // As the text input is controlled, we never set the defaultValue prop // as this is already happening by the value prop. defaultValue: undefined, @@ -281,13 +283,19 @@ function FormProvider({validate, formID, shouldValidateOnBlur, shouldValidateOnC propsToParse.onBlur(event); } }, - onInputChange: (value, key) => { + onInputChange: (inputValue, key) => { const inputKey = key || inputID; setInputValues((prevState) => { - const newState = { - ...prevState, - [inputKey]: value, - }; + const newState = _.isFunction(propsToParse.valueParser) + ? { + ...prevState, + [inputKey]: propsToParse.valueParser(inputValue), + [`${inputKey}ToDisplay`]: inputValue, + } + : { + ...prevState, + [inputKey]: inputValue, + }; if (shouldValidateOnChange) { onValidate(newState); @@ -296,11 +304,11 @@ function FormProvider({validate, formID, shouldValidateOnBlur, shouldValidateOnC }); if (propsToParse.shouldSaveDraft) { - FormActions.setDraftValues(propsToParse.formID, {[inputKey]: value}); + FormActions.setDraftValues(propsToParse.formID, {[inputKey]: inputValue}); } if (_.isFunction(propsToParse.onValueChange)) { - propsToParse.onValueChange(value, inputKey); + propsToParse.onValueChange(inputValue, inputKey); } }, }; diff --git a/src/components/Form/InputWrapper.js b/src/components/Form/InputWrapper.js index 99237fd8db43..6569fd315e23 100644 --- a/src/components/Form/InputWrapper.js +++ b/src/components/Form/InputWrapper.js @@ -7,11 +7,13 @@ const propTypes = { inputID: PropTypes.string.isRequired, valueType: PropTypes.string, forwardedRef: PropTypes.oneOfType([PropTypes.func, PropTypes.shape({current: PropTypes.instanceOf(React.Component)})]), + valueParser: PropTypes.func, }; const defaultProps = { forwardedRef: undefined, valueType: 'string', + valueParser: undefined, }; function InputWrapper(props) { diff --git a/src/components/RoomNameInput/index.js b/src/components/RoomNameInput/index.js index 3f23a47d5f00..e659f92384b1 100644 --- a/src/components/RoomNameInput/index.js +++ b/src/components/RoomNameInput/index.js @@ -1,49 +1,23 @@ -import React, {useState} from 'react'; -import _ from 'underscore'; +import React from 'react'; import TextInput from '@components/TextInput'; import useLocalize from '@hooks/useLocalize'; import * as RoomNameInputUtils from '@libs/RoomNameInputUtils'; import CONST from '@src/CONST'; import * as roomNameInputPropTypes from './roomNameInputPropTypes'; +import InputWrapper from "@components/Form/InputWrapper"; +import getOperatingSystem from "@libs/getOperatingSystem"; -function RoomNameInput({isFocused, autoFocus, disabled, errorText, forwardedRef, value, onBlur, onChangeText, onInputChange, shouldDelayFocus}) { +function RoomNameInput({isFocused, autoFocus, disabled, errorText, forwardedRef, onBlur, shouldDelayFocus, inputID}) { const {translate} = useLocalize(); - const [selection, setSelection] = useState(); + const keyboardType = getOperatingSystem() === CONST.OS.IOS ? CONST.KEYBOARD_TYPE.ASCII_CAPABLE : CONST.KEYBOARD_TYPE.VISIBLE_PASSWORD; - /** - * Calls the onChangeText callback with a modified room name - * @param {Event} event - */ - const setModifiedRoomName = (event) => { - const roomName = event.nativeEvent.text; - const modifiedRoomName = RoomNameInputUtils.modifyRoomName(roomName); - onChangeText(modifiedRoomName); - - // if custom component has onInputChange, use it to trigger changes (Form input) - if (_.isFunction(onInputChange)) { - onInputChange(modifiedRoomName); - } - - // Prevent cursor jump behaviour: - // Check if newRoomNameWithHash is the same as modifiedRoomName - // If it is then the room name is valid (does not contain unallowed characters); no action required - // If not then the room name contains unvalid characters and we must adjust the cursor position manually - // Read more: https://github.com/Expensify/App/issues/12741 - const oldRoomNameWithHash = value || ''; - const newRoomNameWithHash = `${CONST.POLICY.ROOM_PREFIX}${roomName}`; - if (modifiedRoomName !== newRoomNameWithHash) { - const offset = modifiedRoomName.length - oldRoomNameWithHash.length; - const newSelection = { - start: selection.start + offset, - end: selection.end + offset, - }; - setSelection(newSelection); - } - }; + const valueParser = (roomName) => RoomNameInputUtils.modifyRoomName(roomName); return ( - setSelection(event.nativeEvent.selection)} errorText={errorText} + valueParser={valueParser} autoCapitalize="none" onBlur={() => isFocused && onBlur()} shouldDelayFocus={shouldDelayFocus} @@ -63,6 +34,7 @@ function RoomNameInput({isFocused, autoFocus, disabled, errorText, forwardedRef, maxLength={CONST.REPORT.MAX_ROOM_NAME_LENGTH} spellCheck={false} shouldInterceptSwipe + keyboardType={keyboardType} // this is a bit hacky solution to a RN issue https://github.com/facebook/react-native/issues/27449 /> ); } diff --git a/src/components/RoomNameInput/index.native.js b/src/components/RoomNameInput/index.native.js deleted file mode 100644 index d9b592b1537d..000000000000 --- a/src/components/RoomNameInput/index.native.js +++ /dev/null @@ -1,66 +0,0 @@ -import React from 'react'; -import _ from 'underscore'; -import TextInput from '@components/TextInput'; -import useLocalize from '@hooks/useLocalize'; -import getOperatingSystem from '@libs/getOperatingSystem'; -import * as RoomNameInputUtils from '@libs/RoomNameInputUtils'; -import CONST from '@src/CONST'; -import * as roomNameInputPropTypes from './roomNameInputPropTypes'; - -function RoomNameInput({isFocused, autoFocus, disabled, errorText, forwardedRef, value, onBlur, onChangeText, onInputChange, shouldDelayFocus}) { - const {translate} = useLocalize(); - - /** - * Calls the onChangeText callback with a modified room name - * @param {Event} event - */ - const setModifiedRoomName = (event) => { - const roomName = event.nativeEvent.text; - const modifiedRoomName = RoomNameInputUtils.modifyRoomName(roomName); - onChangeText(modifiedRoomName); - - // if custom component has onInputChange, use it to trigger changes (Form input) - if (_.isFunction(onInputChange)) { - onInputChange(modifiedRoomName); - } - }; - - const keyboardType = getOperatingSystem() === CONST.OS.IOS ? CONST.KEYBOARD_TYPE.ASCII_CAPABLE : CONST.KEYBOARD_TYPE.VISIBLE_PASSWORD; - - return ( - isFocused && onBlur()} - autoFocus={isFocused && autoFocus} - autoCapitalize="none" - shouldDelayFocus={shouldDelayFocus} - /> - ); -} - -RoomNameInput.propTypes = roomNameInputPropTypes.propTypes; -RoomNameInput.defaultProps = roomNameInputPropTypes.defaultProps; -RoomNameInput.displayName = 'RoomNameInput'; - -const RoomNameInputWithRef = React.forwardRef((props, ref) => ( - -)); - -RoomNameInputWithRef.displayName = 'RoomNameInputWithRef'; - -export default RoomNameInputWithRef; diff --git a/src/components/RoomNameInput/roomNameInputPropTypes.js b/src/components/RoomNameInput/roomNameInputPropTypes.js index 7f8292f0123e..3d1ad18d27b3 100644 --- a/src/components/RoomNameInput/roomNameInputPropTypes.js +++ b/src/components/RoomNameInput/roomNameInputPropTypes.js @@ -1,4 +1,5 @@ import PropTypes from 'prop-types'; +import refPropTypes from '../refPropTypes'; const propTypes = { /** Callback to execute when the text input is modified correctly */ @@ -14,10 +15,10 @@ const propTypes = { errorText: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.object]))]), /** A ref forwarded to the TextInput */ - forwardedRef: PropTypes.func, + forwardedRef: refPropTypes, /** The ID used to uniquely identify the input in a Form */ - inputID: PropTypes.string, + inputID: PropTypes.string.isRequired, /** Callback that is called when the text input is blurred */ onBlur: PropTypes.func, @@ -39,7 +40,6 @@ const defaultProps = { errorText: '', forwardedRef: () => {}, - inputID: undefined, onBlur: () => {}, autoFocus: false, shouldDelayFocus: false, diff --git a/src/pages/settings/Report/RoomNamePage.js b/src/pages/settings/Report/RoomNamePage.js index 2ff9b1f1108f..937da4565604 100644 --- a/src/pages/settings/Report/RoomNamePage.js +++ b/src/pages/settings/Report/RoomNamePage.js @@ -4,7 +4,6 @@ import React, {useCallback, useRef} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; -import Form from '@components/Form'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import RoomNameInput from '@components/RoomNameInput'; import ScreenWrapper from '@components/ScreenWrapper'; @@ -21,6 +20,7 @@ import * as Report from '@userActions/Report'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import FormProvider from "@components/Form/FormProvider"; const propTypes = { ...withLocalizePropTypes, @@ -90,7 +90,7 @@ function RoomNamePage(props) { title={translate('newRoomPage.roomName')} onBackButtonPress={() => Navigation.goBack(ROUTES.REPORT_SETTINGS.getRoute(report.reportID))} /> -
Report.updatePolicyRoomNameAndNavigate(report, values.roomName)} @@ -100,13 +100,13 @@ function RoomNamePage(props) { > (roomNameInputRef.current = ref)} + ref={roomNameInputRef} inputID="roomName" defaultValue={report.reportName} isFocused={isFocused} /> -
+ ); diff --git a/src/pages/workspace/WorkspaceNewRoomPage.js b/src/pages/workspace/WorkspaceNewRoomPage.js index 271dc45026c7..05cc8df6a547 100644 --- a/src/pages/workspace/WorkspaceNewRoomPage.js +++ b/src/pages/workspace/WorkspaceNewRoomPage.js @@ -4,7 +4,6 @@ import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; -import Form from '@components/Form'; import KeyboardAvoidingView from '@components/KeyboardAvoidingView'; import OfflineIndicator from '@components/OfflineIndicator'; import RoomNameInput from '@components/RoomNameInput'; @@ -30,6 +29,8 @@ import * as App from '@userActions/App'; import * as Report from '@userActions/Report'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import FormProvider from "@components/Form/FormProvider"; +import InputWrapper from "@components/Form/InputWrapper"; const propTypes = { /** All reports shared with the user */ @@ -189,7 +190,7 @@ function WorkspaceNewRoomPage(props) { // This is because when wrapping whole screen the screen was freezing when changing Tabs. keyboardVerticalOffset={variables.contentHeaderHeight + variables.tabSelectorButtonHeight + variables.tabSelectorButtonPadding + insets.top} > -
(roomNameInputRef.current = el)} + ref={roomNameInputRef} inputID="roomName" isFocused={props.isFocused} shouldDelayFocus @@ -207,7 +208,8 @@ function WorkspaceNewRoomPage(props) { /> - - {isPolicyAdmin && ( - )} - {visibilityDescription} - + {isSmallScreenWidth && } )} From 331e2a80c1be738b3382aa8a1030c15a2e6397cc Mon Sep 17 00:00:00 2001 From: tienifr Date: Tue, 7 Nov 2023 17:12:27 +0700 Subject: [PATCH 018/130] fix lint --- .../ComposerWithSuggestions/ComposerWithSuggestions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js index 7f2ef5a0fb4a..64414f1d89e5 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js @@ -153,7 +153,7 @@ function ComposerWithSuggestions({ })); const [composerHeight, setComposerHeight] = useState(0); - + const insertedEmojisRef = useRef([]); // A flag to indicate whether the onScroll callback is likely triggered by a layout change (caused by text change) or not From 2b7be0a1c964084bf6143fb47eef948a8ae7b067 Mon Sep 17 00:00:00 2001 From: tienifr Date: Tue, 7 Nov 2023 17:34:46 +0700 Subject: [PATCH 019/130] refactor code --- .../ComposerWithSuggestions.js | 36 ++++++++++--------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js index 64414f1d89e5..f6e97a838cf2 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js @@ -127,22 +127,6 @@ function ComposerWithSuggestions({ const isEmptyChat = useMemo(() => _.size(reportActions) === 1, [reportActions]); const parentAction = ReportActionsUtils.getParentReportAction(report); const shouldAutoFocus = !modal.isVisible && (shouldFocusInputOnScreenFocus || (isEmptyChat && !ReportActionsUtils.isTransactionThread(parentAction))) && shouldShowComposeInput; - const textInputRef = useRef(null); - const shouldAutoFocusRef = useRef(false); - - shouldAutoFocusRef.current = shouldAutoFocus; - - const prevIsFocused = usePrevious(isFocused); - - useEffect(() => { - if (!prevIsFocused && isFocused) { - setTimeout(() => { - if (shouldAutoFocusRef.current) { - textInputRef.current.focus(); - } - }, CONST.ANIMATED_TRANSITION); - } - }, [isFocused, prevIsFocused]); const valueRef = useRef(value); valueRef.current = value; @@ -154,6 +138,7 @@ function ComposerWithSuggestions({ const [composerHeight, setComposerHeight] = useState(0); + const textInputRef = useRef(null); const insertedEmojisRef = useRef([]); // A flag to indicate whether the onScroll callback is likely triggered by a layout change (caused by text change) or not @@ -547,6 +532,7 @@ function ComposerWithSuggestions({ }, [focusComposerOnKeyPress, navigation, setUpComposeFocusManager]); const prevIsModalVisible = usePrevious(modal.isVisible); + const prevIsFocused = usePrevious(isFocused); useEffect(() => { if (modal.isVisible && !prevIsModalVisible) { @@ -566,6 +552,24 @@ function ComposerWithSuggestions({ } focus(); }, [focus, prevIsFocused, editFocused, prevIsModalVisible, isFocused, modal.isVisible, isNextModalWillOpenRef]); + + const shouldAutoFocusRef = useRef(false); + shouldAutoFocusRef.current = shouldAutoFocus; + + useEffect(() => { + if (prevIsFocused || !isFocused) { + return; + } + // Focus composer when navigating from another screen + const focusTimeout = setTimeout(() => { + if (!shouldAutoFocusRef.current) { + return; + } + textInputRef.current.focus(); + }, CONST.ANIMATED_TRANSITION); + return () => clearTimeout(focusTimeout); + }, [isFocused, prevIsFocused]); + useEffect(() => { // Scrolls the composer to the bottom and sets the selection to the end, so that longer drafts are easier to edit updateMultilineInputRange(textInputRef.current, shouldAutoFocus); From 4294da8d50d5a1ce762695e8b9273934e135cb18 Mon Sep 17 00:00:00 2001 From: tienifr Date: Tue, 7 Nov 2023 17:35:36 +0700 Subject: [PATCH 020/130] fix lint --- .../ComposerWithSuggestions/ComposerWithSuggestions.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js index f6e97a838cf2..0b572bce2d81 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js @@ -533,7 +533,6 @@ function ComposerWithSuggestions({ const prevIsModalVisible = usePrevious(modal.isVisible); const prevIsFocused = usePrevious(isFocused); - useEffect(() => { if (modal.isVisible && !prevIsModalVisible) { // eslint-disable-next-line no-param-reassign @@ -555,7 +554,6 @@ function ComposerWithSuggestions({ const shouldAutoFocusRef = useRef(false); shouldAutoFocusRef.current = shouldAutoFocus; - useEffect(() => { if (prevIsFocused || !isFocused) { return; From 1b637a6cdcedf2cc7abdac829459ec677274bd70 Mon Sep 17 00:00:00 2001 From: Kamil Owczarz Date: Tue, 7 Nov 2023 15:32:28 +0100 Subject: [PATCH 021/130] Fix room name parsing and not displaying room name in room name editor --- src/components/Form/FormProvider.js | 4 ++-- src/components/Form/InputWrapper.js | 5 ++++- src/components/RoomNameInput/index.js | 6 +++++- .../RoomNameInput/roomNameInputPropTypes.js | 3 +++ src/libs/RoomNameInputUtils.ts | 4 ++-- src/pages/settings/Report/RoomNamePage.js | 11 ++++------- 6 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/components/Form/FormProvider.js b/src/components/Form/FormProvider.js index 87bdd5b5c72a..96c575a76e17 100644 --- a/src/components/Form/FormProvider.js +++ b/src/components/Form/FormProvider.js @@ -2,7 +2,7 @@ import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; import React, {createRef, useCallback, useMemo, useRef, useState} from 'react'; import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; + import _ from 'underscore'; import networkPropTypes from '@components/networkPropTypes'; import {withNetwork} from '@components/OnyxProvider'; import compose from '@libs/compose'; @@ -290,7 +290,7 @@ function FormProvider({validate, formID, shouldValidateOnBlur, shouldValidateOnC ? { ...prevState, [inputKey]: propsToParse.valueParser(inputValue), - [`${inputKey}ToDisplay`]: inputValue, + [`${inputKey}ToDisplay`]: _.isFunction(propsToParse.displayParser) ? propsToParse.displayParser(inputValue) : inputValue, } : { ...prevState, diff --git a/src/components/Form/InputWrapper.js b/src/components/Form/InputWrapper.js index 6569fd315e23..aef6f5cdc8b4 100644 --- a/src/components/Form/InputWrapper.js +++ b/src/components/Form/InputWrapper.js @@ -1,19 +1,22 @@ import PropTypes from 'prop-types'; import React, {forwardRef, useContext} from 'react'; import FormContext from './FormContext'; +import refPropTypes from "@components/refPropTypes"; const propTypes = { InputComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.elementType]).isRequired, inputID: PropTypes.string.isRequired, valueType: PropTypes.string, - forwardedRef: PropTypes.oneOfType([PropTypes.func, PropTypes.shape({current: PropTypes.instanceOf(React.Component)})]), + forwardedRef: refPropTypes, valueParser: PropTypes.func, + displayParser: PropTypes.func }; const defaultProps = { forwardedRef: undefined, valueType: 'string', valueParser: undefined, + displayParser: undefined, }; function InputWrapper(props) { diff --git a/src/components/RoomNameInput/index.js b/src/components/RoomNameInput/index.js index e659f92384b1..8801605db1c7 100644 --- a/src/components/RoomNameInput/index.js +++ b/src/components/RoomNameInput/index.js @@ -3,16 +3,18 @@ import TextInput from '@components/TextInput'; import useLocalize from '@hooks/useLocalize'; import * as RoomNameInputUtils from '@libs/RoomNameInputUtils'; import CONST from '@src/CONST'; +import _ from 'underscore'; import * as roomNameInputPropTypes from './roomNameInputPropTypes'; import InputWrapper from "@components/Form/InputWrapper"; import getOperatingSystem from "@libs/getOperatingSystem"; -function RoomNameInput({isFocused, autoFocus, disabled, errorText, forwardedRef, onBlur, shouldDelayFocus, inputID}) { +function RoomNameInput({isFocused, autoFocus, disabled, errorText, forwardedRef, onBlur, shouldDelayFocus, inputID, roomName}) { const {translate} = useLocalize(); const keyboardType = getOperatingSystem() === CONST.OS.IOS ? CONST.KEYBOARD_TYPE.ASCII_CAPABLE : CONST.KEYBOARD_TYPE.VISIBLE_PASSWORD; const valueParser = (roomName) => RoomNameInputUtils.modifyRoomName(roomName); + const displayParser = (roomName) => RoomNameInputUtils.modifyRoomName(roomName, true); return ( isFocused && onBlur()} shouldDelayFocus={shouldDelayFocus} autoFocus={isFocused && autoFocus} maxLength={CONST.REPORT.MAX_ROOM_NAME_LENGTH} + defaultValue={roomName} spellCheck={false} shouldInterceptSwipe keyboardType={keyboardType} // this is a bit hacky solution to a RN issue https://github.com/facebook/react-native/issues/27449 diff --git a/src/components/RoomNameInput/roomNameInputPropTypes.js b/src/components/RoomNameInput/roomNameInputPropTypes.js index 3d1ad18d27b3..c9c5db7f74c0 100644 --- a/src/components/RoomNameInput/roomNameInputPropTypes.js +++ b/src/components/RoomNameInput/roomNameInputPropTypes.js @@ -31,6 +31,8 @@ const propTypes = { /** Whether navigation is focused */ isFocused: PropTypes.bool.isRequired, + + roomName: PropTypes.string }; const defaultProps = { @@ -43,6 +45,7 @@ const defaultProps = { onBlur: () => {}, autoFocus: false, shouldDelayFocus: false, + roomName: '', }; export {propTypes, defaultProps}; diff --git a/src/libs/RoomNameInputUtils.ts b/src/libs/RoomNameInputUtils.ts index cff0bbc30274..e6f7d420bf59 100644 --- a/src/libs/RoomNameInputUtils.ts +++ b/src/libs/RoomNameInputUtils.ts @@ -3,14 +3,14 @@ import CONST from '@src/CONST'; /** * Replaces spaces with dashes */ -function modifyRoomName(roomName: string): string { +function modifyRoomName(roomName: string, skipPolicyPrefix?: boolean): string { const modifiedRoomNameWithoutHash = roomName .replace(/ /g, '-') // Replaces the smart dash on iOS devices with two hyphens .replace(/—/g, '--'); - return `${CONST.POLICY.ROOM_PREFIX}${modifiedRoomNameWithoutHash}`; + return skipPolicyPrefix ? modifiedRoomNameWithoutHash : `${CONST.POLICY.ROOM_PREFIX}${modifiedRoomNameWithoutHash}`; } export { diff --git a/src/pages/settings/Report/RoomNamePage.js b/src/pages/settings/Report/RoomNamePage.js index 937da4565604..d3a3c6058f62 100644 --- a/src/pages/settings/Report/RoomNamePage.js +++ b/src/pages/settings/Report/RoomNamePage.js @@ -21,6 +21,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import FormProvider from "@components/Form/FormProvider"; +import _ from "underscore"; const propTypes = { ...withLocalizePropTypes, @@ -42,13 +43,8 @@ const defaultProps = { policy: {}, }; -function RoomNamePage(props) { - const policy = props.policy; - const report = props.report; - const reports = props.reports; - const translate = props.translate; - - const roomNameInputRef = useRef(null); +function RoomNamePage({policy, report, reports, translate}) { + const roomNameInputRef = useRef(null); const isFocused = useIsFocused(); const validate = useCallback( @@ -104,6 +100,7 @@ function RoomNamePage(props) { inputID="roomName" defaultValue={report.reportName} isFocused={isFocused} + roomName={report.reportName.slice(1)} /> From f0a6fd151a5d070ac75df6f7015c413eccd5c85b Mon Sep 17 00:00:00 2001 From: Kamil Owczarz Date: Tue, 7 Nov 2023 16:27:15 +0100 Subject: [PATCH 022/130] Lint fixes --- src/components/Form/FormProvider.js | 2 +- src/components/Form/InputWrapper.js | 4 ++-- src/components/RoomNameInput/index.js | 9 ++++----- src/components/RoomNameInput/roomNameInputPropTypes.js | 2 +- src/pages/settings/Report/RoomNamePage.js | 5 ++--- src/pages/workspace/WorkspaceNewRoomPage.js | 4 ++-- 6 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/components/Form/FormProvider.js b/src/components/Form/FormProvider.js index 96c575a76e17..48a381ac041e 100644 --- a/src/components/Form/FormProvider.js +++ b/src/components/Form/FormProvider.js @@ -2,7 +2,7 @@ import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; import React, {createRef, useCallback, useMemo, useRef, useState} from 'react'; import {withOnyx} from 'react-native-onyx'; - import _ from 'underscore'; +import _ from 'underscore'; import networkPropTypes from '@components/networkPropTypes'; import {withNetwork} from '@components/OnyxProvider'; import compose from '@libs/compose'; diff --git a/src/components/Form/InputWrapper.js b/src/components/Form/InputWrapper.js index aef6f5cdc8b4..36dca6ffee61 100644 --- a/src/components/Form/InputWrapper.js +++ b/src/components/Form/InputWrapper.js @@ -1,7 +1,7 @@ import PropTypes from 'prop-types'; import React, {forwardRef, useContext} from 'react'; +import refPropTypes from '@components/refPropTypes'; import FormContext from './FormContext'; -import refPropTypes from "@components/refPropTypes"; const propTypes = { InputComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.elementType]).isRequired, @@ -9,7 +9,7 @@ const propTypes = { valueType: PropTypes.string, forwardedRef: refPropTypes, valueParser: PropTypes.func, - displayParser: PropTypes.func + displayParser: PropTypes.func, }; const defaultProps = { diff --git a/src/components/RoomNameInput/index.js b/src/components/RoomNameInput/index.js index 60d6181bcb81..39cf22bc98f3 100644 --- a/src/components/RoomNameInput/index.js +++ b/src/components/RoomNameInput/index.js @@ -1,20 +1,19 @@ import React from 'react'; +import InputWrapper from '@components/Form/InputWrapper'; import TextInput from '@components/TextInput'; import useLocalize from '@hooks/useLocalize'; +import getOperatingSystem from '@libs/getOperatingSystem'; import * as RoomNameInputUtils from '@libs/RoomNameInputUtils'; import CONST from '@src/CONST'; -import _ from 'underscore'; import * as roomNameInputPropTypes from './roomNameInputPropTypes'; -import InputWrapper from "@components/Form/InputWrapper"; -import getOperatingSystem from "@libs/getOperatingSystem"; function RoomNameInput({isFocused, autoFocus, disabled, errorText, forwardedRef, onBlur, shouldDelayFocus, inputID, roomName}) { const {translate} = useLocalize(); const keyboardType = getOperatingSystem() === CONST.OS.IOS ? CONST.KEYBOARD_TYPE.ASCII_CAPABLE : CONST.KEYBOARD_TYPE.VISIBLE_PASSWORD; - const valueParser = (roomName) => RoomNameInputUtils.modifyRoomName(roomName); - const displayParser = (roomName) => RoomNameInputUtils.modifyRoomName(roomName, true); + const valueParser = (innerRoomName) => RoomNameInputUtils.modifyRoomName(innerRoomName); + const displayParser = (innerRoomName) => RoomNameInputUtils.modifyRoomName(innerRoomName, true); return ( Date: Tue, 7 Nov 2023 16:29:05 +0100 Subject: [PATCH 023/130] Fix lint issues --- src/components/Form/InputWrapper.js | 2 +- src/components/RoomNameInput/roomNameInputPropTypes.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Form/InputWrapper.js b/src/components/Form/InputWrapper.js index 36dca6ffee61..9fe2eddf3d64 100644 --- a/src/components/Form/InputWrapper.js +++ b/src/components/Form/InputWrapper.js @@ -1,7 +1,7 @@ import PropTypes from 'prop-types'; import React, {forwardRef, useContext} from 'react'; import refPropTypes from '@components/refPropTypes'; -import FormContext from './FormContext'; +import FormContext from "@components/Form/FormContext"; const propTypes = { InputComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.elementType]).isRequired, diff --git a/src/components/RoomNameInput/roomNameInputPropTypes.js b/src/components/RoomNameInput/roomNameInputPropTypes.js index 476464c7835d..29633f4dfedd 100644 --- a/src/components/RoomNameInput/roomNameInputPropTypes.js +++ b/src/components/RoomNameInput/roomNameInputPropTypes.js @@ -1,5 +1,5 @@ import PropTypes from 'prop-types'; -import refPropTypes from '../refPropTypes'; +import refPropTypes from "@components/refPropTypes"; const propTypes = { /** Callback to execute when the text input is modified correctly */ From 5464fc524817ebf58b06e5079a0961a652823fa6 Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 8 Nov 2023 16:24:25 +0700 Subject: [PATCH 024/130] remove clearTimeout --- .../ComposerWithSuggestions/ComposerWithSuggestions.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js index 224dae018204..a38d88c1c5ec 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js @@ -507,13 +507,12 @@ function ComposerWithSuggestions({ return; } // Focus composer when navigating from another screen - const focusTimeout = setTimeout(() => { + setTimeout(() => { if (!shouldAutoFocusRef.current) { return; } textInputRef.current.focus(); }, CONST.ANIMATED_TRANSITION); - return () => clearTimeout(focusTimeout); }, [isFocused, prevIsFocused]); useEffect(() => { From 580c6895f5f12bf902059942591a1ef34a07baf4 Mon Sep 17 00:00:00 2001 From: Kamil Owczarz Date: Wed, 8 Nov 2023 11:12:57 +0100 Subject: [PATCH 025/130] Fix lint --- src/components/Form/InputWrapper.js | 2 +- src/components/RoomNameInput/roomNameInputPropTypes.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Form/InputWrapper.js b/src/components/Form/InputWrapper.js index 9fe2eddf3d64..36dca6ffee61 100644 --- a/src/components/Form/InputWrapper.js +++ b/src/components/Form/InputWrapper.js @@ -1,7 +1,7 @@ import PropTypes from 'prop-types'; import React, {forwardRef, useContext} from 'react'; import refPropTypes from '@components/refPropTypes'; -import FormContext from "@components/Form/FormContext"; +import FormContext from './FormContext'; const propTypes = { InputComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.elementType]).isRequired, diff --git a/src/components/RoomNameInput/roomNameInputPropTypes.js b/src/components/RoomNameInput/roomNameInputPropTypes.js index 29633f4dfedd..f457e4e2a494 100644 --- a/src/components/RoomNameInput/roomNameInputPropTypes.js +++ b/src/components/RoomNameInput/roomNameInputPropTypes.js @@ -1,5 +1,5 @@ import PropTypes from 'prop-types'; -import refPropTypes from "@components/refPropTypes"; +import refPropTypes from '@components/refPropTypes'; const propTypes = { /** Callback to execute when the text input is modified correctly */ From 839326e399ff4753f5c026ff96d81715c5ce826c Mon Sep 17 00:00:00 2001 From: Getabalew Tesfaye Date: Fri, 10 Nov 2023 19:08:07 +0300 Subject: [PATCH 026/130] update 'reason' to 'cancellationReason' --- src/components/ReportActionItem/MoneyRequestPreview.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestPreview.js b/src/components/ReportActionItem/MoneyRequestPreview.js index f7c647fb4c86..c22b204dd80f 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview.js +++ b/src/components/ReportActionItem/MoneyRequestPreview.js @@ -219,7 +219,7 @@ function MoneyRequestPreview(props) { message += ` • ${props.translate('iou.approved')}`; } else if (props.iouReport.isWaitingOnBankAccount) { message += ` • ${props.translate('iou.pending')}`; - } else if (props.action.originalMessage && props.action.originalMessage.reason === CONST.IOU.CANCEL_REASON.PAYMENT_EXPIRED) { + } else if (props.action.originalMessage && props.action.originalMessage.cancellationReason === CONST.IOU.CANCEL_REASON.PAYMENT_EXPIRED) { message += ` • ${props.translate('iou.canceled')}`; } return message; From f2c59f35b7f2e99005951a96e2f2c2b545fe518d Mon Sep 17 00:00:00 2001 From: Getabalew Tesfaye Date: Fri, 10 Nov 2023 19:15:11 +0300 Subject: [PATCH 027/130] remove usage of report.ownerEmail and add condition for canceled case --- src/components/ReportActionItem/MoneyRequestView.js | 6 ++++-- src/pages/home/report/ReportActionItem.js | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index 4b69f14213a2..7130e3d41928 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -77,7 +77,7 @@ const defaultProps = { policyTags: {}, }; -function MoneyRequestView({report, betas, parentReport, policyCategories, shouldShowHorizontalRule, transaction, policyTags, policy}) { +function MoneyRequestView({report, betas, parentReport, policyCategories, shouldShowHorizontalRule, transaction, policyTags, policy, action}) { const {isSmallScreenWidth} = useWindowDimensions(); const {translate} = useLocalize(); const parentReportAction = ReportActionsUtils.getParentReportAction(report); @@ -136,7 +136,9 @@ function MoneyRequestView({report, betas, parentReport, policyCategories, should if (!isDistanceRequest) { amountDescription += ` • ${translate('iou.cash')}`; } - if (isSettled) { + if(props.action && props.action.originalMessage.cancellationReason === CONST.IOU.CANCEL_REASON.PAYMENT_EXPIRED) { + amountDescription += ` • ${translate('iou.canceled')}`; + } else if (isSettled) { amountDescription += ` • ${translate('iou.settledExpensify')}`; } else if (report.isWaitingOnBankAccount) { amountDescription += ` • ${translate('iou.pending')}`; diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index df56d38dc2dd..448e0ae166c9 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -415,7 +415,7 @@ function ReportActionItem(props) {
); } else if (props.action.actionName === CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENTDEQUEUED) { - const submitterDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(props.personalDetailsList, [props.report.ownerAccountID, 'displayName'], props.report.ownerEmail); + const submitterDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(personalDetails, [props.report.ownerAccountID, 'displayName']); const amount = CurrencyUtils.convertToDisplayString(props.report.total, props.report.currency); children = ; @@ -572,6 +572,7 @@ function ReportActionItem(props) { content = ( From 0b1f90b76f37a60097f3ea0f4139cca3c86f91d2 Mon Sep 17 00:00:00 2001 From: Getabalew Tesfaye Date: Fri, 10 Nov 2023 19:20:33 +0300 Subject: [PATCH 028/130] fix lint --- src/components/ReportActionItem/MoneyRequestView.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index 7130e3d41928..68656c6f40fe 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -136,7 +136,7 @@ function MoneyRequestView({report, betas, parentReport, policyCategories, should if (!isDistanceRequest) { amountDescription += ` • ${translate('iou.cash')}`; } - if(props.action && props.action.originalMessage.cancellationReason === CONST.IOU.CANCEL_REASON.PAYMENT_EXPIRED) { + if(action && action.originalMessage.cancellationReason === CONST.IOU.CANCEL_REASON.PAYMENT_EXPIRED) { amountDescription += ` • ${translate('iou.canceled')}`; } else if (isSettled) { amountDescription += ` • ${translate('iou.settledExpensify')}`; From a4013f10ce3981cf83583103006f05748ad1bfe5 Mon Sep 17 00:00:00 2001 From: Getabalew Tesfaye Date: Fri, 10 Nov 2023 19:21:47 +0300 Subject: [PATCH 029/130] run prettier --- src/components/ReportActionItem/MoneyRequestView.js | 2 +- src/pages/home/report/ReportActionItem.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index 68656c6f40fe..0df83a6c36c9 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -136,7 +136,7 @@ function MoneyRequestView({report, betas, parentReport, policyCategories, should if (!isDistanceRequest) { amountDescription += ` • ${translate('iou.cash')}`; } - if(action && action.originalMessage.cancellationReason === CONST.IOU.CANCEL_REASON.PAYMENT_EXPIRED) { + if (action && action.originalMessage.cancellationReason === CONST.IOU.CANCEL_REASON.PAYMENT_EXPIRED) { amountDescription += ` • ${translate('iou.canceled')}`; } else if (isSettled) { amountDescription += ` • ${translate('iou.settledExpensify')}`; diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 448e0ae166c9..43526d7ee5c4 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -74,7 +74,7 @@ import ReportActionItemSingle from './ReportActionItemSingle'; import ReportActionItemThread from './ReportActionItemThread'; import reportActionPropTypes from './reportActionPropTypes'; import ReportAttachmentsContext from './ReportAttachmentsContext'; -import * as CurrencyUtils from '../../../libs/CurrencyUtils'; +import * as CurrencyUtils from '@libs/CurrencyUtils'; const propTypes = { ...windowDimensionsPropTypes, From 97d8c5eb14f6bf902535d23e9074dd29a44582b9 Mon Sep 17 00:00:00 2001 From: Getabalew Tesfaye Date: Fri, 10 Nov 2023 19:26:18 +0300 Subject: [PATCH 030/130] lint! --- src/pages/home/report/ReportActionItem.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 43526d7ee5c4..bbd368fa6734 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -58,6 +58,7 @@ import * as User from '@userActions/User'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import * as CurrencyUtils from '@libs/CurrencyUtils'; import AnimatedEmptyStateBackground from './AnimatedEmptyStateBackground'; import * as ContextMenuActions from './ContextMenu/ContextMenuActions'; import MiniReportActionContextMenu from './ContextMenu/MiniReportActionContextMenu'; @@ -74,7 +75,6 @@ import ReportActionItemSingle from './ReportActionItemSingle'; import ReportActionItemThread from './ReportActionItemThread'; import reportActionPropTypes from './reportActionPropTypes'; import ReportAttachmentsContext from './ReportAttachmentsContext'; -import * as CurrencyUtils from '@libs/CurrencyUtils'; const propTypes = { ...windowDimensionsPropTypes, From 2f0af0c67c0a664d363bcb25559bf6c211f215fd Mon Sep 17 00:00:00 2001 From: Getabalew Tesfaye Date: Mon, 13 Nov 2023 12:51:30 +0300 Subject: [PATCH 031/130] fix crash --- src/components/ReportActionItem/MoneyRequestView.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index 0df83a6c36c9..b51a55082e0a 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -136,7 +136,7 @@ function MoneyRequestView({report, betas, parentReport, policyCategories, should if (!isDistanceRequest) { amountDescription += ` • ${translate('iou.cash')}`; } - if (action && action.originalMessage.cancellationReason === CONST.IOU.CANCEL_REASON.PAYMENT_EXPIRED) { + if (action && action.originalMessage && action.originalMessage.cancellationReason === CONST.IOU.CANCEL_REASON.PAYMENT_EXPIRED) { amountDescription += ` • ${translate('iou.canceled')}`; } else if (isSettled) { amountDescription += ` • ${translate('iou.settledExpensify')}`; From ef72ec73c244a94611a892ed91edfab9b73669d0 Mon Sep 17 00:00:00 2001 From: Getabalew Tesfaye Date: Mon, 13 Nov 2023 12:51:51 +0300 Subject: [PATCH 032/130] run prettier --- src/languages/en.ts | 2 +- src/languages/es.ts | 2 +- src/pages/home/report/ReportActionItem.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 32143a9ae73a..2dcf373a3182 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -9,6 +9,7 @@ import type { BeginningOfChatHistoryAnnounceRoomPartOneParams, BeginningOfChatHistoryAnnounceRoomPartTwo, BeginningOfChatHistoryDomainRoomPartOneParams, + CanceledRequestParams, CharacterLimitParams, ConfirmThatParams, DateShouldBeAfterParams, @@ -78,7 +79,6 @@ import type { WelcomeToRoomParams, WeSentYouMagicSignInLinkParams, ZipCodeExampleFormatParams, - CanceledRequestParams, } from './types'; type StateValue = { diff --git a/src/languages/es.ts b/src/languages/es.ts index 7e3e7d89fdb5..5486898ca63b 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -8,6 +8,7 @@ import type { BeginningOfChatHistoryAnnounceRoomPartOneParams, BeginningOfChatHistoryAnnounceRoomPartTwo, BeginningOfChatHistoryDomainRoomPartOneParams, + CanceledRequestParams, CharacterLimitParams, ConfirmThatParams, DateShouldBeAfterParams, @@ -77,7 +78,6 @@ import type { WelcomeToRoomParams, WeSentYouMagicSignInLinkParams, ZipCodeExampleFormatParams, - CanceledRequestParams, } from './types'; /* eslint-disable max-len */ diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index bbd368fa6734..031aebdb3512 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -34,6 +34,7 @@ import withWindowDimensions, {windowDimensionsPropTypes} from '@components/withW import usePrevious from '@hooks/usePrevious'; import compose from '@libs/compose'; import ControlSelection from '@libs/ControlSelection'; +import * as CurrencyUtils from '@libs/CurrencyUtils'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import focusTextInputAfterAnimation from '@libs/focusTextInputAfterAnimation'; import Navigation from '@libs/Navigation/Navigation'; @@ -58,7 +59,6 @@ import * as User from '@userActions/User'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import * as CurrencyUtils from '@libs/CurrencyUtils'; import AnimatedEmptyStateBackground from './AnimatedEmptyStateBackground'; import * as ContextMenuActions from './ContextMenu/ContextMenuActions'; import MiniReportActionContextMenu from './ContextMenu/MiniReportActionContextMenu'; From 72113b4295d5881adf612dcefbb68af5874f7c34 Mon Sep 17 00:00:00 2001 From: Dylan Date: Wed, 15 Nov 2023 11:00:18 +0700 Subject: [PATCH 033/130] using SET to save updated transaction --- src/libs/actions/IOU.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index aa07f0e7ca34..d3fa2f4915ba 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -765,7 +765,7 @@ function updateDistanceRequest(transactionID, transactionThreadReportID, transac // Optimistically modify the transaction optimisticData.push({ - onyxMethod: Onyx.METHOD.MERGE, + onyxMethod: Onyx.METHOD.SET, key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, value: { ...updatedTransaction, From 816a67c8ba0a3f89d870445cd8f8fe54b92f5720 Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 15 Nov 2023 16:17:24 +0700 Subject: [PATCH 034/130] refactor to handle focus in the same useEffect --- .../ComposerWithSuggestions.js | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js index 288df7775f65..c3981fabb15d 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js @@ -489,7 +489,7 @@ function ComposerWithSuggestions({ // We want to focus or refocus the input when a modal has been closed or the underlying screen is refocused. // We avoid doing this on native platforms since the software keyboard popping // open creates a jarring and broken UX. - if (!(willBlurTextInputOnTapOutside && !isNextModalWillOpenRef.current && !modal.isVisible && isFocused && (prevIsModalVisible || !prevIsFocused))) { + if (!((willBlurTextInputOnTapOutside || shouldAutoFocus) && !isNextModalWillOpenRef.current && !modal.isVisible && isFocused && (prevIsModalVisible || !prevIsFocused))) { return; } @@ -500,21 +500,6 @@ function ComposerWithSuggestions({ focus(); }, [focus, prevIsFocused, editFocused, prevIsModalVisible, isFocused, modal.isVisible, isNextModalWillOpenRef]); - const shouldAutoFocusRef = useRef(false); - shouldAutoFocusRef.current = shouldAutoFocus; - useEffect(() => { - if (prevIsFocused || !isFocused) { - return; - } - // Focus composer when navigating from another screen - setTimeout(() => { - if (!shouldAutoFocusRef.current) { - return; - } - textInputRef.current.focus(); - }, CONST.ANIMATED_TRANSITION); - }, [isFocused, prevIsFocused]); - useEffect(() => { // Scrolls the composer to the bottom and sets the selection to the end, so that longer drafts are easier to edit updateMultilineInputRange(textInputRef.current, shouldAutoFocus); From f671ed37edf01b6c64ca85f92b377c15b3303132 Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 15 Nov 2023 17:44:51 +0700 Subject: [PATCH 035/130] fix lint --- .../ComposerWithSuggestions/ComposerWithSuggestions.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js index c3981fabb15d..3e8dd7372bc3 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js @@ -498,6 +498,7 @@ function ComposerWithSuggestions({ return; } focus(); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [focus, prevIsFocused, editFocused, prevIsModalVisible, isFocused, modal.isVisible, isNextModalWillOpenRef]); useEffect(() => { From e2678855271fc25e320e9292b70d575c71485c7a Mon Sep 17 00:00:00 2001 From: Pavlo Tsimura Date: Fri, 17 Nov 2023 17:30:44 +0100 Subject: [PATCH 036/130] Show top skeleton when offline; Show animation always. --- .../ListBoundaryLoader/ListBoundaryLoader.js | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/pages/home/report/ListBoundaryLoader/ListBoundaryLoader.js b/src/pages/home/report/ListBoundaryLoader/ListBoundaryLoader.js index 6dd56471af07..7184944d5902 100644 --- a/src/pages/home/report/ListBoundaryLoader/ListBoundaryLoader.js +++ b/src/pages/home/report/ListBoundaryLoader/ListBoundaryLoader.js @@ -36,22 +36,20 @@ function ListBoundaryLoader({type, isLoadingOlderReportActions, isLoadingInitial const styles = useThemeStyles(); const {isOffline} = useNetwork(); - // we use two different loading components for header and footer to reduce the jumping effect when you scrolling to the newer reports + // We use two different loading components for the header and footer + // to reduce the jumping effect when the user is scrolling to the newer report actions if (type === CONST.LIST_COMPONENTS.FOOTER) { if (isLoadingOlderReportActions) { return ; } - // Make sure the oldest report action loaded is not the first. This is so we do not show the - // skeleton view above the created action in a newly generated optimistic chat or one with not + // Make sure the report chat is not loaded till the beginning. This is so we do not show the + // skeleton view above the "created" action in a newly generated optimistic chat or one with not // that many comments. - if (isLoadingInitialReportActions && lastReportActionName !== CONST.REPORT.ACTIONS.TYPE.CREATED) { - return ( - - ); + // Also, if we are offline and the report is not yet loaded till the beginning, we assume there are more actions to load, + // therefore show the skeleton view, even though the actions are not loading. + if (lastReportActionName !== CONST.REPORT.ACTIONS.TYPE.CREATED && (isLoadingInitialReportActions || isOffline)) { + return ; } } if (type === CONST.LIST_COMPONENTS.HEADER && isLoadingNewerReportActions) { From 5504cc0767b3489ea7dfb5b244a0562925bf649d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Miko=C5=82ajczak?= Date: Sat, 18 Nov 2023 15:34:33 +0100 Subject: [PATCH 037/130] split the Hoverable into Active and Disabled parts --- src/components/Hoverable/ActiveHoverable.tsx | 70 ++++++ src/components/Hoverable/index.tsx | 216 ++----------------- 2 files changed, 82 insertions(+), 204 deletions(-) create mode 100644 src/components/Hoverable/ActiveHoverable.tsx diff --git a/src/components/Hoverable/ActiveHoverable.tsx b/src/components/Hoverable/ActiveHoverable.tsx new file mode 100644 index 000000000000..b77b2a60a2fb --- /dev/null +++ b/src/components/Hoverable/ActiveHoverable.tsx @@ -0,0 +1,70 @@ +import {cloneElement, forwardRef, Ref, useCallback, useEffect, useMemo, useRef, useState} from 'react'; +import {DeviceEventEmitter} from 'react-native'; +import CONST from '@src/CONST'; +import HoverableProps from './types'; + +type ActiveHoverableProps = Omit; + +// TODO: Do we really need distinction between onHover* and onMouse*? + +function ActiveHoverable({onHoverIn, onHoverOut, shouldHandleScroll, children, ...props}: ActiveHoverableProps, ref: Ref) { + const [isHovered, setIsHovered] = useState(false); + const isScrolling = useRef(false); + const isHoveredRef = useRef(false); + + const updateIsHovered = useCallback( + (hovered: boolean) => { + isHoveredRef.current = hovered; + if (shouldHandleScroll && isScrolling.current) { + return; + } + setIsHovered(hovered); + }, + [shouldHandleScroll], + ); + + useEffect(() => (isHovered ? onHoverIn?.() : onHoverOut?.()), [isHovered, onHoverIn, onHoverOut]); + + useEffect(() => { + if (!shouldHandleScroll) { + return; + } + + const scrollingListener = DeviceEventEmitter.addListener(CONST.EVENTS.SCROLLING, (scrolling) => { + isScrolling.current = scrolling; + if (!isScrolling.current) { + setIsHovered(isHoveredRef.current); + } + }); + + return () => scrollingListener.remove(); + }, [shouldHandleScroll]); + + const child = useMemo(() => (typeof children === 'function' ? children(!isScrolling.current && isHovered) : children), [children, isHovered]); + + const onMouseEnter = useCallback( + (e: MouseEvent) => { + updateIsHovered(true); + props.onMouseEnter?.(e); + child.props.onMouseEnter?.(e); + }, + [updateIsHovered, props, child.props], + ); + + const onMouseLeave = useCallback( + (e: MouseEvent) => { + updateIsHovered(false); + props.onMouseLeave?.(e); + child.props.onMouseLeave?.(e); + }, + [updateIsHovered, props, child.props], + ); + + return cloneElement(child, { + ref, + onMouseEnter, + onMouseLeave, + }); +} + +export default forwardRef(ActiveHoverable); diff --git a/src/components/Hoverable/index.tsx b/src/components/Hoverable/index.tsx index 9c641cfc19be..e99522019c3c 100644 --- a/src/components/Hoverable/index.tsx +++ b/src/components/Hoverable/index.tsx @@ -1,212 +1,20 @@ -import React, {ForwardedRef, forwardRef, MutableRefObject, ReactElement, RefAttributes, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react'; -import {DeviceEventEmitter} from 'react-native'; -import * as DeviceCapabilities from '@libs/DeviceCapabilities'; -import CONST from '@src/CONST'; +import React, {cloneElement, forwardRef, Ref} from 'react'; +import ActiveHoverable from './ActiveHoverable'; import HoverableProps from './types'; -/** - * Maps the children of a Hoverable component to - * - a function that is called with the parameter - * - the child itself if it is the only child - * @param children The children to map. - * @param callbackParam The parameter to pass to the children function. - * @returns The mapped children. - */ -function mapChildren(children: ((isHovered: boolean) => ReactElement) | ReactElement | ReactElement[], callbackParam: boolean): ReactElement & RefAttributes { - if (Array.isArray(children)) { - return children[0]; +function Hoverable({disabled, ...props}: HoverableProps, ref: Ref) { + // If Hoverable is disabled, just render the child without additional logic or event listeners. + if (disabled) { + return cloneElement(typeof props.children === 'function' ? props.children(false) : props.children, {ref}); } - if (typeof children === 'function') { - return children(callbackParam); - } - - return children; -} - -/** - * Assigns a ref to an element, either by setting the current property of the ref object or by calling the ref function - * @param ref The ref object or function. - * @param element The element to assign the ref to. - */ -function assignRef(ref: ((instance: HTMLElement | null) => void) | MutableRefObject, element: HTMLElement) { - if (!ref) { - return; - } - if (typeof ref === 'function') { - ref(element); - } else if ('current' in ref) { - // eslint-disable-next-line no-param-reassign - ref.current = element; - } -} - -/** - * It is necessary to create a Hoverable component instead of relying solely on Pressable support for hover state, - * because nesting Pressables causes issues where the hovered state of the child cannot be easily propagated to the - * parent. https://github.com/necolas/react-native-web/issues/1875 - */ -function Hoverable( - {disabled = false, onHoverIn = () => {}, onHoverOut = () => {}, onMouseEnter = () => {}, onMouseLeave = () => {}, children, shouldHandleScroll = false}: HoverableProps, - outerRef: ForwardedRef, -) { - const [isHovered, setIsHovered] = useState(false); - - const isScrolling = useRef(false); - const isHoveredRef = useRef(false); - const ref = useRef(null); - - const updateIsHoveredOnScrolling = useCallback( - (hovered: boolean) => { - if (disabled) { - return; - } - - isHoveredRef.current = hovered; - - if (shouldHandleScroll && isScrolling.current) { - return; - } - setIsHovered(hovered); - }, - [disabled, shouldHandleScroll], + return ( + ); - - useEffect(() => { - const unsetHoveredWhenDocumentIsHidden = () => document.visibilityState === 'hidden' && setIsHovered(false); - - document.addEventListener('visibilitychange', unsetHoveredWhenDocumentIsHidden); - - return () => document.removeEventListener('visibilitychange', unsetHoveredWhenDocumentIsHidden); - }, []); - - useEffect(() => { - if (!shouldHandleScroll) { - return; - } - - const scrollingListener = DeviceEventEmitter.addListener(CONST.EVENTS.SCROLLING, (scrolling) => { - isScrolling.current = scrolling; - if (!scrolling) { - setIsHovered(isHoveredRef.current); - } - }); - - return () => scrollingListener.remove(); - }, [shouldHandleScroll]); - - useEffect(() => { - if (!DeviceCapabilities.hasHoverSupport()) { - return; - } - - /** - * Checks the hover state of a component and updates it based on the event target. - * This is necessary to handle cases where the hover state might get stuck due to an unreliable mouseleave trigger, - * such as when an element is removed before the mouseleave event is triggered. - * @param event The hover event object. - */ - const unsetHoveredIfOutside = (event: MouseEvent) => { - if (!ref.current || !isHovered) { - return; - } - - if (ref.current.contains(event.target as Node)) { - return; - } - - setIsHovered(false); - }; - - document.addEventListener('mouseover', unsetHoveredIfOutside); - - return () => document.removeEventListener('mouseover', unsetHoveredIfOutside); - }, [isHovered]); - - useEffect(() => { - if (!disabled || !isHovered) { - return; - } - setIsHovered(false); - }, [disabled, isHovered]); - - useEffect(() => { - if (disabled) { - return; - } - if (onHoverIn && isHovered) { - return onHoverIn(); - } - if (onHoverOut && !isHovered) { - return onHoverOut(); - } - }, [disabled, isHovered, onHoverIn, onHoverOut]); - - // Expose inner ref to parent through outerRef. This enable us to use ref both in parent and child. - useImperativeHandle(outerRef, () => ref.current, []); - - const child = useMemo(() => React.Children.only(mapChildren(children, isHovered)), [children, isHovered]); - - const enableHoveredOnMouseEnter = useCallback( - (event: MouseEvent) => { - updateIsHoveredOnScrolling(true); - onMouseEnter(event); - - if (typeof child.props.onMouseEnter === 'function') { - child.props.onMouseEnter(event); - } - }, - [child.props, onMouseEnter, updateIsHoveredOnScrolling], - ); - - const disableHoveredOnMouseLeave = useCallback( - (event: MouseEvent) => { - updateIsHoveredOnScrolling(false); - onMouseLeave(event); - - if (typeof child.props.onMouseLeave === 'function') { - child.props.onMouseLeave(event); - } - }, - [child.props, onMouseLeave, updateIsHoveredOnScrolling], - ); - - const disableHoveredOnBlur = useCallback( - (event: MouseEvent) => { - // Check if the blur event occurred due to clicking outside the element - // and the wrapperView contains the element that caused the blur and reset isHovered - if (!ref.current?.contains(event.target as Node) && !ref.current?.contains(event.relatedTarget as Node)) { - setIsHovered(false); - } - - if (typeof child.props.onBlur === 'function') { - child.props.onBlur(event); - } - }, - [child.props], - ); - - // We need to access the ref of a children from both parent and current component - // So we pass it to current ref and assign it once again to the child ref prop - const hijackRef = (el: HTMLElement) => { - ref.current = el; - if (child.ref) { - assignRef(child.ref, el); - } - }; - - if (!DeviceCapabilities.hasHoverSupport()) { - return React.cloneElement(child, { - ref: hijackRef, - }); - } - - return React.cloneElement(child, { - ref: hijackRef, - onMouseEnter: enableHoveredOnMouseEnter, - onMouseLeave: disableHoveredOnMouseLeave, - onBlur: disableHoveredOnBlur, - }); } export default forwardRef(Hoverable); From cb8a310f0a5ca422302bc8eae672373ff2f94388 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Miko=C5=82ajczak?= Date: Mon, 20 Nov 2023 16:14:44 +0100 Subject: [PATCH 038/130] unset Hovered when document visibility has changed --- src/components/Hoverable/ActiveHoverable.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/components/Hoverable/ActiveHoverable.tsx b/src/components/Hoverable/ActiveHoverable.tsx index b77b2a60a2fb..7e592a666905 100644 --- a/src/components/Hoverable/ActiveHoverable.tsx +++ b/src/components/Hoverable/ActiveHoverable.tsx @@ -5,8 +5,6 @@ import HoverableProps from './types'; type ActiveHoverableProps = Omit; -// TODO: Do we really need distinction between onHover* and onMouse*? - function ActiveHoverable({onHoverIn, onHoverOut, shouldHandleScroll, children, ...props}: ActiveHoverableProps, ref: Ref) { const [isHovered, setIsHovered] = useState(false); const isScrolling = useRef(false); @@ -40,6 +38,14 @@ function ActiveHoverable({onHoverIn, onHoverOut, shouldHandleScroll, children return () => scrollingListener.remove(); }, [shouldHandleScroll]); + useEffect(() => { + const unsetHoveredWhenDocumentIsHidden = () => document.visibilityState === 'hidden' && setIsHovered(false); + + document.addEventListener('visibilitychange', unsetHoveredWhenDocumentIsHidden); + + return () => document.removeEventListener('visibilitychange', unsetHoveredWhenDocumentIsHidden); + }, []); + const child = useMemo(() => (typeof children === 'function' ? children(!isScrolling.current && isHovered) : children), [children, isHovered]); const onMouseEnter = useCallback( From 86699394ac42de84ec7ed7447108512d469d1505 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Miko=C5=82ajczak?= Date: Mon, 20 Nov 2023 16:19:43 +0100 Subject: [PATCH 039/130] Add missing clarifying comment --- src/components/Hoverable/index.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/components/Hoverable/index.tsx b/src/components/Hoverable/index.tsx index e99522019c3c..456f19e1e524 100644 --- a/src/components/Hoverable/index.tsx +++ b/src/components/Hoverable/index.tsx @@ -2,6 +2,11 @@ import React, {cloneElement, forwardRef, Ref} from 'react'; import ActiveHoverable from './ActiveHoverable'; import HoverableProps from './types'; +/** + * It is necessary to create a Hoverable component instead of relying solely on Pressable support for hover state, + * because nesting Pressables causes issues where the hovered state of the child cannot be easily propagated to the + * parent. https://github.com/necolas/react-native-web/issues/1875 + */ function Hoverable({disabled, ...props}: HoverableProps, ref: Ref) { // If Hoverable is disabled, just render the child without additional logic or event listeners. if (disabled) { From 4ede2250f8e4defa0a546cb9af4eb5af215fff2b Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Tue, 21 Nov 2023 15:36:49 +0700 Subject: [PATCH 040/130] disable auto correct for title task input --- src/pages/tasks/NewTaskDetailsPage.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/tasks/NewTaskDetailsPage.js b/src/pages/tasks/NewTaskDetailsPage.js index 4d0040e01107..d35439ad4d40 100644 --- a/src/pages/tasks/NewTaskDetailsPage.js +++ b/src/pages/tasks/NewTaskDetailsPage.js @@ -109,6 +109,7 @@ function NewTaskDetailsPage(props) { accessibilityLabel={props.translate('task.title')} value={taskTitle} onValueChange={(value) => setTaskTitle(value)} + autoCorrect={false} /> From 87d8979687fd19a16eb685a342231ca60a006728 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Miko=C5=82ajczak?= Date: Tue, 21 Nov 2023 13:36:40 +0100 Subject: [PATCH 041/130] remove onMouse* props from Hoverable --- src/components/Hoverable/ActiveHoverable.tsx | 6 ++---- src/components/Hoverable/types.ts | 6 ------ src/components/Tooltip/BaseTooltip.js | 14 +++++++++++++- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/components/Hoverable/ActiveHoverable.tsx b/src/components/Hoverable/ActiveHoverable.tsx index 7e592a666905..cb504412953b 100644 --- a/src/components/Hoverable/ActiveHoverable.tsx +++ b/src/components/Hoverable/ActiveHoverable.tsx @@ -51,19 +51,17 @@ function ActiveHoverable({onHoverIn, onHoverOut, shouldHandleScroll, children const onMouseEnter = useCallback( (e: MouseEvent) => { updateIsHovered(true); - props.onMouseEnter?.(e); child.props.onMouseEnter?.(e); }, - [updateIsHovered, props, child.props], + [updateIsHovered, child.props], ); const onMouseLeave = useCallback( (e: MouseEvent) => { updateIsHovered(false); - props.onMouseLeave?.(e); child.props.onMouseLeave?.(e); }, - [updateIsHovered, props, child.props], + [updateIsHovered, child.props], ); return cloneElement(child, { diff --git a/src/components/Hoverable/types.ts b/src/components/Hoverable/types.ts index 430b865f50c5..c1c055afe9c7 100644 --- a/src/components/Hoverable/types.ts +++ b/src/components/Hoverable/types.ts @@ -13,12 +13,6 @@ type HoverableProps = { /** Function that executes when the mouse leaves the children. */ onHoverOut?: () => void; - /** Direct pass-through of React's onMouseEnter event. */ - onMouseEnter?: (event: MouseEvent) => void; - - /** Direct pass-through of React's onMouseLeave event. */ - onMouseLeave?: (event: MouseEvent) => void; - /** Decides whether to handle the scroll behaviour to show hover once the scroll ends */ shouldHandleScroll?: boolean; }; diff --git a/src/components/Tooltip/BaseTooltip.js b/src/components/Tooltip/BaseTooltip.js index 3eb905e7a3e5..1aa5fa81e0a4 100644 --- a/src/components/Tooltip/BaseTooltip.js +++ b/src/components/Tooltip/BaseTooltip.js @@ -167,6 +167,16 @@ function Tooltip({children, numberOfLines, maxWidth, text, renderTooltipContent, setIsVisible(false); }, []); + const updateTargetPositionOnMouseEnter = useCallback( + (e) => { + updateTargetAndMousePosition(e); + if (children.props.onMouseEnter) { + children.props.onMouseEnter(e); + } + }, + [children.props, updateTargetAndMousePosition], + ); + // Skip the tooltip and return the children if the text is empty, // we don't have a render function or the device does not support hovering if ((_.isEmpty(text) && renderTooltipContent == null) || !hasHoverSupport) { @@ -205,7 +215,9 @@ function Tooltip({children, numberOfLines, maxWidth, text, renderTooltipContent, onHoverOut={hideTooltip} shouldHandleScroll={shouldHandleScroll} > - {children} + {React.cloneElement(children, { + onMouseEnter: updateTargetPositionOnMouseEnter, + })} From a78b9f8c2d0c94455e68bd5d2b8487a7c41c32c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Miko=C5=82ajczak?= Date: Tue, 21 Nov 2023 14:09:31 +0100 Subject: [PATCH 042/130] remove unused props --- src/components/Hoverable/ActiveHoverable.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Hoverable/ActiveHoverable.tsx b/src/components/Hoverable/ActiveHoverable.tsx index cb504412953b..c03aed5b3d11 100644 --- a/src/components/Hoverable/ActiveHoverable.tsx +++ b/src/components/Hoverable/ActiveHoverable.tsx @@ -5,7 +5,7 @@ import HoverableProps from './types'; type ActiveHoverableProps = Omit; -function ActiveHoverable({onHoverIn, onHoverOut, shouldHandleScroll, children, ...props}: ActiveHoverableProps, ref: Ref) { +function ActiveHoverable({onHoverIn, onHoverOut, shouldHandleScroll, children}: ActiveHoverableProps, ref: Ref) { const [isHovered, setIsHovered] = useState(false); const isScrolling = useRef(false); const isHoveredRef = useRef(false); From 8d53b8f53b988350545a269383bfd0d5cd8edea9 Mon Sep 17 00:00:00 2001 From: Sasha Kluger Date: Tue, 21 Nov 2023 17:21:20 -0800 Subject: [PATCH 043/130] Create Distance-Requests.md New help page for distance requests. It's pretty short and sweet for now, we can add more details and FAQs once new features are added and after we learn what questions people have. --- .../get-paid-back/Distance-Requests.md | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 docs/articles/new-expensify/get-paid-back/Distance-Requests.md diff --git a/docs/articles/new-expensify/get-paid-back/Distance-Requests.md b/docs/articles/new-expensify/get-paid-back/Distance-Requests.md new file mode 100644 index 000000000000..5217e8b08969 --- /dev/null +++ b/docs/articles/new-expensify/get-paid-back/Distance-Requests.md @@ -0,0 +1,30 @@ +--- +title: Distance Requests +description: How to create a distance request and request reimbursement for mileage +--- + + +# Overview + +Expensify allows you to request reimbursement for mileage by creating a distance request from a map. You can send a distance request in Expensify's mobile app, desktop app, or web app. + + +# How to create and send a distance request + +1. Click the green '+' button and select Request Money. +2. Select Distance along the top row of the Request Money window. +3. Enter the Start and Finish addresses then click Next. If there are multiple stops, you can add them before clicking Next. +4. Select your organization's workspace from the list of recent workspaces. +5. On the confirmation page, confirm the amount, Date, and Distance, and optionally enter a Description and Category. Click the Request button. +6. A workspace admin will receive your request and can reimburse you through Expensify or elsewhere! + + + +# FAQs + + +## How can I change the mileage rate used on my distance requests or add an additional rate? + +If you use a group workspace, then you can change the rate in your Workspace settings. In Expensify Classic, go to Settings > Workspaces > [Your Workspace] > Expenses > Distance > Add a Mileage Rate. If you submit mileage expenses on a group workspace, you must be a workspace admin to make changes. + +Individual workspaces offer a single default mileage rate that is aligned with the current IRS mileage reimbursement rate. From 772fae10d4976968f49dcab977be9b5ab9e79787 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Miko=C5=82ajczak?= Date: Wed, 22 Nov 2023 10:51:16 +0100 Subject: [PATCH 044/130] add has hover support in condition --- src/components/Hoverable/index.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/Hoverable/index.tsx b/src/components/Hoverable/index.tsx index 456f19e1e524..dc32c01ce3ab 100644 --- a/src/components/Hoverable/index.tsx +++ b/src/components/Hoverable/index.tsx @@ -1,4 +1,5 @@ import React, {cloneElement, forwardRef, Ref} from 'react'; +import {hasHoverSupport} from '@libs/DeviceCapabilities'; import ActiveHoverable from './ActiveHoverable'; import HoverableProps from './types'; @@ -9,7 +10,8 @@ import HoverableProps from './types'; */ function Hoverable({disabled, ...props}: HoverableProps, ref: Ref) { // If Hoverable is disabled, just render the child without additional logic or event listeners. - if (disabled) { + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + if (disabled || !hasHoverSupport()) { return cloneElement(typeof props.children === 'function' ? props.children(false) : props.children, {ref}); } From 82b7c423949f79336507af57599ee2333d4d52df Mon Sep 17 00:00:00 2001 From: Yauheni Date: Wed, 22 Nov 2023 14:10:30 +0100 Subject: [PATCH 045/130] =?UTF-8?q?Fix=20bug=20with=20Login=E2=80=93Auto-f?= =?UTF-8?q?ill=20list=20appears=20if=20click=20on=20Sign=20in=20here?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/signin/LoginForm/BaseLoginForm.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/pages/signin/LoginForm/BaseLoginForm.js b/src/pages/signin/LoginForm/BaseLoginForm.js index e48446360605..103bd4258f7c 100644 --- a/src/pages/signin/LoginForm/BaseLoginForm.js +++ b/src/pages/signin/LoginForm/BaseLoginForm.js @@ -1,3 +1,4 @@ +import {useNavigation} from '@react-navigation/native'; import {parsePhoneNumber} from 'awesome-phonenumber'; import Str from 'expensify-common/lib/str'; import PropTypes from 'prop-types'; @@ -101,6 +102,7 @@ function LoginForm(props) { const [formError, setFormError] = useState(false); const prevIsVisible = usePrevious(props.isVisible); const firstBlurred = useRef(false); + const navigation = useNavigation(); const {translate} = props; @@ -207,7 +209,7 @@ function LoginForm(props) { if (props.isFocused && props.isVisible) { Session.clearAccountMessages(); } - if (!canFocusInputOnScreenFocus() || !input.current || !props.isVisible) { + if (!canFocusInputOnScreenFocus() || !input.current || !props.isVisible || !navigation.isFocused()) { return; } let focusTimeout; From 827643872806a7d3942b173583aa917f97087e34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Miko=C5=82ajczak?= Date: Wed, 22 Nov 2023 20:09:01 +0100 Subject: [PATCH 046/130] add unsetHoveredIfOutside functionality --- src/components/Hoverable/ActiveHoverable.tsx | 61 +++++++++++++++++++- src/components/Hoverable/types.ts | 4 +- 2 files changed, 60 insertions(+), 5 deletions(-) diff --git a/src/components/Hoverable/ActiveHoverable.tsx b/src/components/Hoverable/ActiveHoverable.tsx index c03aed5b3d11..2a6351f88022 100644 --- a/src/components/Hoverable/ActiveHoverable.tsx +++ b/src/components/Hoverable/ActiveHoverable.tsx @@ -1,12 +1,31 @@ -import {cloneElement, forwardRef, Ref, useCallback, useEffect, useMemo, useRef, useState} from 'react'; +import {cloneElement, forwardRef, MutableRefObject, Ref, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react'; import {DeviceEventEmitter} from 'react-native'; import CONST from '@src/CONST'; import HoverableProps from './types'; +/** + * Assigns a ref to an element, either by setting the current property of the ref object or by calling the ref function + * @param ref The ref object or function. + * @param element The element to assign the ref to. + */ +function assignRef(ref: ((instance: HTMLElement | null) => void) | MutableRefObject, element: HTMLElement) { + if (!ref) { + return; + } + if (typeof ref === 'function') { + ref(element); + } else if ('current' in ref) { + // eslint-disable-next-line no-param-reassign + ref.current = element; + } +} + type ActiveHoverableProps = Omit; -function ActiveHoverable({onHoverIn, onHoverOut, shouldHandleScroll, children}: ActiveHoverableProps, ref: Ref) { +function ActiveHoverable({onHoverIn, onHoverOut, shouldHandleScroll, children}: ActiveHoverableProps, outerRef: Ref) { const [isHovered, setIsHovered] = useState(false); + + const ref = useRef(null); const isScrolling = useRef(false); const isHoveredRef = useRef(false); @@ -21,6 +40,9 @@ function ActiveHoverable({onHoverIn, onHoverOut, shouldHandleScroll, children [shouldHandleScroll], ); + // Expose inner ref to parent through outerRef. This enable us to use ref both in parent and child. + useImperativeHandle(outerRef, () => ref.current, []); + useEffect(() => (isHovered ? onHoverIn?.() : onHoverOut?.()), [isHovered, onHoverIn, onHoverOut]); useEffect(() => { @@ -38,6 +60,30 @@ function ActiveHoverable({onHoverIn, onHoverOut, shouldHandleScroll, children return () => scrollingListener.remove(); }, [shouldHandleScroll]); + useEffect(() => { + /** + * Checks the hover state of a component and updates it based on the event target. + * This is necessary to handle cases where the hover state might get stuck due to an unreliable mouseleave trigger, + * such as when an element is removed before the mouseleave event is triggered. + * @param event The hover event object. + */ + const unsetHoveredIfOutside = (event: MouseEvent) => { + if (!ref.current || !isHovered) { + return; + } + + if (ref.current.contains(event.target as Node)) { + return; + } + + setIsHovered(false); + }; + + document.addEventListener('mouseover', unsetHoveredIfOutside); + + return () => document.removeEventListener('mouseover', unsetHoveredIfOutside); + }, [isHovered, ref]); + useEffect(() => { const unsetHoveredWhenDocumentIsHidden = () => document.visibilityState === 'hidden' && setIsHovered(false); @@ -64,8 +110,17 @@ function ActiveHoverable({onHoverIn, onHoverOut, shouldHandleScroll, children [updateIsHovered, child.props], ); + // We need to access the ref of a children from both parent and current component + // So we pass it to current ref and assign it once again to the child ref prop + const hijackRef = (el: HTMLElement) => { + ref.current = el; + if (child.ref) { + assignRef(child.ref, el); + } + }; + return cloneElement(child, { - ref, + ref: hijackRef, onMouseEnter, onMouseLeave, }); diff --git a/src/components/Hoverable/types.ts b/src/components/Hoverable/types.ts index c1c055afe9c7..8008c5dae0cf 100644 --- a/src/components/Hoverable/types.ts +++ b/src/components/Hoverable/types.ts @@ -1,8 +1,8 @@ -import {ReactElement} from 'react'; +import {ReactElement, RefAttributes} from 'react'; type HoverableProps = { /** Children to wrap with Hoverable. */ - children: ((isHovered: boolean) => ReactElement) | ReactElement; + children: ((isHovered: boolean) => ReactElement & RefAttributes) | (ReactElement & RefAttributes); /** Whether to disable the hover action */ disabled?: boolean; From 1f910d22fc7154b4172b47274b374dd99389fb8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Miko=C5=82ajczak?= Date: Wed, 22 Nov 2023 20:10:55 +0100 Subject: [PATCH 047/130] add onBlur handler --- src/components/Hoverable/ActiveHoverable.tsx | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/components/Hoverable/ActiveHoverable.tsx b/src/components/Hoverable/ActiveHoverable.tsx index 2a6351f88022..741e012f4601 100644 --- a/src/components/Hoverable/ActiveHoverable.tsx +++ b/src/components/Hoverable/ActiveHoverable.tsx @@ -110,6 +110,19 @@ function ActiveHoverable({onHoverIn, onHoverOut, shouldHandleScroll, children}: [updateIsHovered, child.props], ); + const disableHoveredOnBlur = useCallback( + (event: MouseEvent) => { + // Check if the blur event occurred due to clicking outside the element + // and the wrapperView contains the element that caused the blur and reset isHovered + if (!ref.current?.contains(event.target as Node) && !ref.current?.contains(event.relatedTarget as Node)) { + setIsHovered(false); + } + + child.props.onBlur?.(event); + }, + [child.props], + ); + // We need to access the ref of a children from both parent and current component // So we pass it to current ref and assign it once again to the child ref prop const hijackRef = (el: HTMLElement) => { @@ -123,6 +136,7 @@ function ActiveHoverable({onHoverIn, onHoverOut, shouldHandleScroll, children}: ref: hijackRef, onMouseEnter, onMouseLeave, + onBlur: disableHoveredOnBlur, }); } From 3823435ee2870e4392b5d088321695e1d3f9a272 Mon Sep 17 00:00:00 2001 From: Sasha Kluger Date: Wed, 22 Nov 2023 13:27:57 -0800 Subject: [PATCH 048/130] Update Distance-Requests.md Updated doc based on @neil-marcellini's comments. --- .../new-expensify/get-paid-back/Distance-Requests.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/docs/articles/new-expensify/get-paid-back/Distance-Requests.md b/docs/articles/new-expensify/get-paid-back/Distance-Requests.md index 5217e8b08969..c8060be13cea 100644 --- a/docs/articles/new-expensify/get-paid-back/Distance-Requests.md +++ b/docs/articles/new-expensify/get-paid-back/Distance-Requests.md @@ -6,7 +6,7 @@ description: How to create a distance request and request reimbursement for mile # Overview -Expensify allows you to request reimbursement for mileage by creating a distance request from a map. You can send a distance request in Expensify's mobile app, desktop app, or web app. +Expensify allows you to request reimbursement for mileage by creating a distance request from a map. You can send a distance request in Expensify's mobile, desktop, or web app. # How to create and send a distance request @@ -14,8 +14,8 @@ Expensify allows you to request reimbursement for mileage by creating a distance 1. Click the green '+' button and select Request Money. 2. Select Distance along the top row of the Request Money window. 3. Enter the Start and Finish addresses then click Next. If there are multiple stops, you can add them before clicking Next. -4. Select your organization's workspace from the list of recent workspaces. -5. On the confirmation page, confirm the amount, Date, and Distance, and optionally enter a Description and Category. Click the Request button. +4. Choose who to send the request to by selecting your organization's workspace from the list of recent workspaces. +5. On the confirmation page, confirm the amount, date, and distance, and optionally add additional information such as a description or category. Click the Request button. 6. A workspace admin will receive your request and can reimburse you through Expensify or elsewhere! @@ -25,6 +25,4 @@ Expensify allows you to request reimbursement for mileage by creating a distance ## How can I change the mileage rate used on my distance requests or add an additional rate? -If you use a group workspace, then you can change the rate in your Workspace settings. In Expensify Classic, go to Settings > Workspaces > [Your Workspace] > Expenses > Distance > Add a Mileage Rate. If you submit mileage expenses on a group workspace, you must be a workspace admin to make changes. - -Individual workspaces offer a single default mileage rate that is aligned with the current IRS mileage reimbursement rate. +If you use a group workspace, then you can edit the default distance rate or add additional rates in your Workspace settings. In Expensify Classic, go to Settings > Workspaces > [Your Workspace] > Expenses and scroll down to the Distance section to update or add mileage rates. You must be a workspace admin to modify or add rates. From 4e7f09ad359d492937cfa9598985ae5758485808 Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Thu, 23 Nov 2023 09:41:00 +0100 Subject: [PATCH 049/130] add lazy imports for AuthScreens --- src/libs/Navigation/AppNavigator/AuthScreens.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.js b/src/libs/Navigation/AppNavigator/AuthScreens.js index aedb2fa8d741..0fc78f0cb324 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.js +++ b/src/libs/Navigation/AppNavigator/AuthScreens.js @@ -14,7 +14,6 @@ import PusherConnectionManager from '@libs/PusherConnectionManager'; import * as SessionUtils from '@libs/SessionUtils'; import DemoSetupPage from '@pages/DemoSetupPage'; import NotFoundPage from '@pages/ErrorPage/NotFoundPage'; -import DesktopSignInRedirectPage from '@pages/signin/DesktopSignInRedirectPage'; import useThemeStyles from '@styles/useThemeStyles'; import * as App from '@userActions/App'; import * as Download from '@userActions/Download'; @@ -34,13 +33,14 @@ import createCustomStackNavigator from './createCustomStackNavigator'; import defaultScreenOptions from './defaultScreenOptions'; import getRootNavigatorScreenOptions from './getRootNavigatorScreenOptions'; import CentralPaneNavigator from './Navigators/CentralPaneNavigator'; -import RightModalNavigator from './Navigators/RightModalNavigator'; const loadReportAttachments = () => require('../../../pages/home/report/ReportAttachments').default; +const loadRightModalNavigator = () => require('./Navigators/RightModalNavigator').default; const loadSidebarScreen = () => require('../../../pages/home/sidebar/SidebarScreen').default; const loadValidateLoginPage = () => require('../../../pages/ValidateLoginPage').default; const loadLogOutPreviousUserPage = () => require('../../../pages/LogOutPreviousUserPage').default; const loadConciergePage = () => require('../../../pages/ConciergePage').default; +const loadDesktopSignInRedirectPage = () => require('../../../pages/signin/DesktopSignInRedirectPage').default; let timezone; let currentAccountID; @@ -332,13 +332,13 @@ function AuthScreens({isUsingMemoryOnlyKeys, lastUpdateIDAppliedToClient, sessio From 9a958eb812bf006aa185bc2062e4b1beb9f39dff Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Thu, 23 Nov 2023 11:32:13 +0100 Subject: [PATCH 050/130] return RightModalNavigator --- src/libs/Navigation/AppNavigator/AuthScreens.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.js b/src/libs/Navigation/AppNavigator/AuthScreens.js index 0fc78f0cb324..1a4873db0a42 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.js +++ b/src/libs/Navigation/AppNavigator/AuthScreens.js @@ -33,9 +33,9 @@ import createCustomStackNavigator from './createCustomStackNavigator'; import defaultScreenOptions from './defaultScreenOptions'; import getRootNavigatorScreenOptions from './getRootNavigatorScreenOptions'; import CentralPaneNavigator from './Navigators/CentralPaneNavigator'; +import RightModalNavigator from './Navigators/RightModalNavigator'; const loadReportAttachments = () => require('../../../pages/home/report/ReportAttachments').default; -const loadRightModalNavigator = () => require('./Navigators/RightModalNavigator').default; const loadSidebarScreen = () => require('../../../pages/home/sidebar/SidebarScreen').default; const loadValidateLoginPage = () => require('../../../pages/ValidateLoginPage').default; const loadLogOutPreviousUserPage = () => require('../../../pages/LogOutPreviousUserPage').default; @@ -332,7 +332,7 @@ function AuthScreens({isUsingMemoryOnlyKeys, lastUpdateIDAppliedToClient, sessio Date: Thu, 23 Nov 2023 12:00:51 +0100 Subject: [PATCH 051/130] export SupportedLanguage --- src/libs/EmojiTrie.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/EmojiTrie.ts b/src/libs/EmojiTrie.ts index a45d1bc45b33..100d24a01546 100644 --- a/src/libs/EmojiTrie.ts +++ b/src/libs/EmojiTrie.ts @@ -128,4 +128,4 @@ const emojiTrie: EmojiTrie = supportedLanguages.reduce((prev, cur) => ({...prev, Timing.end(CONST.TIMING.TRIE_INITIALIZATION); export default emojiTrie; -export type {SimpleEmoji}; +export type {SimpleEmoji, SupportedLanguage}; From dad435b6c607376c3efc138dced1fdfd40c4ce84 Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Thu, 23 Nov 2023 12:01:31 +0100 Subject: [PATCH 052/130] lazy import of emojisTrie --- src/libs/EmojiUtils.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/libs/EmojiUtils.ts b/src/libs/EmojiUtils.ts index 22e84921b1ee..db515f7d7703 100644 --- a/src/libs/EmojiUtils.ts +++ b/src/libs/EmojiUtils.ts @@ -8,7 +8,7 @@ import {Emoji, HeaderEmoji, PickerEmojis} from '@assets/emojis/types'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import {FrequentlyUsedEmoji} from '@src/types/onyx'; -import emojisTrie from './EmojiTrie'; +import {SupportedLanguage} from './EmojiTrie'; type HeaderIndice = {code: string; index: number; icon: React.FC}; type EmojiSpacer = {code: string; spacer: boolean}; @@ -322,6 +322,9 @@ function getAddedEmojis(currentEmojis: Emoji[], formerEmojis: Emoji[]): Emoji[] * If we're on mobile, we also add a space after the emoji granted there's no text after it. */ function replaceEmojis(text: string, preferredSkinTone = CONST.EMOJI_DEFAULT_SKIN_TONE, lang: 'en' | 'es' = CONST.LOCALES.DEFAULT): ReplacedEmoji { + // emojisTrie importing the emoji json file on app starting and we want to avoid it + const emojisTrie = require('./EmojiTrie').default; + const trie = emojisTrie[lang]; if (!trie) { return {text, emojis: []}; @@ -396,7 +399,10 @@ function replaceAndExtractEmojis(text: string, preferredSkinTone = CONST.EMOJI_D * Suggest emojis when typing emojis prefix after colon * @param [limit] - matching emojis limit */ -function suggestEmojis(text: string, lang: keyof typeof emojisTrie, limit = CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMOUNT_OF_SUGGESTIONS): Emoji[] | undefined { +function suggestEmojis(text: string, lang: keyof SupportedLanguage, limit = CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMOUNT_OF_SUGGESTIONS): Emoji[] | undefined { + // emojisTrie importing the emoji json file on app starting and we want to avoid it + const emojisTrie = require('./EmojiTrie').default; + const trie = emojisTrie[lang]; if (!trie) { return []; From fa945daa32f38ac9631c2343c3f27c37fc7b4f39 Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Thu, 23 Nov 2023 14:25:05 +0100 Subject: [PATCH 053/130] lazy BankIcons --- src/components/Icon/BankIcons.ts | 179 +++++++++++-------------------- 1 file changed, 62 insertions(+), 117 deletions(-) diff --git a/src/components/Icon/BankIcons.ts b/src/components/Icon/BankIcons.ts index 5d17e7f0c991..eaf08b93ddd1 100644 --- a/src/components/Icon/BankIcons.ts +++ b/src/components/Icon/BankIcons.ts @@ -1,46 +1,8 @@ import {CSSProperties} from 'react'; import {ViewStyle} from 'react-native'; import {SvgProps} from 'react-native-svg'; -import AmericanExpress from '@assets/images/bankicons/american-express.svg'; -import BankOfAmerica from '@assets/images/bankicons/bank-of-america.svg'; -import BB_T from '@assets/images/bankicons/bb-t.svg'; -import CapitalOne from '@assets/images/bankicons/capital-one.svg'; -import CharlesSchwab from '@assets/images/bankicons/charles-schwab.svg'; -import Chase from '@assets/images/bankicons/chase.svg'; -import CitiBank from '@assets/images/bankicons/citibank.svg'; -import CitizensBank from '@assets/images/bankicons/citizens-bank.svg'; -import Discover from '@assets/images/bankicons/discover.svg'; -import Fidelity from '@assets/images/bankicons/fidelity.svg'; import GenericBank from '@assets/images/bankicons/generic-bank-account.svg'; -import HuntingtonBank from '@assets/images/bankicons/huntington-bank.svg'; -import NavyFederalCreditUnion from '@assets/images/bankicons/navy-federal-credit-union.svg'; -import PNC from '@assets/images/bankicons/pnc.svg'; -import RegionsBank from '@assets/images/bankicons/regions-bank.svg'; -import SunTrust from '@assets/images/bankicons/suntrust.svg'; -import TdBank from '@assets/images/bankicons/td-bank.svg'; -import USBank from '@assets/images/bankicons/us-bank.svg'; -import USAA from '@assets/images/bankicons/usaa.svg'; -// Card Icons -import AmericanExpressCard from '@assets/images/cardicons/american-express.svg'; -import BankOfAmericaCard from '@assets/images/cardicons/bank-of-america.svg'; -import BB_TCard from '@assets/images/cardicons/bb-t.svg'; -import CapitalOneCard from '@assets/images/cardicons/capital-one.svg'; -import CharlesSchwabCard from '@assets/images/cardicons/charles-schwab.svg'; -import ChaseCard from '@assets/images/cardicons/chase.svg'; -import CitiBankCard from '@assets/images/cardicons/citibank.svg'; -import CitizensBankCard from '@assets/images/cardicons/citizens.svg'; -import DiscoverCard from '@assets/images/cardicons/discover.svg'; -import ExpensifyCardImage from '@assets/images/cardicons/expensify-card-dark.svg'; -import FidelityCard from '@assets/images/cardicons/fidelity.svg'; import GenericBankCard from '@assets/images/cardicons/generic-bank-card.svg'; -import HuntingtonBankCard from '@assets/images/cardicons/huntington-bank.svg'; -import NavyFederalCreditUnionCard from '@assets/images/cardicons/navy-federal-credit-union.svg'; -import PNCCard from '@assets/images/cardicons/pnc.svg'; -import RegionsBankCard from '@assets/images/cardicons/regions-bank.svg'; -import SunTrustCard from '@assets/images/cardicons/suntrust.svg'; -import TdBankCard from '@assets/images/cardicons/td-bank.svg'; -import USBankCard from '@assets/images/cardicons/us-bank.svg'; -import USAACard from '@assets/images/cardicons/usaa.svg'; import styles from '@styles/styles'; import variables from '@styles/variables'; @@ -52,101 +14,84 @@ type BankIcon = { iconStyles?: Array; }; +const BANK_NAMES = { + EXPENSIFY: 'expensify', + AMERICAN_EXPRESS: 'americanexpress', + BANK_OF_AMERICA: 'bank of america', + BB_T: 'bbt', + CAPITAL_ONE: 'capital one', + CHASE: 'chase', + CHARLES_SCHWAB: 'charles schwab', + CITIBANK: 'citibank', + CITIZENS_BANK: 'citizens bank', + DISCOVER: 'discover', + FIDELITY: 'fidelity', + GENERIC_BANK: 'generic bank', + HUNTINGTON_BANK: 'huntington bank', + NAVY_FEDERAL_CREDIT_UNION: 'navy federal credit union', + PNC: 'pnc', + REGIONS_BANK: 'regions bank', + SUNTRUST: 'suntrust', + TD_BANK: 'td bank', + US_BANK: 'us bank', + USAA: 'usaa', +}; + +type BankName = keyof typeof BANK_NAMES; + /** * Returns matching asset icon for bankName */ -function getAssetIcon(bankName: string, isCard: boolean): React.FC { - if (bankName.includes('expensify')) { - return ExpensifyCardImage; - } - - if (bankName.includes('americanexpress')) { - return isCard ? AmericanExpressCard : AmericanExpress; - } - - if (bankName.includes('bank of america') || bankName.includes('bankofamerica')) { - return isCard ? BankOfAmericaCard : BankOfAmerica; - } - - if (bankName.startsWith('bbt')) { - return isCard ? BB_TCard : BB_T; - } - - if (bankName.startsWith('capital one') || bankName.includes('capitalone')) { - return isCard ? CapitalOneCard : CapitalOne; - } - - if (bankName.startsWith('chase') || bankName.includes('chase')) { - return isCard ? ChaseCard : Chase; - } - - if (bankName.includes('charles schwab') || bankName.includes('charlesschwab')) { - return isCard ? CharlesSchwabCard : CharlesSchwab; - } - - if (bankName.startsWith('citibank') || bankName.includes('citibank')) { - return isCard ? CitiBankCard : CitiBank; - } - - if (bankName.startsWith('citizens bank') || bankName.includes('citizensbank')) { - return isCard ? CitizensBankCard : CitizensBank; - } - - if (bankName.startsWith('discover ') || bankName.includes('discover.') || bankName === 'discover') { - return isCard ? DiscoverCard : Discover; - } - - if (bankName.startsWith('fidelity')) { - return isCard ? FidelityCard : Fidelity; - } - - if (bankName.startsWith('huntington bank') || bankName.includes('huntingtonnational') || bankName.includes('huntington national')) { - return isCard ? HuntingtonBankCard : HuntingtonBank; - } - - if (bankName.startsWith('navy federal credit union') || bankName.includes('navy federal credit union')) { - return isCard ? NavyFederalCreditUnionCard : NavyFederalCreditUnion; - } - - if (bankName.startsWith('pnc') || bankName.includes('pnc')) { - return isCard ? PNCCard : PNC; - } - - if (bankName.startsWith('regions bank') || bankName.includes('regionsbank')) { - return isCard ? RegionsBankCard : RegionsBank; - } - - if (bankName.startsWith('suntrust') || bankName.includes('suntrust')) { - return isCard ? SunTrustCard : SunTrust; - } - - if (bankName.startsWith('td bank') || bankName.startsWith('tdbank') || bankName.includes('tdbank')) { - return isCard ? TdBankCard : TdBank; - } - - if (bankName.startsWith('us bank') || bankName.startsWith('usbank')) { - return isCard ? USBankCard : USBank; - } +function getAssetIcon(bankName: BankName, isCard: boolean): React.FC { + // Mapping bank names to their respective icon paths + const iconMappings = { + [BANK_NAMES.EXPENSIFY]: isCard ? require('@assets/images/cardicons/expensify-card-dark.svg') : require('@assets/images/bankicons/expensify.svg'), + [BANK_NAMES.AMERICAN_EXPRESS]: isCard ? require('@assets/images/cardicons/american-express.svg') : require('@assets/images/bankicons/american-express.svg'), + [BANK_NAMES.BANK_OF_AMERICA]: isCard ? require('@assets/images/cardicons/bank-of-america.svg') : require('@assets/images/bankicons/bank-of-america.svg'), + [BANK_NAMES.BB_T]: isCard ? require('@assets/images/cardicons/bb-t.svg') : require('@assets/images/bankicons/bb-t.svg'), + [BANK_NAMES.CAPITAL_ONE]: isCard ? require('@assets/images/cardicons/capital-one.svg') : require('@assets/images/bankicons/capital-one.svg'), + [BANK_NAMES.CHASE]: isCard ? require('@assets/images/cardicons/chase.svg') : require('@assets/images/bankicons/chase.svg'), + [BANK_NAMES.CHARLES_SCHWAB]: isCard ? require('@assets/images/cardicons/charles-schwab.svg') : require('@assets/images/bankicons/charles-schwab.svg'), + [BANK_NAMES.CITIBANK]: isCard ? require('@assets/images/cardicons/citibank.svg') : require('@assets/images/bankicons/citibank.svg'), + [BANK_NAMES.CITIZENS_BANK]: isCard ? require('@assets/images/cardicons/citizens.svg') : require('@assets/images/bankicons/citizens-bank.svg'), + [BANK_NAMES.DISCOVER]: isCard ? require('@assets/images/cardicons/discover.svg') : require('@assets/images/bankicons/discover.svg'), + [BANK_NAMES.FIDELITY]: isCard ? require('@assets/images/cardicons/fidelity.svg') : require('@assets/images/bankicons/fidelity.svg'), + [BANK_NAMES.GENERIC_BANK]: isCard ? require('@assets/images/cardicons/generic-bank-card.svg') : require('@assets/images/bankicons/generic-bank-account.svg'), + [BANK_NAMES.HUNTINGTON_BANK]: isCard ? require('@assets/images/cardicons/huntington-bank.svg') : require('@assets/images/bankicons/huntington-bank.svg'), + [BANK_NAMES.NAVY_FEDERAL_CREDIT_UNION]: isCard + ? require('@assets/images/cardicons/navy-federal-credit-union.svg') + : require('@assets/images/bankicons/navy-federal-credit-union.svg'), + [BANK_NAMES.PNC]: isCard ? require('@assets/images/cardicons/pnc.svg') : require('@assets/images/bankicons/pnc.svg'), + [BANK_NAMES.REGIONS_BANK]: isCard ? require('@assets/images/cardicons/regions-bank.svg') : require('@assets/images/bankicons/regions-bank.svg'), + [BANK_NAMES.SUNTRUST]: isCard ? require('@assets/images/cardicons/suntrust.svg') : require('@assets/images/bankicons/suntrust.svg'), + [BANK_NAMES.TD_BANK]: isCard ? require('@assets/images/cardicons/td-bank.svg') : require('@assets/images/bankicons/td-bank.svg'), + [BANK_NAMES.US_BANK]: isCard ? require('@assets/images/cardicons/us-bank.svg') : require('@assets/images/bankicons/us-bank.svg'), + [BANK_NAMES.USAA]: isCard ? require('@assets/images/cardicons/usaa.svg') : require('@assets/images/bankicons/usaa.svg'), + }; - if (bankName.includes('usaa')) { - return isCard ? USAACard : USAA; - } + // Fallback to generic bank/card icon + const iconModule = iconMappings[bankName] || (isCard ? '@assets/images/cardicons/generic-bank-card.svg' : '@assets/images/bankicons/generic-bank-account.svg'); + return iconModule as React.FC; +} - return isCard ? GenericBankCard : GenericBank; +function getBankNameKey(bankName: string): BankName | undefined { + return Object.keys(BANK_NAMES).find((key) => BANK_NAMES[key as BankName].toLowerCase() === bankName) as BankName | undefined; } /** * Returns Bank Icon Object that matches to existing bank icons or default icons */ -export default function getBankIcon(bankName: string, isCard = false): BankIcon { +export default function getBankIcon(bankName: BankName, isCard = false): BankIcon { + const bankNameKey = getBankNameKey(bankName.toLowerCase()); + const bankIcon: BankIcon = { icon: isCard ? GenericBankCard : GenericBank, }; - if (bankName) { - bankIcon.icon = getAssetIcon(bankName.toLowerCase(), isCard); + if (bankNameKey && Object.keys(BANK_NAMES).includes(bankNameKey)) { + bankIcon.icon = getAssetIcon(bankNameKey, isCard); } // For default Credit Card icon the icon size should not be set. From a83117b9b407cd4cc2d6ec9a710d8efcfa22ea7e Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Thu, 23 Nov 2023 16:11:04 +0100 Subject: [PATCH 054/130] add BankName type --- src/components/Icon/BankIcons.ts | 11 +++++++---- src/types/onyx/AccountData.ts | 3 ++- src/types/onyx/Fund.ts | 3 ++- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/components/Icon/BankIcons.ts b/src/components/Icon/BankIcons.ts index eaf08b93ddd1..9087582b3997 100644 --- a/src/components/Icon/BankIcons.ts +++ b/src/components/Icon/BankIcons.ts @@ -37,13 +37,14 @@ const BANK_NAMES = { USAA: 'usaa', }; +type BankNameAndEmptyString = keyof typeof BANK_NAMES | ''; type BankName = keyof typeof BANK_NAMES; /** * Returns matching asset icon for bankName */ -function getAssetIcon(bankName: BankName, isCard: boolean): React.FC { +function getAssetIcon(bankName: BankNameAndEmptyString, isCard: boolean): React.FC { // Mapping bank names to their respective icon paths const iconMappings = { [BANK_NAMES.EXPENSIFY]: isCard ? require('@assets/images/cardicons/expensify-card-dark.svg') : require('@assets/images/bankicons/expensify.svg'), @@ -75,15 +76,15 @@ function getAssetIcon(bankName: BankName, isCard: boolean): React.FC { return iconModule as React.FC; } -function getBankNameKey(bankName: string): BankName | undefined { - return Object.keys(BANK_NAMES).find((key) => BANK_NAMES[key as BankName].toLowerCase() === bankName) as BankName | undefined; +function getBankNameKey(bankName: string): BankNameAndEmptyString | undefined { + return Object.keys(BANK_NAMES).find((key) => BANK_NAMES[key as BankName].toLowerCase() === bankName) as BankNameAndEmptyString | undefined; } /** * Returns Bank Icon Object that matches to existing bank icons or default icons */ -export default function getBankIcon(bankName: BankName, isCard = false): BankIcon { +export default function getBankIcon(bankName: BankNameAndEmptyString, isCard = false): BankIcon { const bankNameKey = getBankNameKey(bankName.toLowerCase()); const bankIcon: BankIcon = { @@ -106,3 +107,5 @@ export default function getBankIcon(bankName: BankName, isCard = false): BankIco return bankIcon; } + +export type {BankName}; diff --git a/src/types/onyx/AccountData.ts b/src/types/onyx/AccountData.ts index 79484e7886af..c9ffcc34c659 100644 --- a/src/types/onyx/AccountData.ts +++ b/src/types/onyx/AccountData.ts @@ -1,10 +1,11 @@ +import {BankName} from '@components/Icon/BankIcons'; import * as OnyxCommon from './OnyxCommon'; type AdditionalData = { isP2PDebitCard?: boolean; beneficialOwners?: string[]; currency?: string; - bankName?: string; + bankName?: BankName; fieldsType?: string; country?: string; }; diff --git a/src/types/onyx/Fund.ts b/src/types/onyx/Fund.ts index 4e6cbc695a8d..d1d27cce5d71 100644 --- a/src/types/onyx/Fund.ts +++ b/src/types/onyx/Fund.ts @@ -1,3 +1,4 @@ +import {BankName} from '@components/Icon/BankIcons'; import CONST from '@src/CONST'; import * as OnyxCommon from './OnyxCommon'; @@ -21,7 +22,7 @@ type AccountData = { created?: string; currency?: string; fundID?: number; - bank?: string; + bank?: BankName; }; type Fund = { From e962db8fee674697d8291804fe16de8503c0b1f7 Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Thu, 23 Nov 2023 16:43:16 +0100 Subject: [PATCH 055/130] fix warning --- src/libs/Navigation/AppNavigator/AuthScreens.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.js b/src/libs/Navigation/AppNavigator/AuthScreens.js index 1a4873db0a42..50a70cd70c56 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.js +++ b/src/libs/Navigation/AppNavigator/AuthScreens.js @@ -40,7 +40,7 @@ const loadSidebarScreen = () => require('../../../pages/home/sidebar/SidebarScre const loadValidateLoginPage = () => require('../../../pages/ValidateLoginPage').default; const loadLogOutPreviousUserPage = () => require('../../../pages/LogOutPreviousUserPage').default; const loadConciergePage = () => require('../../../pages/ConciergePage').default; -const loadDesktopSignInRedirectPage = () => require('../../../pages/signin/DesktopSignInRedirectPage').default; +const LoadDesktopSignInRedirectPage = () => require('../../../pages/signin/DesktopSignInRedirectPage').default; let timezone; let currentAccountID; @@ -338,7 +338,7 @@ function AuthScreens({isUsingMemoryOnlyKeys, lastUpdateIDAppliedToClient, sessio From 1e9d3af2b81437d7458f6f3a19524351602e4017 Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Thu, 23 Nov 2023 17:11:50 +0100 Subject: [PATCH 056/130] clean --- src/libs/EmojiUtils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/EmojiUtils.ts b/src/libs/EmojiUtils.ts index db515f7d7703..49d08fcc1b2c 100644 --- a/src/libs/EmojiUtils.ts +++ b/src/libs/EmojiUtils.ts @@ -322,7 +322,7 @@ function getAddedEmojis(currentEmojis: Emoji[], formerEmojis: Emoji[]): Emoji[] * If we're on mobile, we also add a space after the emoji granted there's no text after it. */ function replaceEmojis(text: string, preferredSkinTone = CONST.EMOJI_DEFAULT_SKIN_TONE, lang: 'en' | 'es' = CONST.LOCALES.DEFAULT): ReplacedEmoji { - // emojisTrie importing the emoji json file on app starting and we want to avoid it + // emojisTrie importing the emoji JSON file on the app starting and we want to avoid it const emojisTrie = require('./EmojiTrie').default; const trie = emojisTrie[lang]; @@ -400,7 +400,7 @@ function replaceAndExtractEmojis(text: string, preferredSkinTone = CONST.EMOJI_D * @param [limit] - matching emojis limit */ function suggestEmojis(text: string, lang: keyof SupportedLanguage, limit = CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMOUNT_OF_SUGGESTIONS): Emoji[] | undefined { - // emojisTrie importing the emoji json file on app starting and we want to avoid it + // emojisTrie importing the emoji JSON file on the app starting and we want to avoid it const emojisTrie = require('./EmojiTrie').default; const trie = emojisTrie[lang]; From 33c787d5e260c12d0eab65aea52a94e89148bc75 Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Fri, 24 Nov 2023 11:43:03 +0100 Subject: [PATCH 057/130] update types --- src/components/Icon/BankIcons.ts | 83 +++++++++++++++++++++----------- 1 file changed, 54 insertions(+), 29 deletions(-) diff --git a/src/components/Icon/BankIcons.ts b/src/components/Icon/BankIcons.ts index 9087582b3997..a1711ac24955 100644 --- a/src/components/Icon/BankIcons.ts +++ b/src/components/Icon/BankIcons.ts @@ -1,6 +1,7 @@ import {CSSProperties} from 'react'; import {ViewStyle} from 'react-native'; import {SvgProps} from 'react-native-svg'; +import {ValueOf} from 'type-fest'; import GenericBank from '@assets/images/bankicons/generic-bank-account.svg'; import GenericBankCard from '@assets/images/cardicons/generic-bank-card.svg'; import styles from '@styles/styles'; @@ -35,49 +36,73 @@ const BANK_NAMES = { TD_BANK: 'td bank', US_BANK: 'us bank', USAA: 'usaa', -}; +} as const; type BankNameAndEmptyString = keyof typeof BANK_NAMES | ''; -type BankName = keyof typeof BANK_NAMES; +type BankName = ValueOf; +type BankNameKey = keyof typeof BANK_NAMES; /** * Returns matching asset icon for bankName */ -function getAssetIcon(bankName: BankNameAndEmptyString, isCard: boolean): React.FC { +function getAssetIcon(bankNameKey: BankNameKey, isCard: boolean): React.FC { + const bankValue = BANK_NAMES[bankNameKey]; + // Mapping bank names to their respective icon paths const iconMappings = { - [BANK_NAMES.EXPENSIFY]: isCard ? require('@assets/images/cardicons/expensify-card-dark.svg') : require('@assets/images/bankicons/expensify.svg'), - [BANK_NAMES.AMERICAN_EXPRESS]: isCard ? require('@assets/images/cardicons/american-express.svg') : require('@assets/images/bankicons/american-express.svg'), - [BANK_NAMES.BANK_OF_AMERICA]: isCard ? require('@assets/images/cardicons/bank-of-america.svg') : require('@assets/images/bankicons/bank-of-america.svg'), - [BANK_NAMES.BB_T]: isCard ? require('@assets/images/cardicons/bb-t.svg') : require('@assets/images/bankicons/bb-t.svg'), - [BANK_NAMES.CAPITAL_ONE]: isCard ? require('@assets/images/cardicons/capital-one.svg') : require('@assets/images/bankicons/capital-one.svg'), - [BANK_NAMES.CHASE]: isCard ? require('@assets/images/cardicons/chase.svg') : require('@assets/images/bankicons/chase.svg'), - [BANK_NAMES.CHARLES_SCHWAB]: isCard ? require('@assets/images/cardicons/charles-schwab.svg') : require('@assets/images/bankicons/charles-schwab.svg'), - [BANK_NAMES.CITIBANK]: isCard ? require('@assets/images/cardicons/citibank.svg') : require('@assets/images/bankicons/citibank.svg'), - [BANK_NAMES.CITIZENS_BANK]: isCard ? require('@assets/images/cardicons/citizens.svg') : require('@assets/images/bankicons/citizens-bank.svg'), - [BANK_NAMES.DISCOVER]: isCard ? require('@assets/images/cardicons/discover.svg') : require('@assets/images/bankicons/discover.svg'), - [BANK_NAMES.FIDELITY]: isCard ? require('@assets/images/cardicons/fidelity.svg') : require('@assets/images/bankicons/fidelity.svg'), - [BANK_NAMES.GENERIC_BANK]: isCard ? require('@assets/images/cardicons/generic-bank-card.svg') : require('@assets/images/bankicons/generic-bank-account.svg'), - [BANK_NAMES.HUNTINGTON_BANK]: isCard ? require('@assets/images/cardicons/huntington-bank.svg') : require('@assets/images/bankicons/huntington-bank.svg'), + [BANK_NAMES.EXPENSIFY]: isCard + ? (require('@assets/images/cardicons/expensify-card-dark.svg') as React.FC) + : (require('@assets/images/bankicons/expensify.svg') as React.FC), + [BANK_NAMES.AMERICAN_EXPRESS]: isCard + ? (require('@assets/images/cardicons/american-express.svg') as React.FC) + : (require('@assets/images/bankicons/american-express.svg') as React.FC), + [BANK_NAMES.BANK_OF_AMERICA]: isCard + ? (require('@assets/images/cardicons/bank-of-america.svg') as React.FC) + : (require('@assets/images/bankicons/bank-of-america.svg') as React.FC), + [BANK_NAMES.BB_T]: isCard ? (require('@assets/images/cardicons/bb-t.svg') as React.FC) : (require('@assets/images/bankicons/bb-t.svg') as React.FC), + [BANK_NAMES.CAPITAL_ONE]: isCard + ? (require('@assets/images/cardicons/capital-one.svg') as React.FC) + : (require('@assets/images/bankicons/capital-one.svg') as React.FC), + [BANK_NAMES.CHASE]: isCard ? (require('@assets/images/cardicons/chase.svg') as React.FC) : (require('@assets/images/bankicons/chase.svg') as React.FC), + [BANK_NAMES.CHARLES_SCHWAB]: isCard + ? (require('@assets/images/cardicons/charles-schwab.svg') as React.FC) + : (require('@assets/images/bankicons/charles-schwab.svg') as React.FC), + [BANK_NAMES.CITIBANK]: isCard ? (require('@assets/images/cardicons/citibank.svg') as React.FC) : (require('@assets/images/bankicons/citibank.svg') as React.FC), + [BANK_NAMES.CITIZENS_BANK]: isCard + ? (require('@assets/images/cardicons/citizens.svg') as React.FC) + : (require('@assets/images/bankicons/citizens-bank.svg') as React.FC), + [BANK_NAMES.DISCOVER]: isCard ? (require('@assets/images/cardicons/discover.svg') as React.FC) : (require('@assets/images/bankicons/discover.svg') as React.FC), + [BANK_NAMES.FIDELITY]: isCard ? (require('@assets/images/cardicons/fidelity.svg') as React.FC) : (require('@assets/images/bankicons/fidelity.svg') as React.FC), + [BANK_NAMES.GENERIC_BANK]: isCard + ? (require('@assets/images/cardicons/generic-bank-card.svg') as React.FC) + : (require('@assets/images/bankicons/generic-bank-account.svg') as React.FC), + [BANK_NAMES.HUNTINGTON_BANK]: isCard + ? (require('@assets/images/cardicons/huntington-bank.svg') as React.FC) + : (require('@assets/images/bankicons/huntington-bank.svg') as React.FC), [BANK_NAMES.NAVY_FEDERAL_CREDIT_UNION]: isCard - ? require('@assets/images/cardicons/navy-federal-credit-union.svg') - : require('@assets/images/bankicons/navy-federal-credit-union.svg'), - [BANK_NAMES.PNC]: isCard ? require('@assets/images/cardicons/pnc.svg') : require('@assets/images/bankicons/pnc.svg'), - [BANK_NAMES.REGIONS_BANK]: isCard ? require('@assets/images/cardicons/regions-bank.svg') : require('@assets/images/bankicons/regions-bank.svg'), - [BANK_NAMES.SUNTRUST]: isCard ? require('@assets/images/cardicons/suntrust.svg') : require('@assets/images/bankicons/suntrust.svg'), - [BANK_NAMES.TD_BANK]: isCard ? require('@assets/images/cardicons/td-bank.svg') : require('@assets/images/bankicons/td-bank.svg'), - [BANK_NAMES.US_BANK]: isCard ? require('@assets/images/cardicons/us-bank.svg') : require('@assets/images/bankicons/us-bank.svg'), - [BANK_NAMES.USAA]: isCard ? require('@assets/images/cardicons/usaa.svg') : require('@assets/images/bankicons/usaa.svg'), - }; + ? (require('@assets/images/cardicons/navy-federal-credit-union.svg') as React.FC) + : (require('@assets/images/bankicons/navy-federal-credit-union.svg') as React.FC), + [BANK_NAMES.PNC]: isCard ? (require('@assets/images/cardicons/pnc.svg') as React.FC) : (require('@assets/images/bankicons/pnc.svg') as React.FC), + [BANK_NAMES.REGIONS_BANK]: isCard + ? (require('@assets/images/cardicons/regions-bank.svg') as React.FC) + : (require('@assets/images/bankicons/regions-bank.svg') as React.FC), + [BANK_NAMES.SUNTRUST]: isCard ? (require('@assets/images/cardicons/suntrust.svg') as React.FC) : (require('@assets/images/bankicons/suntrust.svg') as React.FC), + [BANK_NAMES.TD_BANK]: isCard ? (require('@assets/images/cardicons/td-bank.svg') as React.FC) : (require('@assets/images/bankicons/td-bank.svg') as React.FC), + [BANK_NAMES.US_BANK]: isCard ? (require('@assets/images/cardicons/us-bank.svg') as React.FC) : (require('@assets/images/bankicons/us-bank.svg') as React.FC), + [BANK_NAMES.USAA]: isCard ? (require('@assets/images/cardicons/usaa.svg') as React.FC) : (require('@assets/images/bankicons/usaa.svg') as React.FC), + } as const; // Fallback to generic bank/card icon - const iconModule = iconMappings[bankName] || (isCard ? '@assets/images/cardicons/generic-bank-card.svg' : '@assets/images/bankicons/generic-bank-account.svg'); - return iconModule as React.FC; + const iconModule = + iconMappings[bankValue] || + (isCard ? (require('@assets/images/cardicons/generic-bank-card.svg') as React.FC) : (require('@assets/images/bankicons/generic-bank-account.svg') as React.FC)); + return iconModule; } -function getBankNameKey(bankName: string): BankNameAndEmptyString | undefined { - return Object.keys(BANK_NAMES).find((key) => BANK_NAMES[key as BankName].toLowerCase() === bankName) as BankNameAndEmptyString | undefined; +function getBankNameKey(bankName: string): BankNameKey | '' { + const bank = Object.entries(BANK_NAMES).find(([, value]) => value?.toLowerCase() === bankName); + return (bank?.[0] as BankNameKey) ?? ''; } /** From f72c49c2432d362ec9a96e3183fda5350dae3de0 Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Fri, 24 Nov 2023 11:47:57 +0100 Subject: [PATCH 058/130] create Bank type --- src/types/onyx/Bank.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/types/onyx/Bank.ts diff --git a/src/types/onyx/Bank.ts b/src/types/onyx/Bank.ts new file mode 100644 index 000000000000..a490198f2650 --- /dev/null +++ b/src/types/onyx/Bank.ts @@ -0,0 +1,19 @@ +import {CSSProperties} from 'react'; +import {ViewStyle} from 'react-native'; +import {SvgProps} from 'react-native-svg'; +import {ValueOf} from 'type-fest'; +import CONST from '@src/CONST'; + +type BankIcon = { + icon: React.FC; + iconSize?: number; + iconHeight?: number; + iconWidth?: number; + iconStyles?: Array; +}; + +type BankNameAndEmptyString = keyof typeof CONST.BANK_NAMES | ''; +type BankName = ValueOf; +type BankNameKey = keyof typeof CONST.BANK_NAMES; + +export type {BankIcon, BankNameAndEmptyString, BankName, BankNameKey}; From 61a67018d1066a8fa8480e25942e6efc9fc020a2 Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Fri, 24 Nov 2023 11:48:17 +0100 Subject: [PATCH 059/130] clean BankIcon component --- src/components/Icon/BankIcons.ts | 98 ++++++++++++-------------------- 1 file changed, 37 insertions(+), 61 deletions(-) diff --git a/src/components/Icon/BankIcons.ts b/src/components/Icon/BankIcons.ts index a1711ac24955..9d843856c4d1 100644 --- a/src/components/Icon/BankIcons.ts +++ b/src/components/Icon/BankIcons.ts @@ -1,96 +1,72 @@ -import {CSSProperties} from 'react'; -import {ViewStyle} from 'react-native'; import {SvgProps} from 'react-native-svg'; -import {ValueOf} from 'type-fest'; import GenericBank from '@assets/images/bankicons/generic-bank-account.svg'; import GenericBankCard from '@assets/images/cardicons/generic-bank-card.svg'; import styles from '@styles/styles'; import variables from '@styles/variables'; - -type BankIcon = { - icon: React.FC; - iconSize?: number; - iconHeight?: number; - iconWidth?: number; - iconStyles?: Array; -}; - -const BANK_NAMES = { - EXPENSIFY: 'expensify', - AMERICAN_EXPRESS: 'americanexpress', - BANK_OF_AMERICA: 'bank of america', - BB_T: 'bbt', - CAPITAL_ONE: 'capital one', - CHASE: 'chase', - CHARLES_SCHWAB: 'charles schwab', - CITIBANK: 'citibank', - CITIZENS_BANK: 'citizens bank', - DISCOVER: 'discover', - FIDELITY: 'fidelity', - GENERIC_BANK: 'generic bank', - HUNTINGTON_BANK: 'huntington bank', - NAVY_FEDERAL_CREDIT_UNION: 'navy federal credit union', - PNC: 'pnc', - REGIONS_BANK: 'regions bank', - SUNTRUST: 'suntrust', - TD_BANK: 'td bank', - US_BANK: 'us bank', - USAA: 'usaa', -} as const; - -type BankNameAndEmptyString = keyof typeof BANK_NAMES | ''; -type BankName = ValueOf; -type BankNameKey = keyof typeof BANK_NAMES; +import CONST from '@src/CONST'; +import {BankIcon, BankName, BankNameAndEmptyString, BankNameKey} from '@src/types/onyx/Bank'; /** * Returns matching asset icon for bankName */ function getAssetIcon(bankNameKey: BankNameKey, isCard: boolean): React.FC { - const bankValue = BANK_NAMES[bankNameKey]; + const bankValue = CONST.BANK_NAMES[bankNameKey]; // Mapping bank names to their respective icon paths const iconMappings = { - [BANK_NAMES.EXPENSIFY]: isCard + [CONST.BANK_NAMES.EXPENSIFY]: isCard ? (require('@assets/images/cardicons/expensify-card-dark.svg') as React.FC) : (require('@assets/images/bankicons/expensify.svg') as React.FC), - [BANK_NAMES.AMERICAN_EXPRESS]: isCard + [CONST.BANK_NAMES.AMERICAN_EXPRESS]: isCard ? (require('@assets/images/cardicons/american-express.svg') as React.FC) : (require('@assets/images/bankicons/american-express.svg') as React.FC), - [BANK_NAMES.BANK_OF_AMERICA]: isCard + [CONST.BANK_NAMES.BANK_OF_AMERICA]: isCard ? (require('@assets/images/cardicons/bank-of-america.svg') as React.FC) : (require('@assets/images/bankicons/bank-of-america.svg') as React.FC), - [BANK_NAMES.BB_T]: isCard ? (require('@assets/images/cardicons/bb-t.svg') as React.FC) : (require('@assets/images/bankicons/bb-t.svg') as React.FC), - [BANK_NAMES.CAPITAL_ONE]: isCard + [CONST.BANK_NAMES.BB_T]: isCard ? (require('@assets/images/cardicons/bb-t.svg') as React.FC) : (require('@assets/images/bankicons/bb-t.svg') as React.FC), + [CONST.BANK_NAMES.CAPITAL_ONE]: isCard ? (require('@assets/images/cardicons/capital-one.svg') as React.FC) : (require('@assets/images/bankicons/capital-one.svg') as React.FC), - [BANK_NAMES.CHASE]: isCard ? (require('@assets/images/cardicons/chase.svg') as React.FC) : (require('@assets/images/bankicons/chase.svg') as React.FC), - [BANK_NAMES.CHARLES_SCHWAB]: isCard + [CONST.BANK_NAMES.CHASE]: isCard ? (require('@assets/images/cardicons/chase.svg') as React.FC) : (require('@assets/images/bankicons/chase.svg') as React.FC), + [CONST.BANK_NAMES.CHARLES_SCHWAB]: isCard ? (require('@assets/images/cardicons/charles-schwab.svg') as React.FC) : (require('@assets/images/bankicons/charles-schwab.svg') as React.FC), - [BANK_NAMES.CITIBANK]: isCard ? (require('@assets/images/cardicons/citibank.svg') as React.FC) : (require('@assets/images/bankicons/citibank.svg') as React.FC), - [BANK_NAMES.CITIZENS_BANK]: isCard + [CONST.BANK_NAMES.CITIBANK]: isCard + ? (require('@assets/images/cardicons/citibank.svg') as React.FC) + : (require('@assets/images/bankicons/citibank.svg') as React.FC), + [CONST.BANK_NAMES.CITIZENS_BANK]: isCard ? (require('@assets/images/cardicons/citizens.svg') as React.FC) : (require('@assets/images/bankicons/citizens-bank.svg') as React.FC), - [BANK_NAMES.DISCOVER]: isCard ? (require('@assets/images/cardicons/discover.svg') as React.FC) : (require('@assets/images/bankicons/discover.svg') as React.FC), - [BANK_NAMES.FIDELITY]: isCard ? (require('@assets/images/cardicons/fidelity.svg') as React.FC) : (require('@assets/images/bankicons/fidelity.svg') as React.FC), - [BANK_NAMES.GENERIC_BANK]: isCard + [CONST.BANK_NAMES.DISCOVER]: isCard + ? (require('@assets/images/cardicons/discover.svg') as React.FC) + : (require('@assets/images/bankicons/discover.svg') as React.FC), + [CONST.BANK_NAMES.FIDELITY]: isCard + ? (require('@assets/images/cardicons/fidelity.svg') as React.FC) + : (require('@assets/images/bankicons/fidelity.svg') as React.FC), + [CONST.BANK_NAMES.GENERIC_BANK]: isCard ? (require('@assets/images/cardicons/generic-bank-card.svg') as React.FC) : (require('@assets/images/bankicons/generic-bank-account.svg') as React.FC), - [BANK_NAMES.HUNTINGTON_BANK]: isCard + [CONST.BANK_NAMES.HUNTINGTON_BANK]: isCard ? (require('@assets/images/cardicons/huntington-bank.svg') as React.FC) : (require('@assets/images/bankicons/huntington-bank.svg') as React.FC), - [BANK_NAMES.NAVY_FEDERAL_CREDIT_UNION]: isCard + [CONST.BANK_NAMES.NAVY_FEDERAL_CREDIT_UNION]: isCard ? (require('@assets/images/cardicons/navy-federal-credit-union.svg') as React.FC) : (require('@assets/images/bankicons/navy-federal-credit-union.svg') as React.FC), - [BANK_NAMES.PNC]: isCard ? (require('@assets/images/cardicons/pnc.svg') as React.FC) : (require('@assets/images/bankicons/pnc.svg') as React.FC), - [BANK_NAMES.REGIONS_BANK]: isCard + [CONST.BANK_NAMES.PNC]: isCard ? (require('@assets/images/cardicons/pnc.svg') as React.FC) : (require('@assets/images/bankicons/pnc.svg') as React.FC), + [CONST.BANK_NAMES.REGIONS_BANK]: isCard ? (require('@assets/images/cardicons/regions-bank.svg') as React.FC) : (require('@assets/images/bankicons/regions-bank.svg') as React.FC), - [BANK_NAMES.SUNTRUST]: isCard ? (require('@assets/images/cardicons/suntrust.svg') as React.FC) : (require('@assets/images/bankicons/suntrust.svg') as React.FC), - [BANK_NAMES.TD_BANK]: isCard ? (require('@assets/images/cardicons/td-bank.svg') as React.FC) : (require('@assets/images/bankicons/td-bank.svg') as React.FC), - [BANK_NAMES.US_BANK]: isCard ? (require('@assets/images/cardicons/us-bank.svg') as React.FC) : (require('@assets/images/bankicons/us-bank.svg') as React.FC), - [BANK_NAMES.USAA]: isCard ? (require('@assets/images/cardicons/usaa.svg') as React.FC) : (require('@assets/images/bankicons/usaa.svg') as React.FC), + [CONST.BANK_NAMES.SUNTRUST]: isCard + ? (require('@assets/images/cardicons/suntrust.svg') as React.FC) + : (require('@assets/images/bankicons/suntrust.svg') as React.FC), + [CONST.BANK_NAMES.TD_BANK]: isCard + ? (require('@assets/images/cardicons/td-bank.svg') as React.FC) + : (require('@assets/images/bankicons/td-bank.svg') as React.FC), + [CONST.BANK_NAMES.US_BANK]: isCard + ? (require('@assets/images/cardicons/us-bank.svg') as React.FC) + : (require('@assets/images/bankicons/us-bank.svg') as React.FC), + [CONST.BANK_NAMES.USAA]: isCard ? (require('@assets/images/cardicons/usaa.svg') as React.FC) : (require('@assets/images/bankicons/usaa.svg') as React.FC), } as const; // Fallback to generic bank/card icon @@ -101,7 +77,7 @@ function getAssetIcon(bankNameKey: BankNameKey, isCard: boolean): React.FC value?.toLowerCase() === bankName); + const bank = Object.entries(CONST.BANK_NAMES).find(([, value]) => value?.toLowerCase() === bankName); return (bank?.[0] as BankNameKey) ?? ''; } @@ -116,7 +92,7 @@ export default function getBankIcon(bankName: BankNameAndEmptyString, isCard = f icon: isCard ? GenericBankCard : GenericBank, }; - if (bankNameKey && Object.keys(BANK_NAMES).includes(bankNameKey)) { + if (bankNameKey && Object.keys(CONST.BANK_NAMES).includes(bankNameKey)) { bankIcon.icon = getAssetIcon(bankNameKey, isCard); } From b87507a4a40562e00b8d8af3d0dd339f3efc6afd Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Fri, 24 Nov 2023 11:48:32 +0100 Subject: [PATCH 060/130] add BANK_NAMES const --- src/CONST.ts | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/CONST.ts b/src/CONST.ts index 436ac4ebbc31..599fabaeac9f 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -2914,6 +2914,29 @@ const CONST = { PERFORMANCE_TESTS: { RUNS: 20, }, + + BANK_NAMES: { + EXPENSIFY: 'expensify', + AMERICAN_EXPRESS: 'americanexpress', + BANK_OF_AMERICA: 'bank of america', + BB_T: 'bbt', + CAPITAL_ONE: 'capital one', + CHASE: 'chase', + CHARLES_SCHWAB: 'charles schwab', + CITIBANK: 'citibank', + CITIZENS_BANK: 'citizens bank', + DISCOVER: 'discover', + FIDELITY: 'fidelity', + GENERIC_BANK: 'generic bank', + HUNTINGTON_BANK: 'huntington bank', + NAVY_FEDERAL_CREDIT_UNION: 'navy federal credit union', + PNC: 'pnc', + REGIONS_BANK: 'regions bank', + SUNTRUST: 'suntrust', + TD_BANK: 'td bank', + US_BANK: 'us bank', + USAA: 'usaa', + } } as const; export default CONST; From 9f2c4efaf12be8e9652eeb48cc39738a784ff360 Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Fri, 24 Nov 2023 12:03:01 +0100 Subject: [PATCH 061/130] spaces --- src/CONST.ts | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 599fabaeac9f..4913869af838 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -2916,27 +2916,27 @@ const CONST = { }, BANK_NAMES: { - EXPENSIFY: 'expensify', - AMERICAN_EXPRESS: 'americanexpress', - BANK_OF_AMERICA: 'bank of america', - BB_T: 'bbt', - CAPITAL_ONE: 'capital one', - CHASE: 'chase', - CHARLES_SCHWAB: 'charles schwab', - CITIBANK: 'citibank', - CITIZENS_BANK: 'citizens bank', - DISCOVER: 'discover', - FIDELITY: 'fidelity', - GENERIC_BANK: 'generic bank', - HUNTINGTON_BANK: 'huntington bank', - NAVY_FEDERAL_CREDIT_UNION: 'navy federal credit union', - PNC: 'pnc', - REGIONS_BANK: 'regions bank', - SUNTRUST: 'suntrust', - TD_BANK: 'td bank', - US_BANK: 'us bank', - USAA: 'usaa', - } + EXPENSIFY: 'expensify', + AMERICAN_EXPRESS: 'americanexpress', + BANK_OF_AMERICA: 'bank of america', + BB_T: 'bbt', + CAPITAL_ONE: 'capital one', + CHASE: 'chase', + CHARLES_SCHWAB: 'charles schwab', + CITIBANK: 'citibank', + CITIZENS_BANK: 'citizens bank', + DISCOVER: 'discover', + FIDELITY: 'fidelity', + GENERIC_BANK: 'generic bank', + HUNTINGTON_BANK: 'huntington bank', + NAVY_FEDERAL_CREDIT_UNION: 'navy federal credit union', + PNC: 'pnc', + REGIONS_BANK: 'regions bank', + SUNTRUST: 'suntrust', + TD_BANK: 'td bank', + US_BANK: 'us bank', + USAA: 'usaa', + }, } as const; export default CONST; From 11083015f4abd1574846c44b49a1385c50946c81 Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Fri, 24 Nov 2023 12:38:11 +0100 Subject: [PATCH 062/130] prettier --- src/components/Icon/BankIcons.ts | 4 ++-- src/types/onyx/AccountData.ts | 2 +- src/types/onyx/Bank.ts | 3 +-- src/types/onyx/Fund.ts | 2 +- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/components/Icon/BankIcons.ts b/src/components/Icon/BankIcons.ts index 9d843856c4d1..0afd4e304680 100644 --- a/src/components/Icon/BankIcons.ts +++ b/src/components/Icon/BankIcons.ts @@ -4,7 +4,7 @@ import GenericBankCard from '@assets/images/cardicons/generic-bank-card.svg'; import styles from '@styles/styles'; import variables from '@styles/variables'; import CONST from '@src/CONST'; -import {BankIcon, BankName, BankNameAndEmptyString, BankNameKey} from '@src/types/onyx/Bank'; +import {BankIcon, BankName, BankNameKey} from '@src/types/onyx/Bank'; /** * Returns matching asset icon for bankName @@ -85,7 +85,7 @@ function getBankNameKey(bankName: string): BankNameKey | '' { * Returns Bank Icon Object that matches to existing bank icons or default icons */ -export default function getBankIcon(bankName: BankNameAndEmptyString, isCard = false): BankIcon { +export default function getBankIcon(bankName: BankName | '', isCard = false): BankIcon { const bankNameKey = getBankNameKey(bankName.toLowerCase()); const bankIcon: BankIcon = { diff --git a/src/types/onyx/AccountData.ts b/src/types/onyx/AccountData.ts index c9ffcc34c659..601e82cde836 100644 --- a/src/types/onyx/AccountData.ts +++ b/src/types/onyx/AccountData.ts @@ -1,4 +1,4 @@ -import {BankName} from '@components/Icon/BankIcons'; +import {BankName} from './Bank'; import * as OnyxCommon from './OnyxCommon'; type AdditionalData = { diff --git a/src/types/onyx/Bank.ts b/src/types/onyx/Bank.ts index a490198f2650..b6312e039079 100644 --- a/src/types/onyx/Bank.ts +++ b/src/types/onyx/Bank.ts @@ -12,8 +12,7 @@ type BankIcon = { iconStyles?: Array; }; -type BankNameAndEmptyString = keyof typeof CONST.BANK_NAMES | ''; type BankName = ValueOf; type BankNameKey = keyof typeof CONST.BANK_NAMES; -export type {BankIcon, BankNameAndEmptyString, BankName, BankNameKey}; +export type {BankIcon, BankName, BankNameKey}; diff --git a/src/types/onyx/Fund.ts b/src/types/onyx/Fund.ts index d1d27cce5d71..82c15c5089d7 100644 --- a/src/types/onyx/Fund.ts +++ b/src/types/onyx/Fund.ts @@ -1,5 +1,5 @@ -import {BankName} from '@components/Icon/BankIcons'; import CONST from '@src/CONST'; +import {BankName} from './Bank'; import * as OnyxCommon from './OnyxCommon'; type AdditionalData = { From 0129ddbb42f1e0fbf388ae8c43ead616f81e73fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Miko=C5=82ajczak?= Date: Fri, 24 Nov 2023 13:54:01 +0100 Subject: [PATCH 063/130] change generic type to HTMLElement --- src/components/Hoverable/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Hoverable/index.tsx b/src/components/Hoverable/index.tsx index dc32c01ce3ab..d6b81e102805 100644 --- a/src/components/Hoverable/index.tsx +++ b/src/components/Hoverable/index.tsx @@ -8,7 +8,7 @@ import HoverableProps from './types'; * because nesting Pressables causes issues where the hovered state of the child cannot be easily propagated to the * parent. https://github.com/necolas/react-native-web/issues/1875 */ -function Hoverable({disabled, ...props}: HoverableProps, ref: Ref) { +function Hoverable({disabled, ...props}: HoverableProps, ref: Ref) { // If Hoverable is disabled, just render the child without additional logic or event listeners. // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing if (disabled || !hasHoverSupport()) { From 6e644a796716db20befee6a83df90e00148aaa47 Mon Sep 17 00:00:00 2001 From: Viktoryia Kliushun Date: Fri, 24 Nov 2023 16:23:37 +0100 Subject: [PATCH 064/130] [TS migration] Migrate 'WalletStatementModal' component --- .../WalletStatementModalPropTypes.js | 21 ----------- .../{index.native.js => index.native.tsx} | 26 ++++++-------- .../{index.js => index.tsx} | 35 +++++++------------ src/components/WalletStatementModal/types.ts | 14 ++++++++ 4 files changed, 37 insertions(+), 59 deletions(-) delete mode 100644 src/components/WalletStatementModal/WalletStatementModalPropTypes.js rename src/components/WalletStatementModal/{index.native.js => index.native.tsx} (74%) rename src/components/WalletStatementModal/{index.js => index.tsx} (64%) create mode 100644 src/components/WalletStatementModal/types.ts diff --git a/src/components/WalletStatementModal/WalletStatementModalPropTypes.js b/src/components/WalletStatementModal/WalletStatementModalPropTypes.js deleted file mode 100644 index 06a2248151f6..000000000000 --- a/src/components/WalletStatementModal/WalletStatementModalPropTypes.js +++ /dev/null @@ -1,21 +0,0 @@ -import PropTypes from 'prop-types'; - -const walletStatementPropTypes = { - /* Onyx Props */ - /** Session info for the currently logged in user. */ - session: PropTypes.shape({ - /** Currently logged in user authToken */ - authToken: PropTypes.string, - }), - - /** URL for oldDot (expensify.com) statements page to display */ - statementPageURL: PropTypes.string, -}; - -const walletStatementDefaultProps = { - session: { - authToken: null, - }, -}; - -export {walletStatementPropTypes, walletStatementDefaultProps}; diff --git a/src/components/WalletStatementModal/index.native.js b/src/components/WalletStatementModal/index.native.tsx similarity index 74% rename from src/components/WalletStatementModal/index.native.js rename to src/components/WalletStatementModal/index.native.tsx index 9c2aea8e8ec4..896861e7898f 100644 --- a/src/components/WalletStatementModal/index.native.js +++ b/src/components/WalletStatementModal/index.native.tsx @@ -1,31 +1,29 @@ -import lodashGet from 'lodash/get'; import React, {useCallback, useRef} from 'react'; import {withOnyx} from 'react-native-onyx'; -import {WebView} from 'react-native-webview'; -import _ from 'underscore'; +import {WebView, WebViewNavigation} from 'react-native-webview'; +import {ValueOf} from 'type-fest'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import Navigation from '@libs/Navigation/Navigation'; import * as Report from '@userActions/Report'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import {walletStatementDefaultProps, walletStatementPropTypes} from './WalletStatementModalPropTypes'; +import type {WalletStatementOnyxProps, WalletStatementProps} from './types'; + +type WebViewNavigationEvent = WebViewNavigation & {type?: ValueOf}; const IOU_ROUTES = [ROUTES.IOU_REQUEST, ROUTES.IOU_SEND]; const renderLoading = () => ; -function WalletStatementModal({statementPageURL, session}) { - const webViewRef = useRef(); - const authToken = lodashGet(session, 'authToken', null); +function WalletStatementModal({statementPageURL, session}: WalletStatementProps) { + const webViewRef = useRef(null); + const authToken = session?.authToken ?? null; /** * Handles in-app navigation for webview links - * - * @param {String} params.type - * @param {String} params.url */ const handleNavigationStateChange = useCallback( - ({type, url}) => { + ({type, url}: WebViewNavigationEvent) => { if (!webViewRef.current || (type !== CONST.WALLET.WEB_MESSAGE_TYPE.STATEMENT && type !== CONST.WALLET.WEB_MESSAGE_TYPE.CONCIERGE)) { return; } @@ -36,7 +34,7 @@ function WalletStatementModal({statementPageURL, session}) { } if (type === CONST.WALLET.WEB_MESSAGE_TYPE.STATEMENT && url) { - const iouRoute = _.find(IOU_ROUTES, (item) => url.includes(item)); + const iouRoute = IOU_ROUTES.find((item) => url.includes(item)); if (iouRoute) { webViewRef.current.stopLoading(); @@ -66,10 +64,8 @@ function WalletStatementModal({statementPageURL, session}) { } WalletStatementModal.displayName = 'WalletStatementModal'; -WalletStatementModal.propTypes = walletStatementPropTypes; -WalletStatementModal.defaultProps = walletStatementDefaultProps; -export default withOnyx({ +export default withOnyx({ session: { key: ONYXKEYS.SESSION, }, diff --git a/src/components/WalletStatementModal/index.js b/src/components/WalletStatementModal/index.tsx similarity index 64% rename from src/components/WalletStatementModal/index.js rename to src/components/WalletStatementModal/index.tsx index 8ce4ee01ed30..0f1d1804df60 100644 --- a/src/components/WalletStatementModal/index.js +++ b/src/components/WalletStatementModal/index.tsx @@ -1,31 +1,25 @@ -import lodashGet from 'lodash/get'; import React, {useState} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; -import withLocalize from '@components/withLocalize'; -import compose from '@libs/compose'; import Navigation from '@libs/Navigation/Navigation'; import useThemeStyles from '@styles/useThemeStyles'; import * as Report from '@userActions/Report'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import {walletStatementDefaultProps, walletStatementPropTypes} from './WalletStatementModalPropTypes'; +import type {WalletStatementOnyxProps, WalletStatementProps} from './types'; -function WalletStatementModal({statementPageURL, session}) { +function WalletStatementModal({statementPageURL, session}: WalletStatementProps) { const styles = useThemeStyles(); const [isLoading, setIsLoading] = useState(true); - const authToken = lodashGet(session, 'authToken', null); + const authToken = session?.authToken ?? null; /** * Handles in-app navigation for iframe links - * - * @param {MessageEvent} event */ - const navigate = (event) => { - if (!event.data || !event.data.type || (event.data.type !== CONST.WALLET.WEB_MESSAGE_TYPE.STATEMENT && event.data.type !== CONST.WALLET.WEB_MESSAGE_TYPE.CONCIERGE)) { + const navigate = (event: MessageEvent) => { + if (!event.data?.type || (event.data.type !== CONST.WALLET.WEB_MESSAGE_TYPE.STATEMENT && event.data.type !== CONST.WALLET.WEB_MESSAGE_TYPE.CONCIERGE)) { return; } @@ -35,7 +29,7 @@ function WalletStatementModal({statementPageURL, session}) { if (event.data.type === CONST.WALLET.WEB_MESSAGE_TYPE.STATEMENT && event.data.url) { const iouRoutes = [ROUTES.IOU_REQUEST, ROUTES.IOU_SEND]; - const navigateToIOURoute = _.find(iouRoutes, (iouRoute) => event.data.url.includes(iouRoute)); + const navigateToIOURoute = iouRoutes.find((iouRoute) => event.data.url.includes(iouRoute)); if (navigateToIOURoute) { Navigation.navigate(navigateToIOURoute); } @@ -51,7 +45,7 @@ function WalletStatementModal({statementPageURL, session}) { title="Statements" height="100%" width="100%" - seamless="seamless" + seamless frameBorder="0" onLoad={() => { setIsLoading(false); @@ -66,15 +60,10 @@ function WalletStatementModal({statementPageURL, session}) { ); } -WalletStatementModal.propTypes = walletStatementPropTypes; -WalletStatementModal.defaultProps = walletStatementDefaultProps; WalletStatementModal.displayName = 'WalletStatementModal'; -export default compose( - withLocalize, - withOnyx({ - session: { - key: ONYXKEYS.SESSION, - }, - }), -)(WalletStatementModal); +export default withOnyx({ + session: { + key: ONYXKEYS.SESSION, + }, +})(WalletStatementModal); diff --git a/src/components/WalletStatementModal/types.ts b/src/components/WalletStatementModal/types.ts new file mode 100644 index 000000000000..bb926b8250ae --- /dev/null +++ b/src/components/WalletStatementModal/types.ts @@ -0,0 +1,14 @@ +import {OnyxEntry} from 'react-native-onyx/lib/types'; +import type {Session} from '@src/types/onyx'; + +type WalletStatementOnyxProps = { + /** Session info for the currently logged in user. */ + session: OnyxEntry; +}; + +type WalletStatementProps = WalletStatementOnyxProps & { + /** URL for oldDot (expensify.com) statements page to display */ + statementPageURL: string; +}; + +export type {WalletStatementProps, WalletStatementOnyxProps}; From a2bf1a5ff5329ef2341422e659c2646a7e543574 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Miko=C5=82ajczak?= Date: Sun, 26 Nov 2023 10:08:46 +0100 Subject: [PATCH 065/130] simplify unsetHoveredIfOutside listener --- src/components/Hoverable/ActiveHoverable.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/Hoverable/ActiveHoverable.tsx b/src/components/Hoverable/ActiveHoverable.tsx index 741e012f4601..dc56d8e74f59 100644 --- a/src/components/Hoverable/ActiveHoverable.tsx +++ b/src/components/Hoverable/ActiveHoverable.tsx @@ -61,6 +61,10 @@ function ActiveHoverable({onHoverIn, onHoverOut, shouldHandleScroll, children}: }, [shouldHandleScroll]); useEffect(() => { + // Do not mount a listener if the component is not hovered + if (!isHovered) { + return; + } /** * Checks the hover state of a component and updates it based on the event target. * This is necessary to handle cases where the hover state might get stuck due to an unreliable mouseleave trigger, @@ -68,11 +72,7 @@ function ActiveHoverable({onHoverIn, onHoverOut, shouldHandleScroll, children}: * @param event The hover event object. */ const unsetHoveredIfOutside = (event: MouseEvent) => { - if (!ref.current || !isHovered) { - return; - } - - if (ref.current.contains(event.target as Node)) { + if (!ref.current || ref.current.contains(event.target as Node)) { return; } From a75178aa3746f04e9b71d6e6e44574f7def6337e Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Sun, 26 Nov 2023 18:21:59 +0100 Subject: [PATCH 066/130] Change Boolean to '!!' --- src/components/TestToolMenu.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/TestToolMenu.tsx b/src/components/TestToolMenu.tsx index 2dfc060171f1..6a3efb0c26ce 100644 --- a/src/components/TestToolMenu.tsx +++ b/src/components/TestToolMenu.tsx @@ -57,7 +57,7 @@ function TestToolMenu({user = USER_DEFAULT, network}: TestToolMenuProps) { Network.setShouldForceOffline(!network?.shouldForceOffline)} /> @@ -66,7 +66,7 @@ function TestToolMenu({user = USER_DEFAULT, network}: TestToolMenuProps) { Network.setShouldFailAllRequests(!network?.shouldFailAllRequests)} /> From 6ec7fbc71fa4e4e9aa00089b5baab2a071a9c8ba Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Sun, 26 Nov 2023 18:33:42 +0100 Subject: [PATCH 067/130] Import react in testmenu --- src/components/TestToolMenu.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/TestToolMenu.tsx b/src/components/TestToolMenu.tsx index 6a3efb0c26ce..e40d8f473cdf 100644 --- a/src/components/TestToolMenu.tsx +++ b/src/components/TestToolMenu.tsx @@ -1,3 +1,4 @@ +import React from 'react'; import {OnyxEntry, withOnyx} from 'react-native-onyx'; import * as ApiUtils from '@libs/ApiUtils'; import compose from '@libs/compose'; From 706dd0087b484c5d92e2622d5bb8a13dd6c1d340 Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Mon, 27 Nov 2023 15:40:01 +0100 Subject: [PATCH 068/130] fix typo --- src/libs/EmojiUtils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/EmojiUtils.ts b/src/libs/EmojiUtils.ts index 49d08fcc1b2c..22bfc83218ac 100644 --- a/src/libs/EmojiUtils.ts +++ b/src/libs/EmojiUtils.ts @@ -322,7 +322,7 @@ function getAddedEmojis(currentEmojis: Emoji[], formerEmojis: Emoji[]): Emoji[] * If we're on mobile, we also add a space after the emoji granted there's no text after it. */ function replaceEmojis(text: string, preferredSkinTone = CONST.EMOJI_DEFAULT_SKIN_TONE, lang: 'en' | 'es' = CONST.LOCALES.DEFAULT): ReplacedEmoji { - // emojisTrie importing the emoji JSON file on the app starting and we want to avoid it + // emojisTrie is importing the emoji JSON file on the app starting and we want to avoid it const emojisTrie = require('./EmojiTrie').default; const trie = emojisTrie[lang]; @@ -400,7 +400,7 @@ function replaceAndExtractEmojis(text: string, preferredSkinTone = CONST.EMOJI_D * @param [limit] - matching emojis limit */ function suggestEmojis(text: string, lang: keyof SupportedLanguage, limit = CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMOUNT_OF_SUGGESTIONS): Emoji[] | undefined { - // emojisTrie importing the emoji JSON file on the app starting and we want to avoid it + // emojisTrie is importing the emoji JSON file on the app starting and we want to avoid it const emojisTrie = require('./EmojiTrie').default; const trie = emojisTrie[lang]; From 8141e5097b92590d9132cf3c64831b6b6727e90a Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Mon, 27 Nov 2023 15:40:15 +0100 Subject: [PATCH 069/130] add comments --- src/components/Icon/BankIcons.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/Icon/BankIcons.ts b/src/components/Icon/BankIcons.ts index 0afd4e304680..cc9730b5232b 100644 --- a/src/components/Icon/BankIcons.ts +++ b/src/components/Icon/BankIcons.ts @@ -13,7 +13,9 @@ import {BankIcon, BankName, BankNameKey} from '@src/types/onyx/Bank'; function getAssetIcon(bankNameKey: BankNameKey, isCard: boolean): React.FC { const bankValue = CONST.BANK_NAMES[bankNameKey]; - // Mapping bank names to their respective icon paths + // This maps bank names to their respective icon paths. + // The purpose is to avoid importing these at the app startup stage. + // Depending on whether 'isCard' is true, it selects either a card icon or a bank icon. const iconMappings = { [CONST.BANK_NAMES.EXPENSIFY]: isCard ? (require('@assets/images/cardicons/expensify-card-dark.svg') as React.FC) From 90d68384cf3b537efe6cd99e5cf0af09186b1bf1 Mon Sep 17 00:00:00 2001 From: Viktoryia Kliushun Date: Mon, 27 Nov 2023 16:31:40 +0100 Subject: [PATCH 070/130] [TS migration] Migrate 'AnonymousReportFooter.js' component --- ...ortFooter.js => AnonymousReportFooter.tsx} | 49 ++++++++----------- 1 file changed, 21 insertions(+), 28 deletions(-) rename src/components/{AnonymousReportFooter.js => AnonymousReportFooter.tsx} (51%) diff --git a/src/components/AnonymousReportFooter.js b/src/components/AnonymousReportFooter.tsx similarity index 51% rename from src/components/AnonymousReportFooter.js rename to src/components/AnonymousReportFooter.tsx index 387e2ab01930..65dc813a829d 100644 --- a/src/components/AnonymousReportFooter.js +++ b/src/components/AnonymousReportFooter.tsx @@ -1,57 +1,52 @@ -import PropTypes from 'prop-types'; import React from 'react'; import {Text, View} from 'react-native'; -import reportPropTypes from '@pages/reportPropTypes'; +import {OnyxCollection} from 'react-native-onyx'; +import {OnyxEntry} from 'react-native-onyx/lib/types'; +import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@styles/useThemeStyles'; import * as Session from '@userActions/Session'; +import {PersonalDetails, Report} from '@src/types/onyx'; import AvatarWithDisplayName from './AvatarWithDisplayName'; import Button from './Button'; import ExpensifyWordmark from './ExpensifyWordmark'; -import participantPropTypes from './participantPropTypes'; -import withLocalize, {withLocalizePropTypes} from './withLocalize'; -const propTypes = { +type AnonymousReportFooterProps = { /** The report currently being looked at */ - report: reportPropTypes, + report: OnyxEntry; - isSmallSizeLayout: PropTypes.bool, + /** Whether the small screen size layout should be used */ + isSmallSizeLayout?: boolean; /** Personal details of all the users */ - personalDetails: PropTypes.objectOf(participantPropTypes), - - ...withLocalizePropTypes, -}; - -const defaultProps = { - report: {}, - isSmallSizeLayout: false, - personalDetails: {}, + personalDetails: OnyxCollection; }; -function AnonymousReportFooter(props) { +function AnonymousReportFooter({isSmallSizeLayout = false, personalDetails, report}: AnonymousReportFooterProps) { const styles = useThemeStyles(); + const {translate} = useLocalize(); + return ( - + - - - + + + - {props.translate('anonymousReportFooter.logoTagline')} + {translate('anonymousReportFooter.logoTagline')}