From 98a52fc64c35f5de78832f2b662d64c61c9ee8ac Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Sun, 10 Mar 2024 22:21:50 +0700 Subject: [PATCH 001/106] Add new welcome note --- src/languages/en.ts | 10 ++++- src/languages/types.ts | 2 +- .../workspace/WorkspaceInviteMessagePage.tsx | 42 +++++++++++++------ 3 files changed, 39 insertions(+), 15 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 3575854ee7e2..302a039d697c 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1906,8 +1906,14 @@ export default { personalMessagePrompt: 'Message', genericFailureMessage: 'An error occurred inviting the user to the workspace, please try again.', inviteNoMembersError: 'Please select at least one member to invite', - welcomeNote: ({workspaceName}: WelcomeNoteParams) => - `You have been invited to ${workspaceName || 'a workspace'}! Download the Expensify mobile app at use.expensify.com/download to start tracking your expenses.`, + welcomeNote: ({workspaceName, senderDisplayName, senderLogin, workspaceDescription, workspaceLink}: WelcomeNoteParams) => `${ + senderDisplayName ? `#${senderDisplayName} (${senderLogin}) invited you to Expensify` : `#${senderLogin} invited you to Expensify` + }\nHi there -\n\nTo join "${workspaceName}", [click here](${workspaceLink}) to start tracking your expenses!\n\nThey also added the message:\nYou have been invited to ${ + workspaceName || 'a workspace' + }! Download the Expensify mobile app at use.expensify.com/download to start tracking your expenses.\n\n${ + workspaceDescription ? `Additionally, here is the workspace description:\n ${workspaceDescription}` : '' + } + `, }, editor: { descriptionInputLabel: 'Description', diff --git a/src/languages/types.ts b/src/languages/types.ts index 2bb05b614483..a2f4c92c7a3c 100644 --- a/src/languages/types.ts +++ b/src/languages/types.ts @@ -184,7 +184,7 @@ type UserIsAlreadyMemberParams = {login: string; name: string}; type GoToRoomParams = {roomName: string}; -type WelcomeNoteParams = {workspaceName: string}; +type WelcomeNoteParams = {workspaceName: string; senderDisplayName: string; senderLogin: string; workspaceDescription: string; workspaceLink: string}; type RoomNameReservedErrorParams = {reservedName: string}; diff --git a/src/pages/workspace/WorkspaceInviteMessagePage.tsx b/src/pages/workspace/WorkspaceInviteMessagePage.tsx index 72f08095b58a..4961990d69b5 100644 --- a/src/pages/workspace/WorkspaceInviteMessagePage.tsx +++ b/src/pages/workspace/WorkspaceInviteMessagePage.tsx @@ -1,4 +1,5 @@ import type {StackScreenProps} from '@react-navigation/stack'; +import ExpensiMark from 'expensify-common/lib/ExpensiMark'; import lodashDebounce from 'lodash/debounce'; import React, {useEffect, useState} from 'react'; import {Keyboard, View} from 'react-native'; @@ -16,6 +17,8 @@ import type {AnimatedTextInputRef} from '@components/RNTextInput'; import ScreenWrapper from '@components/ScreenWrapper'; import Text from '@components/Text'; import TextInput from '@components/TextInput'; +import type {WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails'; +import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails'; import useAutoFocusInput from '@hooks/useAutoFocusInput'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -50,11 +53,20 @@ type WorkspaceInviteMessagePageOnyxProps = { type WorkspaceInviteMessagePageProps = WithPolicyAndFullscreenLoadingProps & WorkspaceInviteMessagePageOnyxProps & + WithCurrentUserPersonalDetailsProps & StackScreenProps; -function WorkspaceInviteMessagePage({workspaceInviteMessageDraft, invitedEmailsToAccountIDsDraft, policy, route, allPersonalDetails}: WorkspaceInviteMessagePageProps) { +function WorkspaceInviteMessagePage({ + workspaceInviteMessageDraft, + invitedEmailsToAccountIDsDraft, + policy, + route, + allPersonalDetails, + currentUserPersonalDetails, +}: WorkspaceInviteMessagePageProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); + const parser = new ExpensiMark(); const [welcomeNote, setWelcomeNote] = useState(); @@ -66,6 +78,10 @@ function WorkspaceInviteMessagePage({workspaceInviteMessageDraft, invitedEmailsT workspaceInviteMessageDraft || translate('workspace.inviteMessage.welcomeNote', { workspaceName: policy?.name ?? '', + senderDisplayName: currentUserPersonalDetails?.displayName ?? '', + senderLogin: currentUserPersonalDetails?.login ?? '', + workspaceDescription: parser.htmlToMarkdown(policy?.description ?? ''), + workspaceLink: ROUTES.WORKSPACE_PROFILE.getRoute(route.params.policyID), }); useEffect(() => { @@ -200,15 +216,17 @@ function WorkspaceInviteMessagePage({workspaceInviteMessageDraft, invitedEmailsT WorkspaceInviteMessagePage.displayName = 'WorkspaceInviteMessagePage'; export default withPolicyAndFullscreenLoading( - withOnyx({ - allPersonalDetails: { - key: ONYXKEYS.PERSONAL_DETAILS_LIST, - }, - invitedEmailsToAccountIDsDraft: { - key: ({route}) => `${ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MEMBERS_DRAFT}${route.params.policyID.toString()}`, - }, - workspaceInviteMessageDraft: { - key: ({route}) => `${ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MESSAGE_DRAFT}${route.params.policyID.toString()}`, - }, - })(WorkspaceInviteMessagePage), + withCurrentUserPersonalDetails( + withOnyx({ + allPersonalDetails: { + key: ONYXKEYS.PERSONAL_DETAILS_LIST, + }, + invitedEmailsToAccountIDsDraft: { + key: ({route}) => `${ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MEMBERS_DRAFT}${route.params.policyID.toString()}`, + }, + workspaceInviteMessageDraft: { + key: ({route}) => `${ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MESSAGE_DRAFT}${route.params.policyID.toString()}`, + }, + })(WorkspaceInviteMessagePage), + ), ); From a9165f3a6e3e25af759b9abcdb2b9644a43b5048 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Thu, 14 Mar 2024 01:13:38 +0700 Subject: [PATCH 002/106] add welcome note es --- src/languages/en.ts | 2 +- src/languages/es.ts | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 8c120d58fa3c..a27635b100c7 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1966,7 +1966,7 @@ export default { inviteNoMembersError: 'Please select at least one member to invite', welcomeNote: ({workspaceName, senderDisplayName, senderLogin, workspaceDescription, workspaceLink}: WelcomeNoteParams) => `${ senderDisplayName ? `#${senderDisplayName} (${senderLogin}) invited you to Expensify` : `#${senderLogin} invited you to Expensify` - }\nHi there -\n\nTo join "${workspaceName}", [click here](${workspaceLink}) to start tracking your expenses!\n\nThey also added the message:\nYou have been invited to ${ + }\nHi there 1 -\n\nTo join "${workspaceName}", [click here](${workspaceLink}) to start tracking your expenses!\n\nThey also added the message:\nYou have been invited to ${ workspaceName || 'a workspace' }! Download the Expensify mobile app at use.expensify.com/download to start tracking your expenses.\n\n${ workspaceDescription ? `Additionally, here is the workspace description:\n ${workspaceDescription}` : '' diff --git a/src/languages/es.ts b/src/languages/es.ts index 267581f043ee..d76790b1281d 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1989,8 +1989,14 @@ export default { personalMessagePrompt: 'Mensaje', inviteNoMembersError: 'Por favor, selecciona al menos un miembro a invitar', genericFailureMessage: 'Se produjo un error al invitar al usuario al espacio de trabajo. Vuelva a intentarlo..', - welcomeNote: ({workspaceName}: WelcomeNoteParams) => - `¡Has sido invitado a ${workspaceName}! Descargue la aplicación móvil Expensify en use.expensify.com/download para comenzar a rastrear sus gastos.`, + welcomeNote: ({workspaceName, senderDisplayName, senderLogin, workspaceDescription, workspaceLink}: WelcomeNoteParams) => `${ + senderDisplayName ? `#${senderDisplayName} (${senderLogin}) te invitó a Expensify` : `#${senderLogin} te invitó a Expensify` + }\n¡Hola! -\n\nPara unirte a "${workspaceName}", [haz clic aquí](${workspaceLink}) para empezar a seguir tus gastos!\n\nTambién añadieron el mensaje:\nHas sido invitado a ${ + workspaceName || 'un espacio de trabajo' + }! Descarga la aplicación móvil de Expensify en use.expensify.com/download para empezar a seguir tus gastos.\n\n${ + workspaceDescription ? `Además, aquí está la descripción del espacio de trabajo:\n ${workspaceDescription}` : '' + } + `, }, distanceRates: { oopsNotSoFast: 'Ups! No tan rápido...', From 0d13ebc2cf2edbc626504f6ef81efaed9aa7c955 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Sat, 23 Mar 2024 00:39:36 +0300 Subject: [PATCH 003/106] fix chat thread reimbursement queued action bugs --- src/libs/ReportActionsUtils.ts | 2 +- src/libs/ReportUtils.ts | 23 +++++++++++++++++----- src/pages/home/report/ReportActionItem.tsx | 9 +++++---- 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 05701c3e321f..a163cefd43a3 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -187,7 +187,7 @@ function isThreadParentMessage(reportAction: OnyxEntry, reportID: * * @deprecated Use Onyx.connect() or withOnyx() instead */ -function getParentReportAction(report: OnyxEntry | EmptyObject): ReportAction | Record { +function getParentReportAction(report: OnyxEntry | EmptyObject): ReportAction | EmptyObject { if (!report?.parentReportID || !report.parentReportActionID) { return {}; } diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index d0ce53a0e10b..c4ebe2335fba 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -2731,6 +2731,23 @@ function getAdminRoomInvitedParticipants(parentReportAction: ReportAction | Reco return roomName ? `${verb} ${users} ${preposition} ${roomName}` : `${verb} ${users}`; } +/** + * Get the report action message for a report action. + */ + +function getReportActionMessage(reportAction: ReportAction | EmptyObject, parentReportID?: string) { + if (isEmptyObject(reportAction)) { + return ''; + } + if (ReportActionsUtils.isApprovedOrSubmittedReportAction(reportAction)) { + return ReportActionsUtils.getReportActionMessageText(reportAction); + } + if (ReportActionsUtils.isReimbursementQueuedAction(reportAction)) { + return getReimbursementQueuedActionMessage(reportAction, getReport(parentReportID) as OnyxEntry, false); + } + return reportAction?.message?.[0]?.text ?? ''; +} + /** * Get the title for a report. */ @@ -2751,11 +2768,7 @@ function getReportName(report: OnyxEntry, policy: OnyxEntry = nu } const isAttachment = ReportActionsUtils.isReportActionAttachment(!isEmptyObject(parentReportAction) ? parentReportAction : null); - const parentReportActionMessage = ( - ReportActionsUtils.isApprovedOrSubmittedReportAction(parentReportAction) - ? ReportActionsUtils.getReportActionMessageText(parentReportAction) - : parentReportAction?.message?.[0]?.text ?? '' - ).replace(/(\r\n|\n|\r)/gm, ' '); + const parentReportActionMessage = getReportActionMessage(parentReportAction, report?.parentReportID).replace(/(\r\n|\n|\r)/gm, ' '); if (isAttachment && parentReportActionMessage) { return `[${Localize.translateLocal('common.attachment')}]`; } diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index 0e8b0cf97d1e..8971bd2af970 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -466,10 +466,11 @@ function ReportActionItem({ ); } else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENTQUEUED) { - const submitterDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(personalDetails[report.ownerAccountID ?? -1]); + const linkedReport = ReportUtils.isChatThread(report) ? ReportUtils.getReport(report.parentReportID) : report; + const submitterDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(personalDetails[linkedReport?.ownerAccountID ?? -1]); const paymentType = action.originalMessage.paymentType ?? ''; - const missingPaymentMethod = ReportUtils.getIndicatedMissingPaymentMethod(userWallet, report.reportID, action); + const missingPaymentMethod = ReportUtils.getIndicatedMissingPaymentMethod(userWallet, linkedReport?.reportID ?? '', action); children = ( BankAccounts.openPersonalBankAccountSetupView(report.reportID)} + onPress={() => BankAccounts.openPersonalBankAccountSetupView(linkedReport?.reportID)} pressOnEnter large /> @@ -491,7 +492,7 @@ function ReportActionItem({ enablePaymentsRoute={ROUTES.ENABLE_PAYMENTS} addBankAccountRoute={ROUTES.BANK_ACCOUNT_PERSONAL} addDebitCardRoute={ROUTES.SETTINGS_ADD_DEBIT_CARD} - chatReportID={report.reportID} + chatReportID={linkedReport?.reportID} iouReport={iouReport} > {(triggerKYCFlow, buttonRef) => ( From 8045460bdc940c5015d20a6ce43b03a6f438069f Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Sun, 24 Mar 2024 16:43:00 +0800 Subject: [PATCH 004/106] revert #34075 --- src/libs/actions/IOU.ts | 18 +---- .../iou/request/step/IOURequestStepAmount.js | 28 ++----- .../step/IOURequestStepConfirmation.js | 79 ++++++++----------- .../request/step/IOURequestStepCurrency.js | 14 +++- .../step/IOURequestStepTaxAmountPage.js | 29 ++----- 5 files changed, 54 insertions(+), 114 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 5632268ef6ca..2e7c36115641 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -329,11 +329,7 @@ function startMoneyRequest(iouType: ValueOf, reportID: st } // eslint-disable-next-line @typescript-eslint/naming-convention -function setMoneyRequestAmount_temporaryForRefactor(transactionID: string, amount: number, currency: string, removeOriginalCurrency = false) { - if (removeOriginalCurrency) { - Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {amount, currency, originalCurrency: null}); - return; - } +function setMoneyRequestAmount_temporaryForRefactor(transactionID: string, amount: number, currency: string) { Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {amount, currency}); } @@ -343,19 +339,10 @@ function setMoneyRequestCreated(transactionID: string, created: string, isDraft: } // eslint-disable-next-line @typescript-eslint/naming-convention -function setMoneyRequestCurrency_temporaryForRefactor(transactionID: string, currency: string, removeOriginalCurrency = false) { - if (removeOriginalCurrency) { - Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {currency, originalCurrency: null}); - return; - } +function setMoneyRequestCurrency_temporaryForRefactor(transactionID: string, currency: string) { Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {currency}); } -// eslint-disable-next-line @typescript-eslint/naming-convention -function setMoneyRequestOriginalCurrency_temporaryForRefactor(transactionID: string, originalCurrency: string) { - Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {originalCurrency}); -} - function setMoneyRequestDescription(transactionID: string, comment: string, isDraft: boolean) { Onyx.merge(`${isDraft ? ONYXKEYS.COLLECTION.TRANSACTION_DRAFT : ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, {comment: {comment: comment.trim()}}); } @@ -5179,7 +5166,6 @@ export { setMoneyRequestCreated, setMoneyRequestCurrency_temporaryForRefactor, setMoneyRequestDescription, - setMoneyRequestOriginalCurrency_temporaryForRefactor, setMoneyRequestParticipants_temporaryForRefactor, setMoneyRequestPendingFields, setMoneyRequestReceipt, diff --git a/src/pages/iou/request/step/IOURequestStepAmount.js b/src/pages/iou/request/step/IOURequestStepAmount.js index 9fdd2bea24f4..740cbe105a57 100644 --- a/src/pages/iou/request/step/IOURequestStepAmount.js +++ b/src/pages/iou/request/step/IOURequestStepAmount.js @@ -1,7 +1,7 @@ import {useFocusEffect} from '@react-navigation/native'; import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; -import React, {useCallback, useEffect, useRef} from 'react'; +import React, {useCallback, useRef} from 'react'; import {withOnyx} from 'react-native-onyx'; import taxPropTypes from '@components/taxPropTypes'; import transactionPropTypes from '@components/transactionPropTypes'; @@ -67,18 +67,17 @@ const getTaxAmount = (transaction, defaultTaxValue, amount) => { function IOURequestStepAmount({ report, route: { - params: {iouType, reportID, transactionID, backTo}, + params: {iouType, reportID, transactionID, backTo, currency: selectedCurrency}, }, transaction, - transaction: {currency}, + transaction: {currency: originalCurrency}, policy, }) { const {translate} = useLocalize(); const textInput = useRef(null); const focusTimeoutRef = useRef(null); - const isSaveButtonPressed = useRef(false); - const originalCurrency = useRef(null); const iouRequestType = getRequestType(transaction); + const currency = selectedCurrency || originalCurrency; const taxRates = lodashGet(policy, 'taxRates', {}); const isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(ReportUtils.getRootParentReport(report)); @@ -96,22 +95,6 @@ function IOURequestStepAmount({ }, []), ); - useEffect(() => { - if (transaction.originalCurrency) { - originalCurrency.current = transaction.originalCurrency; - } else { - originalCurrency.current = currency; - IOU.setMoneyRequestOriginalCurrency_temporaryForRefactor(transactionID, currency); - } - return () => { - if (isSaveButtonPressed.current) { - return; - } - IOU.setMoneyRequestCurrency_temporaryForRefactor(transactionID, originalCurrency.current, true); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - const navigateBack = () => { Navigation.goBack(backTo); }; @@ -124,7 +107,6 @@ function IOURequestStepAmount({ * @param {Number} amount */ const navigateToNextPage = ({amount}) => { - isSaveButtonPressed.current = true; const amountInSmallestCurrencyUnits = CurrencyUtils.convertToBackendAmount(Number.parseFloat(amount)); if ((iouRequestType === CONST.IOU.REQUEST_TYPE.MANUAL || backTo) && isTaxTrackingEnabled) { @@ -133,7 +115,7 @@ function IOURequestStepAmount({ IOU.setMoneyRequestTaxAmount(transaction.transactionID, taxAmountInSmallestCurrencyUnits); } - IOU.setMoneyRequestAmount_temporaryForRefactor(transactionID, amountInSmallestCurrencyUnits, currency || CONST.CURRENCY.USD, true); + IOU.setMoneyRequestAmount_temporaryForRefactor(transactionID, amountInSmallestCurrencyUnits, currency || CONST.CURRENCY.USD); if (backTo) { Navigation.goBack(backTo); diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.js b/src/pages/iou/request/step/IOURequestStepConfirmation.js index 435121a76028..7c5683974a0e 100644 --- a/src/pages/iou/request/step/IOURequestStepConfirmation.js +++ b/src/pages/iou/request/step/IOURequestStepConfirmation.js @@ -5,7 +5,6 @@ import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import categoryPropTypes from '@components/categoryPropTypes'; -import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import * as Expensicons from '@components/Icon/Expensicons'; import MoneyRequestConfirmationList from '@components/MoneyTemporaryForRefactorRequestConfirmationList'; @@ -120,15 +119,6 @@ function IOURequestStepConfirmation({ const isPolicyExpenseChat = useMemo(() => ReportUtils.isPolicyExpenseChat(ReportUtils.getRootParentReport(report)), [report]); const formHasBeenSubmitted = useRef(false); - useEffect(() => { - if (!transaction || !transaction.originalCurrency) { - return; - } - // If user somehow lands on this page without the currency reset, then reset it here. - IOU.setMoneyRequestCurrency_temporaryForRefactor(transactionID, transaction.originalCurrency, true); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - useEffect(() => { const policyExpenseChat = _.find(participants, (participant) => participant.isPolicyExpenseChat); if (policyExpenseChat) { @@ -492,10 +482,6 @@ function IOURequestStepConfirmation({ IOU.setMoneyRequestBillable_temporaryForRefactor(transactionID, billable); }; - // This loading indicator is shown because the transaction originalCurrency is being updated later than the component mounts. - // To prevent the component from rendering with the wrong currency, we show a loading indicator until the correct currency is set. - const isLoading = !!(transaction && transaction.originalCurrency); - return ( - {isLoading && } - - - + )} diff --git a/src/pages/iou/request/step/IOURequestStepCurrency.js b/src/pages/iou/request/step/IOURequestStepCurrency.js index 43e4e9bf0eaa..49bfbd8355d7 100644 --- a/src/pages/iou/request/step/IOURequestStepCurrency.js +++ b/src/pages/iou/request/step/IOURequestStepCurrency.js @@ -60,14 +60,18 @@ function IOURequestStepCurrency({ const [searchValue, setSearchValue] = useState(''); const optionsSelectorRef = useRef(); - const navigateBack = () => { + const navigateBack = (selectedCurrency = undefined) => { // If the currency selection was done from the confirmation step (eg. + > request money > manual > confirm > amount > currency) // then the user needs taken back to the confirmation page instead of the initial amount page. This is because the route params // are only able to handle one backTo param at a time and the user needs to go back to the amount page before going back // to the confirmation page if (pageIndex === 'confirm') { const routeToAmountPageWithConfirmationAsBackTo = getUrlWithBackToParam(backTo, `/${ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(iouType, transactionID, reportID)}`); - Navigation.goBack(routeToAmountPageWithConfirmationAsBackTo); + if (selectedCurrency) { + Navigation.navigate(`${routeToAmountPageWithConfirmationAsBackTo}¤cy=${selectedCurrency}`); + } else { + Navigation.goBack(routeToAmountPageWithConfirmationAsBackTo); + } return; } Navigation.goBack(backTo); @@ -79,8 +83,10 @@ function IOURequestStepCurrency({ */ const confirmCurrencySelection = (option) => { Keyboard.dismiss(); - IOU.setMoneyRequestCurrency_temporaryForRefactor(transactionID, option.currencyCode); - navigateBack(); + if (pageIndex !== 'confirm') { + IOU.setMoneyRequestCurrency_temporaryForRefactor(transactionID, option.currencyCode); + } + navigateBack(option.currencyCode); }; const {sections, headerMessage, initiallyFocusedOptionKey} = useMemo(() => { diff --git a/src/pages/iou/request/step/IOURequestStepTaxAmountPage.js b/src/pages/iou/request/step/IOURequestStepTaxAmountPage.js index 7a75e9f48805..292ad78dac45 100644 --- a/src/pages/iou/request/step/IOURequestStepTaxAmountPage.js +++ b/src/pages/iou/request/step/IOURequestStepTaxAmountPage.js @@ -1,7 +1,7 @@ import {useFocusEffect} from '@react-navigation/native'; import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; -import React, {useCallback, useEffect, useRef} from 'react'; +import React, {useCallback, useRef} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; @@ -58,10 +58,10 @@ const getTaxAmount = (transaction, defaultTaxValue) => { function IOURequestStepTaxAmountPage({ route: { - params: {iouType, reportID, transactionID, backTo}, + params: {iouType, reportID, transactionID, backTo, currency: selectedCurrency}, }, transaction, - transaction: {currency}, + transaction: {currency: originalCurrency}, report, policy, }) { @@ -70,28 +70,12 @@ function IOURequestStepTaxAmountPage({ const textInput = useRef(null); const isEditing = Navigation.getActiveRoute().includes('taxAmount'); + const currency = selectedCurrency || originalCurrency; + const focusTimeoutRef = useRef(null); - const isSaveButtonPressed = useRef(false); - const originalCurrency = useRef(null); const taxRates = lodashGet(policy, 'taxRates', {}); - useEffect(() => { - if (transaction.originalCurrency) { - originalCurrency.current = transaction.originalCurrency; - } else { - originalCurrency.current = currency; - IOU.setMoneyRequestOriginalCurrency_temporaryForRefactor(transactionID, currency); - } - return () => { - if (isSaveButtonPressed.current) { - return; - } - IOU.setMoneyRequestCurrency_temporaryForRefactor(transactionID, originalCurrency.current, true); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - useFocusEffect( useCallback(() => { focusTimeoutRef.current = setTimeout(() => textInput.current && textInput.current.focus(), CONST.ANIMATED_TRANSITION); @@ -116,11 +100,10 @@ function IOURequestStepTaxAmountPage({ }; const updateTaxAmount = (currentAmount) => { - isSaveButtonPressed.current = true; const amountInSmallestCurrencyUnits = CurrencyUtils.convertToBackendAmount(Number.parseFloat(currentAmount.amount)); IOU.setMoneyRequestTaxAmount(transactionID, amountInSmallestCurrencyUnits); - IOU.setMoneyRequestCurrency_temporaryForRefactor(transactionID, currency || CONST.CURRENCY.USD, true); + IOU.setMoneyRequestCurrency_temporaryForRefactor(transactionID, currency || CONST.CURRENCY.USD); if (backTo) { Navigation.goBack(backTo); From 90a53e011929f92c158a5b1bd981bbde3679a056 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Sun, 24 Mar 2024 16:57:03 +0800 Subject: [PATCH 005/106] allow currency page to accept currency params and show it if available --- src/ROUTES.ts | 4 ++-- src/pages/iou/request/step/IOURequestStepAmount.js | 2 +- src/pages/iou/request/step/IOURequestStepCurrency.js | 11 ++++++----- .../iou/request/step/IOURequestStepTaxAmountPage.js | 2 +- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index c216d5ac288c..c41dc0558da5 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -335,8 +335,8 @@ const ROUTES = { }, MONEY_REQUEST_STEP_CURRENCY: { route: 'create/:iouType/currency/:transactionID/:reportID/:pageIndex?', - getRoute: (iouType: ValueOf, transactionID: string, reportID: string, pageIndex = '', backTo = '') => - getUrlWithBackToParam(`create/${iouType}/currency/${transactionID}/${reportID}/${pageIndex}`, backTo), + getRoute: (iouType: ValueOf, transactionID: string, reportID: string, pageIndex = '', currency = '', backTo = '') => + getUrlWithBackToParam(`create/${iouType}/currency/${transactionID}/${reportID}/${pageIndex}?currency=${currency}`, backTo), }, MONEY_REQUEST_STEP_DATE: { route: ':action/:iouType/date/:transactionID/:reportID', diff --git a/src/pages/iou/request/step/IOURequestStepAmount.js b/src/pages/iou/request/step/IOURequestStepAmount.js index 740cbe105a57..fd70c69e88c3 100644 --- a/src/pages/iou/request/step/IOURequestStepAmount.js +++ b/src/pages/iou/request/step/IOURequestStepAmount.js @@ -100,7 +100,7 @@ function IOURequestStepAmount({ }; const navigateToCurrencySelectionPage = () => { - Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CURRENCY.getRoute(iouType, transactionID, reportID, backTo ? 'confirm' : '', Navigation.getActiveRouteWithoutParams())); + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CURRENCY.getRoute(iouType, transactionID, reportID, backTo ? 'confirm' : '', currency, Navigation.getActiveRouteWithoutParams())); }; /** diff --git a/src/pages/iou/request/step/IOURequestStepCurrency.js b/src/pages/iou/request/step/IOURequestStepCurrency.js index 49bfbd8355d7..5af9d27dbda1 100644 --- a/src/pages/iou/request/step/IOURequestStepCurrency.js +++ b/src/pages/iou/request/step/IOURequestStepCurrency.js @@ -52,23 +52,24 @@ const defaultProps = { function IOURequestStepCurrency({ currencyList, route: { - params: {backTo, iouType, pageIndex, reportID, transactionID}, + params: {backTo, iouType, pageIndex, reportID, transactionID, currency: selectedCurrency}, }, - transaction: {currency}, + transaction: {currency: originalCurrency}, }) { const {translate} = useLocalize(); const [searchValue, setSearchValue] = useState(''); const optionsSelectorRef = useRef(); + const currency = selectedCurrency || originalCurrency; - const navigateBack = (selectedCurrency = undefined) => { + const navigateBack = (selectedCurrencyValue = undefined) => { // If the currency selection was done from the confirmation step (eg. + > request money > manual > confirm > amount > currency) // then the user needs taken back to the confirmation page instead of the initial amount page. This is because the route params // are only able to handle one backTo param at a time and the user needs to go back to the amount page before going back // to the confirmation page if (pageIndex === 'confirm') { const routeToAmountPageWithConfirmationAsBackTo = getUrlWithBackToParam(backTo, `/${ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(iouType, transactionID, reportID)}`); - if (selectedCurrency) { - Navigation.navigate(`${routeToAmountPageWithConfirmationAsBackTo}¤cy=${selectedCurrency}`); + if (selectedCurrencyValue) { + Navigation.navigate(`${routeToAmountPageWithConfirmationAsBackTo}¤cy=${selectedCurrencyValue}`); } else { Navigation.goBack(routeToAmountPageWithConfirmationAsBackTo); } diff --git a/src/pages/iou/request/step/IOURequestStepTaxAmountPage.js b/src/pages/iou/request/step/IOURequestStepTaxAmountPage.js index 292ad78dac45..32e70fec5e16 100644 --- a/src/pages/iou/request/step/IOURequestStepTaxAmountPage.js +++ b/src/pages/iou/request/step/IOURequestStepTaxAmountPage.js @@ -96,7 +96,7 @@ function IOURequestStepTaxAmountPage({ // If the money request being created is a distance request, don't allow the user to choose the currency. // Only USD is allowed for distance requests. // Remove query from the route and encode it. - Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CURRENCY.getRoute(iouType, transactionID, reportID, backTo ? 'confirm' : '', Navigation.getActiveRouteWithoutParams())); + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CURRENCY.getRoute(iouType, transactionID, reportID, backTo ? 'confirm' : '', currency, Navigation.getActiveRouteWithoutParams())); }; const updateTaxAmount = (currentAmount) => { From b6f1979d8a939fafb45adae396a83880b1c7c603 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Sun, 24 Mar 2024 17:00:17 +0800 Subject: [PATCH 006/106] show saved currency if the currency param is invalid --- src/pages/iou/request/step/IOURequestStepAmount.js | 2 +- src/pages/iou/request/step/IOURequestStepCurrency.js | 2 +- src/pages/iou/request/step/IOURequestStepTaxAmountPage.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepAmount.js b/src/pages/iou/request/step/IOURequestStepAmount.js index fd70c69e88c3..0c8af94ad841 100644 --- a/src/pages/iou/request/step/IOURequestStepAmount.js +++ b/src/pages/iou/request/step/IOURequestStepAmount.js @@ -77,7 +77,7 @@ function IOURequestStepAmount({ const textInput = useRef(null); const focusTimeoutRef = useRef(null); const iouRequestType = getRequestType(transaction); - const currency = selectedCurrency || originalCurrency; + const currency = CurrencyUtils.isValidCurrencyCode(selectedCurrency) ? selectedCurrency : originalCurrency; const taxRates = lodashGet(policy, 'taxRates', {}); const isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(ReportUtils.getRootParentReport(report)); diff --git a/src/pages/iou/request/step/IOURequestStepCurrency.js b/src/pages/iou/request/step/IOURequestStepCurrency.js index 5af9d27dbda1..53d04a0ae0ed 100644 --- a/src/pages/iou/request/step/IOURequestStepCurrency.js +++ b/src/pages/iou/request/step/IOURequestStepCurrency.js @@ -59,7 +59,7 @@ function IOURequestStepCurrency({ const {translate} = useLocalize(); const [searchValue, setSearchValue] = useState(''); const optionsSelectorRef = useRef(); - const currency = selectedCurrency || originalCurrency; + const currency = CurrencyUtils.isValidCurrencyCode(selectedCurrency) ? selectedCurrency : originalCurrency; const navigateBack = (selectedCurrencyValue = undefined) => { // If the currency selection was done from the confirmation step (eg. + > request money > manual > confirm > amount > currency) diff --git a/src/pages/iou/request/step/IOURequestStepTaxAmountPage.js b/src/pages/iou/request/step/IOURequestStepTaxAmountPage.js index 32e70fec5e16..7d84a91ebd2b 100644 --- a/src/pages/iou/request/step/IOURequestStepTaxAmountPage.js +++ b/src/pages/iou/request/step/IOURequestStepTaxAmountPage.js @@ -70,7 +70,7 @@ function IOURequestStepTaxAmountPage({ const textInput = useRef(null); const isEditing = Navigation.getActiveRoute().includes('taxAmount'); - const currency = selectedCurrency || originalCurrency; + const currency = CurrencyUtils.isValidCurrencyCode(selectedCurrency) ? selectedCurrency : originalCurrency; const focusTimeoutRef = useRef(null); From f91b49a6b001338e5386e1fb296060822c614414 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Fri, 29 Mar 2024 10:40:01 +0300 Subject: [PATCH 007/106] changed exitReportID to top most report id --- src/pages/home/report/ReportActionItem.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index fb338075d630..46009a69d4d5 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -481,7 +481,7 @@ function ReportActionItem({ success style={[styles.w100, styles.requestPreviewBox]} text={translate('bankAccount.addBankAccount')} - onPress={() => BankAccounts.openPersonalBankAccountSetupView(linkedReport?.reportID)} + onPress={() => BankAccounts.openPersonalBankAccountSetupView(Navigation.getTopmostReportId() ?? linkedReport?.reportID)} pressOnEnter large /> From 5750264acae685f6744ac991b0e818961d41fe00 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Sat, 30 Mar 2024 16:30:18 +0700 Subject: [PATCH 008/106] update welcome note --- src/languages/en.ts | 10 ++----- src/languages/es.ts | 10 ++----- src/languages/types.ts | 2 +- .../workspace/WorkspaceInviteMessagePage.tsx | 26 +++++++++++++------ 4 files changed, 23 insertions(+), 25 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index f83d78ac14e9..7c4eb6e3328b 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2048,14 +2048,8 @@ export default { personalMessagePrompt: 'Message', genericFailureMessage: 'An error occurred inviting the user to the workspace, please try again.', inviteNoMembersError: 'Please select at least one member to invite', - welcomeNote: ({workspaceName, senderDisplayName, senderLogin, workspaceDescription, workspaceLink}: WelcomeNoteParams) => `${ - senderDisplayName ? `#${senderDisplayName} (${senderLogin}) invited you to Expensify` : `#${senderLogin} invited you to Expensify` - }\nHi there 1 -\n\nTo join "${workspaceName}", [click here](${workspaceLink}) to start tracking your expenses!\n\nThey also added the message:\nYou have been invited to ${ - workspaceName || 'a workspace' - }! Download the Expensify mobile app at use.expensify.com/download to start tracking your expenses.\n\n${ - workspaceDescription ? `Additionally, here is the workspace description:\n ${workspaceDescription}` : '' - } - `, + welcomeNote: ({workspaceName, senderDisplayName, senderLogin, inviteMessage}: WelcomeNoteParams) => + `${`${senderDisplayName !== senderLogin ? `${senderDisplayName} (${senderLogin})` : senderLogin} invited you to ${workspaceName}`}\n\n${inviteMessage}`, }, distanceRates: { oopsNotSoFast: 'Oops! Not so fast...', diff --git a/src/languages/es.ts b/src/languages/es.ts index ac6ef2f3f9b7..3849999be62a 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2076,14 +2076,8 @@ export default { personalMessagePrompt: 'Mensaje', inviteNoMembersError: 'Por favor, selecciona al menos un miembro a invitar', genericFailureMessage: 'Se produjo un error al invitar al usuario al espacio de trabajo. Vuelva a intentarlo..', - welcomeNote: ({workspaceName, senderDisplayName, senderLogin, workspaceDescription, workspaceLink}: WelcomeNoteParams) => `${ - senderDisplayName ? `#${senderDisplayName} (${senderLogin}) te invitó a Expensify` : `#${senderLogin} te invitó a Expensify` - }\n¡Hola! -\n\nPara unirte a "${workspaceName}", [haz clic aquí](${workspaceLink}) para empezar a seguir tus gastos!\n\nTambién añadieron el mensaje:\nHas sido invitado a ${ - workspaceName || 'un espacio de trabajo' - }! Descarga la aplicación móvil de Expensify en use.expensify.com/download para empezar a seguir tus gastos.\n\n${ - workspaceDescription ? `Además, aquí está la descripción del espacio de trabajo:\n ${workspaceDescription}` : '' - } - `, + welcomeNote: ({workspaceName, senderDisplayName, senderLogin, inviteMessage}: WelcomeNoteParams) => + `${`${senderDisplayName !== senderLogin ? `${senderDisplayName} (${senderLogin})` : senderLogin} te invitó a ${workspaceName}`}\n\n${inviteMessage}`, }, distanceRates: { oopsNotSoFast: 'Ups! No tan rápido...', diff --git a/src/languages/types.ts b/src/languages/types.ts index df40a8d25425..98ef55f58587 100644 --- a/src/languages/types.ts +++ b/src/languages/types.ts @@ -185,7 +185,7 @@ type UserIsAlreadyMemberParams = {login: string; name: string}; type GoToRoomParams = {roomName: string}; -type WelcomeNoteParams = {workspaceName: string; senderDisplayName: string; senderLogin: string; workspaceDescription: string; workspaceLink: string}; +type WelcomeNoteParams = {workspaceName: string; senderDisplayName?: string; senderLogin?: string; inviteMessage?: string}; type RoomNameReservedErrorParams = {reservedName: string}; diff --git a/src/pages/workspace/WorkspaceInviteMessagePage.tsx b/src/pages/workspace/WorkspaceInviteMessagePage.tsx index 99c7f4dd41a8..8b4ecdf31887 100644 --- a/src/pages/workspace/WorkspaceInviteMessagePage.tsx +++ b/src/pages/workspace/WorkspaceInviteMessagePage.tsx @@ -76,13 +76,14 @@ function WorkspaceInviteMessagePage({ // workspaceInviteMessageDraft can be an empty string // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing workspaceInviteMessageDraft || - translate('workspace.inviteMessage.welcomeNote', { - workspaceName: policy?.name ?? '', - senderDisplayName: currentUserPersonalDetails?.displayName ?? '', - senderLogin: currentUserPersonalDetails?.login ?? '', - workspaceDescription: parser.htmlToMarkdown(policy?.description ?? ''), - workspaceLink: ROUTES.WORKSPACE_PROFILE.getRoute(route.params.policyID), - }); + // policy?.description can be an empty string + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + policy?.description || + parser.replace( + translate('workspace.common.welcomeNote', { + workspaceName: policy?.name ?? '', + }), + ); useEffect(() => { if (!isEmptyObject(invitedEmailsToAccountIDsDraft)) { @@ -100,7 +101,16 @@ function WorkspaceInviteMessagePage({ const sendInvitation = () => { Keyboard.dismiss(); // Please see https://github.com/Expensify/App/blob/main/README.md#Security for more details - Policy.addMembersToWorkspace(invitedEmailsToAccountIDsDraft ?? {}, welcomeNote ?? '', route.params.policyID); + Policy.addMembersToWorkspace( + invitedEmailsToAccountIDsDraft ?? {}, + translate('workspace.inviteMessage.welcomeNote', { + workspaceName: policy?.name ?? '', + senderDisplayName: currentUserPersonalDetails?.displayName ?? '', + senderLogin: currentUserPersonalDetails?.login ?? '', + inviteMessage: welcomeNote, + }), + route.params.policyID, + ); debouncedSaveDraft(null); SearchInputManager.searchInput = ''; // Pop the invite message page before navigating to the members page. From a1fe399a89167c26ef64ae1a1527001684995775 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Tue, 2 Apr 2024 13:02:05 +0700 Subject: [PATCH 009/106] update default welcome note --- src/languages/en.ts | 2 - src/languages/es.ts | 2 - src/languages/types.ts | 2 +- .../workspace/WorkspaceInviteMessagePage.tsx | 53 +++++++------------ src/types/onyx/Policy.ts | 5 ++ 5 files changed, 24 insertions(+), 40 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 28854ca4d0f4..fd2daa50942f 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2054,8 +2054,6 @@ export default { personalMessagePrompt: 'Message', genericFailureMessage: 'An error occurred inviting the user to the workspace, please try again.', inviteNoMembersError: 'Please select at least one member to invite', - welcomeNote: ({workspaceName, senderDisplayName, senderLogin, inviteMessage}: WelcomeNoteParams) => - `${`${senderDisplayName !== senderLogin ? `${senderDisplayName} (${senderLogin})` : senderLogin} invited you to ${workspaceName}`}\n\n${inviteMessage}`, }, distanceRates: { oopsNotSoFast: 'Oops! Not so fast...', diff --git a/src/languages/es.ts b/src/languages/es.ts index acbe8dc064ce..1482ad5e0c5c 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2082,8 +2082,6 @@ export default { personalMessagePrompt: 'Mensaje', inviteNoMembersError: 'Por favor, selecciona al menos un miembro a invitar', genericFailureMessage: 'Se produjo un error al invitar al usuario al espacio de trabajo. Vuelva a intentarlo..', - welcomeNote: ({workspaceName, senderDisplayName, senderLogin, inviteMessage}: WelcomeNoteParams) => - `${`${senderDisplayName !== senderLogin ? `${senderDisplayName} (${senderLogin})` : senderLogin} te invitó a ${workspaceName}`}\n\n${inviteMessage}`, }, distanceRates: { oopsNotSoFast: 'Ups! No tan rápido...', diff --git a/src/languages/types.ts b/src/languages/types.ts index 98ef55f58587..c365363f84af 100644 --- a/src/languages/types.ts +++ b/src/languages/types.ts @@ -185,7 +185,7 @@ type UserIsAlreadyMemberParams = {login: string; name: string}; type GoToRoomParams = {roomName: string}; -type WelcomeNoteParams = {workspaceName: string; senderDisplayName?: string; senderLogin?: string; inviteMessage?: string}; +type WelcomeNoteParams = {workspaceName: string}; type RoomNameReservedErrorParams = {reservedName: string}; diff --git a/src/pages/workspace/WorkspaceInviteMessagePage.tsx b/src/pages/workspace/WorkspaceInviteMessagePage.tsx index 8b4ecdf31887..f060eb7746d1 100644 --- a/src/pages/workspace/WorkspaceInviteMessagePage.tsx +++ b/src/pages/workspace/WorkspaceInviteMessagePage.tsx @@ -17,8 +17,6 @@ import type {AnimatedTextInputRef} from '@components/RNTextInput'; import ScreenWrapper from '@components/ScreenWrapper'; import Text from '@components/Text'; import TextInput from '@components/TextInput'; -import type {WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails'; -import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails'; import useAutoFocusInput from '@hooks/useAutoFocusInput'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -53,20 +51,13 @@ type WorkspaceInviteMessagePageOnyxProps = { type WorkspaceInviteMessagePageProps = WithPolicyAndFullscreenLoadingProps & WorkspaceInviteMessagePageOnyxProps & - WithCurrentUserPersonalDetailsProps & StackScreenProps; -function WorkspaceInviteMessagePage({ - workspaceInviteMessageDraft, - invitedEmailsToAccountIDsDraft, - policy, - route, - allPersonalDetails, - currentUserPersonalDetails, -}: WorkspaceInviteMessagePageProps) { +const parser = new ExpensiMark(); + +function WorkspaceInviteMessagePage({workspaceInviteMessageDraft, invitedEmailsToAccountIDsDraft, policy, route, allPersonalDetails}: WorkspaceInviteMessagePageProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); - const parser = new ExpensiMark(); const [welcomeNote, setWelcomeNote] = useState(); @@ -76,6 +67,9 @@ function WorkspaceInviteMessagePage({ // workspaceInviteMessageDraft can be an empty string // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing workspaceInviteMessageDraft || + // policy?.welcomeNote?.user can be an empty string + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + policy?.welcomeNote?.user || // policy?.description can be an empty string // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing policy?.description || @@ -101,16 +95,7 @@ function WorkspaceInviteMessagePage({ const sendInvitation = () => { Keyboard.dismiss(); // Please see https://github.com/Expensify/App/blob/main/README.md#Security for more details - Policy.addMembersToWorkspace( - invitedEmailsToAccountIDsDraft ?? {}, - translate('workspace.inviteMessage.welcomeNote', { - workspaceName: policy?.name ?? '', - senderDisplayName: currentUserPersonalDetails?.displayName ?? '', - senderLogin: currentUserPersonalDetails?.login ?? '', - inviteMessage: welcomeNote, - }), - route.params.policyID, - ); + Policy.addMembersToWorkspace(invitedEmailsToAccountIDsDraft ?? {}, welcomeNote ?? '', route.params.policyID); debouncedSaveDraft(null); SearchInputManager.searchInput = ''; // Pop the invite message page before navigating to the members page. @@ -226,17 +211,15 @@ function WorkspaceInviteMessagePage({ WorkspaceInviteMessagePage.displayName = 'WorkspaceInviteMessagePage'; export default withPolicyAndFullscreenLoading( - withCurrentUserPersonalDetails( - withOnyx({ - allPersonalDetails: { - key: ONYXKEYS.PERSONAL_DETAILS_LIST, - }, - invitedEmailsToAccountIDsDraft: { - key: ({route}) => `${ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MEMBERS_DRAFT}${route.params.policyID.toString()}`, - }, - workspaceInviteMessageDraft: { - key: ({route}) => `${ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MESSAGE_DRAFT}${route.params.policyID.toString()}`, - }, - })(WorkspaceInviteMessagePage), - ), + withOnyx({ + allPersonalDetails: { + key: ONYXKEYS.PERSONAL_DETAILS_LIST, + }, + invitedEmailsToAccountIDsDraft: { + key: ({route}) => `${ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MEMBERS_DRAFT}${route.params.policyID.toString()}`, + }, + workspaceInviteMessageDraft: { + key: ({route}) => `${ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MESSAGE_DRAFT}${route.params.policyID.toString()}`, + }, + })(WorkspaceInviteMessagePage), ); diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index ddb0c33c2f0c..84d6e217835a 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -438,6 +438,11 @@ type Policy = OnyxCommon.OnyxValueWithOfflineFeedback< /** Indicates if the Policy ownership change is failed */ isChangeOwnerFailed?: boolean; + + /** The welcomeNote of user */ + welcomeNote?: { + user: string; + }; } & Partial, 'generalSettings' | 'addWorkspaceRoom' >; From 394ea6a03fe78c82946a4cb46cc49e2e492af894 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Wed, 3 Apr 2024 14:01:05 +0700 Subject: [PATCH 010/106] add subject line --- src/languages/en.ts | 4 ++ src/languages/es.ts | 4 ++ src/languages/types.ts | 2 +- src/libs/actions/Policy.ts | 3 +- .../workspace/WorkspaceInviteMessagePage.tsx | 49 +++++++++++++------ 5 files changed, 43 insertions(+), 19 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 752d17e37d03..b43656daaa7a 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2060,6 +2060,10 @@ export default { personalMessagePrompt: 'Message', genericFailureMessage: 'An error occurred inviting the user to the workspace, please try again.', inviteNoMembersError: 'Please select at least one member to invite', + welcomeNote: ({inviterDisplayName, workspaceName, inviteMessage}: WelcomeNoteParams) => + `# ${inviterDisplayName} invited you to ${workspaceName || 'a workspace'} + +${inviteMessage}`, }, distanceRates: { oopsNotSoFast: 'Oops! Not so fast...', diff --git a/src/languages/es.ts b/src/languages/es.ts index 53ee6d3fba79..18b7f15d70cf 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2088,6 +2088,10 @@ export default { personalMessagePrompt: 'Mensaje', inviteNoMembersError: 'Por favor, selecciona al menos un miembro a invitar', genericFailureMessage: 'Se produjo un error al invitar al usuario al espacio de trabajo. Vuelva a intentarlo..', + welcomeNote: ({inviterDisplayName, workspaceName, inviteMessage}: WelcomeNoteParams) => + `# ${inviterDisplayName} invited you to ${workspaceName || 'a workspace'} + +${inviteMessage}`, }, distanceRates: { oopsNotSoFast: 'Ups! No tan rápido...', diff --git a/src/languages/types.ts b/src/languages/types.ts index c365363f84af..f64291ff6396 100644 --- a/src/languages/types.ts +++ b/src/languages/types.ts @@ -185,7 +185,7 @@ type UserIsAlreadyMemberParams = {login: string; name: string}; type GoToRoomParams = {roomName: string}; -type WelcomeNoteParams = {workspaceName: string}; +type WelcomeNoteParams = {workspaceName: string; inviterDisplayName?: string; inviteMessage?: string}; type RoomNameReservedErrorParams = {reservedName: string}; diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index 3c34e823ac9a..79ef142e772c 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -1,5 +1,4 @@ import {PUBLIC_DOMAINS} from 'expensify-common/lib/CONST'; -import ExpensiMark from 'expensify-common/lib/ExpensiMark'; import Str from 'expensify-common/lib/str'; import {escapeRegExp} from 'lodash'; import lodashClone from 'lodash/clone'; @@ -1348,7 +1347,7 @@ function addMembersToWorkspace(invitedEmailsToAccountIDs: InvitedEmailsToAccount const params: AddMembersToWorkspaceParams = { employees: JSON.stringify(logins.map((login) => ({email: login}))), - welcomeNote: new ExpensiMark().replace(welcomeNote), + welcomeNote, policyID, }; if (!isEmptyObject(membersChats.reportCreationData)) { diff --git a/src/pages/workspace/WorkspaceInviteMessagePage.tsx b/src/pages/workspace/WorkspaceInviteMessagePage.tsx index f060eb7746d1..be9859a55899 100644 --- a/src/pages/workspace/WorkspaceInviteMessagePage.tsx +++ b/src/pages/workspace/WorkspaceInviteMessagePage.tsx @@ -17,6 +17,8 @@ import type {AnimatedTextInputRef} from '@components/RNTextInput'; import ScreenWrapper from '@components/ScreenWrapper'; import Text from '@components/Text'; import TextInput from '@components/TextInput'; +import type {WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails'; +import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails'; import useAutoFocusInput from '@hooks/useAutoFocusInput'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -50,12 +52,20 @@ type WorkspaceInviteMessagePageOnyxProps = { }; type WorkspaceInviteMessagePageProps = WithPolicyAndFullscreenLoadingProps & + WithCurrentUserPersonalDetailsProps & WorkspaceInviteMessagePageOnyxProps & StackScreenProps; const parser = new ExpensiMark(); -function WorkspaceInviteMessagePage({workspaceInviteMessageDraft, invitedEmailsToAccountIDsDraft, policy, route, allPersonalDetails}: WorkspaceInviteMessagePageProps) { +function WorkspaceInviteMessagePage({ + workspaceInviteMessageDraft, + invitedEmailsToAccountIDsDraft, + policy, + route, + allPersonalDetails, + currentUserPersonalDetails, +}: WorkspaceInviteMessagePageProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); @@ -67,9 +77,6 @@ function WorkspaceInviteMessagePage({workspaceInviteMessageDraft, invitedEmailsT // workspaceInviteMessageDraft can be an empty string // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing workspaceInviteMessageDraft || - // policy?.welcomeNote?.user can be an empty string - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - policy?.welcomeNote?.user || // policy?.description can be an empty string // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing policy?.description || @@ -95,7 +102,15 @@ function WorkspaceInviteMessagePage({workspaceInviteMessageDraft, invitedEmailsT const sendInvitation = () => { Keyboard.dismiss(); // Please see https://github.com/Expensify/App/blob/main/README.md#Security for more details - Policy.addMembersToWorkspace(invitedEmailsToAccountIDsDraft ?? {}, welcomeNote ?? '', route.params.policyID); + Policy.addMembersToWorkspace( + invitedEmailsToAccountIDsDraft ?? {}, + translate('workspace.inviteMessage.welcomeNote', { + workspaceName: policy?.name ?? '', + inviterDisplayName: currentUserPersonalDetails?.displayName ?? '', + inviteMessage: parser.replace(welcomeNote ?? ''), + }), + route.params.policyID, + ); debouncedSaveDraft(null); SearchInputManager.searchInput = ''; // Pop the invite message page before navigating to the members page. @@ -211,15 +226,17 @@ function WorkspaceInviteMessagePage({workspaceInviteMessageDraft, invitedEmailsT WorkspaceInviteMessagePage.displayName = 'WorkspaceInviteMessagePage'; export default withPolicyAndFullscreenLoading( - withOnyx({ - allPersonalDetails: { - key: ONYXKEYS.PERSONAL_DETAILS_LIST, - }, - invitedEmailsToAccountIDsDraft: { - key: ({route}) => `${ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MEMBERS_DRAFT}${route.params.policyID.toString()}`, - }, - workspaceInviteMessageDraft: { - key: ({route}) => `${ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MESSAGE_DRAFT}${route.params.policyID.toString()}`, - }, - })(WorkspaceInviteMessagePage), + withCurrentUserPersonalDetails( + withOnyx({ + allPersonalDetails: { + key: ONYXKEYS.PERSONAL_DETAILS_LIST, + }, + invitedEmailsToAccountIDsDraft: { + key: ({route}) => `${ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MEMBERS_DRAFT}${route.params.policyID.toString()}`, + }, + workspaceInviteMessageDraft: { + key: ({route}) => `${ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MESSAGE_DRAFT}${route.params.policyID.toString()}`, + }, + })(WorkspaceInviteMessagePage), + ), ); From d912e12443718d239df4ac0c2357762c4b0e2592 Mon Sep 17 00:00:00 2001 From: Shahe Shahinyan Date: Wed, 3 Apr 2024 11:58:35 +0400 Subject: [PATCH 011/106] Fix an inconsistency between the mute status icon in full screen mode and mini player mode --- src/components/VideoPlayer/BaseVideoPlayer.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/components/VideoPlayer/BaseVideoPlayer.js b/src/components/VideoPlayer/BaseVideoPlayer.js index 7016de3fa86c..dcda27a3a8f6 100644 --- a/src/components/VideoPlayer/BaseVideoPlayer.js +++ b/src/components/VideoPlayer/BaseVideoPlayer.js @@ -8,6 +8,7 @@ import Hoverable from '@components/Hoverable'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; import {useFullScreenContext} from '@components/VideoPlayerContexts/FullScreenContext'; import {usePlaybackContext} from '@components/VideoPlayerContexts/PlaybackContext'; +import {useVolumeContext} from '@components/VideoPlayerContexts/VolumeContext'; import VideoPopoverMenu from '@components/VideoPopoverMenu'; import useNetwork from '@hooks/useNetwork'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -73,6 +74,7 @@ function BaseVideoPlayer({ const isCurrentlyURLSet = currentlyPlayingURL === url; const isUploading = _.some(CONST.ATTACHMENT_LOCAL_URL_PREFIX, (prefix) => url.startsWith(prefix)); const videoStateRef = useRef(null); + const {updateVolume} = useVolumeContext(); const togglePlayCurrentVideo = useCallback(() => { videoResumeTryNumber.current = 0; @@ -134,6 +136,15 @@ function BaseVideoPlayer({ const handleFullscreenUpdate = useCallback( (e) => { onFullscreenUpdate(e); + + if (e.fullscreenUpdate === VideoFullscreenUpdate.PLAYER_DID_DISMISS) { + currentVideoPlayerRef.current.getStatusAsync().then((status) => { + if (status.volume === 0 || status.isMuted === true) { + updateVolume(0); + } + }); + } + // fix for iOS native and mWeb: when switching to fullscreen and then exiting // the fullscreen mode while playing, the video pauses if (e.fullscreenUpdate === VideoFullscreenUpdate.PLAYER_DID_DISMISS) { From 82039cc1c5a483a56ca0efe185fe1ba2c3328512 Mon Sep 17 00:00:00 2001 From: Shahe Shahinyan Date: Wed, 3 Apr 2024 18:30:43 +0400 Subject: [PATCH 012/106] fix eslint error --- src/components/VideoPlayer/BaseVideoPlayer.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/VideoPlayer/BaseVideoPlayer.js b/src/components/VideoPlayer/BaseVideoPlayer.js index dcda27a3a8f6..d04db56795f9 100644 --- a/src/components/VideoPlayer/BaseVideoPlayer.js +++ b/src/components/VideoPlayer/BaseVideoPlayer.js @@ -139,9 +139,11 @@ function BaseVideoPlayer({ if (e.fullscreenUpdate === VideoFullscreenUpdate.PLAYER_DID_DISMISS) { currentVideoPlayerRef.current.getStatusAsync().then((status) => { - if (status.volume === 0 || status.isMuted === true) { - updateVolume(0); + if (status.volume > 0 && !status.isMuted) { + return } + + updateVolume(0); }); } @@ -158,7 +160,7 @@ function BaseVideoPlayer({ } } }, - [isFullScreenRef, onFullscreenUpdate, pauseVideo, playVideo, videoResumeTryNumber], + [isFullScreenRef, onFullscreenUpdate, pauseVideo, playVideo, videoResumeTryNumber, updateVolume, currentVideoPlayerRef], ); const bindFunctions = useCallback(() => { From 71d5de8cf33e7de1abce8acf7d311526bd19da52 Mon Sep 17 00:00:00 2001 From: Shahe Shahinyan Date: Wed, 3 Apr 2024 18:41:21 +0400 Subject: [PATCH 013/106] run prettier --- src/components/VideoPlayer/BaseVideoPlayer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/VideoPlayer/BaseVideoPlayer.js b/src/components/VideoPlayer/BaseVideoPlayer.js index d04db56795f9..5972989dc2b3 100644 --- a/src/components/VideoPlayer/BaseVideoPlayer.js +++ b/src/components/VideoPlayer/BaseVideoPlayer.js @@ -140,7 +140,7 @@ function BaseVideoPlayer({ if (e.fullscreenUpdate === VideoFullscreenUpdate.PLAYER_DID_DISMISS) { currentVideoPlayerRef.current.getStatusAsync().then((status) => { if (status.volume > 0 && !status.isMuted) { - return + return; } updateVolume(0); From 222486b60c5bb02b651ddd17fa0cd7e423fdc090 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Thu, 4 Apr 2024 18:21:33 +0700 Subject: [PATCH 014/106] fix lint --- src/languages/en.ts | 4 +--- src/languages/es.ts | 4 +--- src/libs/actions/Policy.ts | 3 ++- src/pages/workspace/WorkspaceInviteMessagePage.tsx | 2 +- 4 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index b43656daaa7a..cd9a0c866f1b 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2061,9 +2061,7 @@ export default { genericFailureMessage: 'An error occurred inviting the user to the workspace, please try again.', inviteNoMembersError: 'Please select at least one member to invite', welcomeNote: ({inviterDisplayName, workspaceName, inviteMessage}: WelcomeNoteParams) => - `# ${inviterDisplayName} invited you to ${workspaceName || 'a workspace'} - -${inviteMessage}`, + `# ${inviterDisplayName} invited you to ${workspaceName || 'a workspace'}\n\n${inviteMessage}`, }, distanceRates: { oopsNotSoFast: 'Oops! Not so fast...', diff --git a/src/languages/es.ts b/src/languages/es.ts index 18b7f15d70cf..35141ba5592c 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2089,9 +2089,7 @@ export default { inviteNoMembersError: 'Por favor, selecciona al menos un miembro a invitar', genericFailureMessage: 'Se produjo un error al invitar al usuario al espacio de trabajo. Vuelva a intentarlo..', welcomeNote: ({inviterDisplayName, workspaceName, inviteMessage}: WelcomeNoteParams) => - `# ${inviterDisplayName} invited you to ${workspaceName || 'a workspace'} - -${inviteMessage}`, + `# ${inviterDisplayName} invited you to ${workspaceName || 'a workspace'}\n\n${inviteMessage}`, }, distanceRates: { oopsNotSoFast: 'Ups! No tan rápido...', diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index dce82727a1cc..08c493e0c0aa 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -1,4 +1,5 @@ import {PUBLIC_DOMAINS} from 'expensify-common/lib/CONST'; +import ExpensiMark from 'expensify-common/lib/ExpensiMark'; import Str from 'expensify-common/lib/str'; import {escapeRegExp} from 'lodash'; import lodashClone from 'lodash/clone'; @@ -1355,7 +1356,7 @@ function addMembersToWorkspace(invitedEmailsToAccountIDs: InvitedEmailsToAccount const params: AddMembersToWorkspaceParams = { employees: JSON.stringify(logins.map((login) => ({email: login}))), - welcomeNote, + welcomeNote: new ExpensiMark().replace(welcomeNote), policyID, }; if (!isEmptyObject(membersChats.reportCreationData)) { diff --git a/src/pages/workspace/WorkspaceInviteMessagePage.tsx b/src/pages/workspace/WorkspaceInviteMessagePage.tsx index be9859a55899..8655c67254c5 100644 --- a/src/pages/workspace/WorkspaceInviteMessagePage.tsx +++ b/src/pages/workspace/WorkspaceInviteMessagePage.tsx @@ -107,7 +107,7 @@ function WorkspaceInviteMessagePage({ translate('workspace.inviteMessage.welcomeNote', { workspaceName: policy?.name ?? '', inviterDisplayName: currentUserPersonalDetails?.displayName ?? '', - inviteMessage: parser.replace(welcomeNote ?? ''), + inviteMessage: welcomeNote ?? '', }), route.params.policyID, ); From 0f6f36fc7ac4d649df47bef895ccc90233facb36 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Fri, 5 Apr 2024 13:14:56 +0800 Subject: [PATCH 015/106] add back code that still being used --- src/pages/iou/request/step/IOURequestStepAmount.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pages/iou/request/step/IOURequestStepAmount.js b/src/pages/iou/request/step/IOURequestStepAmount.js index e2784175ad5c..2f1d4039066c 100644 --- a/src/pages/iou/request/step/IOURequestStepAmount.js +++ b/src/pages/iou/request/step/IOURequestStepAmount.js @@ -60,6 +60,7 @@ function IOURequestStepAmount({ const {translate} = useLocalize(); const textInput = useRef(null); const focusTimeoutRef = useRef(null); + const isSaveButtonPressed = useRef(false); const iouRequestType = getRequestType(transaction); const isEditing = action === CONST.IOU.ACTION.EDIT; const isSplitBill = iouType === CONST.IOU.TYPE.SPLIT; @@ -109,6 +110,7 @@ function IOURequestStepAmount({ * @param {Number} amount */ const navigateToNextPage = ({amount}) => { + isSaveButtonPressed.current = true; const amountInSmallestCurrencyUnits = CurrencyUtils.convertToBackendAmount(Number.parseFloat(amount)); IOU.setMoneyRequestAmount_temporaryForRefactor(transactionID, amountInSmallestCurrencyUnits, currency || CONST.CURRENCY.USD); From 9636b97487b485f1ca69385934a523d0a7d0afee Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Fri, 5 Apr 2024 13:15:18 +0800 Subject: [PATCH 016/106] remove conflict --- src/pages/iou/request/step/IOURequestStepTaxAmountPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/iou/request/step/IOURequestStepTaxAmountPage.tsx b/src/pages/iou/request/step/IOURequestStepTaxAmountPage.tsx index 6375587c71d8..6387f9aee0d7 100644 --- a/src/pages/iou/request/step/IOURequestStepTaxAmountPage.tsx +++ b/src/pages/iou/request/step/IOURequestStepTaxAmountPage.tsx @@ -83,7 +83,7 @@ function IOURequestStepTaxAmountPage({ IOU.setMoneyRequestTaxAmount(transactionID, amountInSmallestCurrencyUnits, true); // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - IOU.setMoneyRequestCurrency_temporaryForRefactor(transactionID, currency || CONST.CURRENCY.USD, true); + IOU.setMoneyRequestCurrency_temporaryForRefactor(transactionID, currency || CONST.CURRENCY.USD); if (backTo) { Navigation.goBack(backTo); From 422c2074b8b7a35ca504174e54f45936a4aa4268 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Fri, 5 Apr 2024 13:23:18 +0800 Subject: [PATCH 017/106] pass currency to currency page --- src/pages/iou/request/step/IOURequestStepTaxAmountPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/iou/request/step/IOURequestStepTaxAmountPage.tsx b/src/pages/iou/request/step/IOURequestStepTaxAmountPage.tsx index 6387f9aee0d7..879d0c7eaaf9 100644 --- a/src/pages/iou/request/step/IOURequestStepTaxAmountPage.tsx +++ b/src/pages/iou/request/step/IOURequestStepTaxAmountPage.tsx @@ -74,7 +74,7 @@ function IOURequestStepTaxAmountPage({ // Only USD is allowed for distance requests. // Remove query from the route and encode it. Navigation.navigate( - ROUTES.MONEY_REQUEST_STEP_CURRENCY.getRoute(CONST.IOU.ACTION.CREATE, iouType, transactionID, reportID, backTo ? 'confirm' : '', Navigation.getActiveRouteWithoutParams()), + ROUTES.MONEY_REQUEST_STEP_CURRENCY.getRoute(CONST.IOU.ACTION.CREATE, iouType, transactionID, reportID, backTo ? 'confirm' : '', currency, Navigation.getActiveRouteWithoutParams()), ); }; From 967f5a489db9c7b1e146f6e1c66132f4694222ee Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Fri, 5 Apr 2024 13:25:20 +0800 Subject: [PATCH 018/106] lint --- src/pages/iou/request/step/IOURequestStepAmount.js | 4 +++- .../iou/request/step/IOURequestStepTaxAmountPage.tsx | 12 ++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepAmount.js b/src/pages/iou/request/step/IOURequestStepAmount.js index 2f1d4039066c..316d2fce5b13 100644 --- a/src/pages/iou/request/step/IOURequestStepAmount.js +++ b/src/pages/iou/request/step/IOURequestStepAmount.js @@ -103,7 +103,9 @@ function IOURequestStepAmount({ }; const navigateToCurrencySelectionPage = () => { - Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CURRENCY.getRoute(action, iouType, transactionID, reportID, backTo ? 'confirm' : '', currency, Navigation.getActiveRouteWithoutParams())); + Navigation.navigate( + ROUTES.MONEY_REQUEST_STEP_CURRENCY.getRoute(action, iouType, transactionID, reportID, backTo ? 'confirm' : '', currency, Navigation.getActiveRouteWithoutParams()), + ); }; /** diff --git a/src/pages/iou/request/step/IOURequestStepTaxAmountPage.tsx b/src/pages/iou/request/step/IOURequestStepTaxAmountPage.tsx index 879d0c7eaaf9..2ed58b277a78 100644 --- a/src/pages/iou/request/step/IOURequestStepTaxAmountPage.tsx +++ b/src/pages/iou/request/step/IOURequestStepTaxAmountPage.tsx @@ -1,5 +1,5 @@ import {useFocusEffect} from '@react-navigation/native'; -import React, {useCallback, useEffect, useRef} from 'react'; +import React, {useCallback, useRef} from 'react'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import type {BaseTextInputRef} from '@components/TextInput/BaseTextInput/types'; @@ -74,7 +74,15 @@ function IOURequestStepTaxAmountPage({ // Only USD is allowed for distance requests. // Remove query from the route and encode it. Navigation.navigate( - ROUTES.MONEY_REQUEST_STEP_CURRENCY.getRoute(CONST.IOU.ACTION.CREATE, iouType, transactionID, reportID, backTo ? 'confirm' : '', currency, Navigation.getActiveRouteWithoutParams()), + ROUTES.MONEY_REQUEST_STEP_CURRENCY.getRoute( + CONST.IOU.ACTION.CREATE, + iouType, + transactionID, + reportID, + backTo ? 'confirm' : '', + currency, + Navigation.getActiveRouteWithoutParams(), + ), ); }; From 8aac71c4b9cb2440d36b29efc6fa5c21ddf4370a Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Fri, 5 Apr 2024 13:31:49 +0800 Subject: [PATCH 019/106] another lint --- .../iou/request/step/IOURequestStepAmount.js | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepAmount.js b/src/pages/iou/request/step/IOURequestStepAmount.js index 316d2fce5b13..73dcc9fad5a5 100644 --- a/src/pages/iou/request/step/IOURequestStepAmount.js +++ b/src/pages/iou/request/step/IOURequestStepAmount.js @@ -82,19 +82,20 @@ function IOURequestStepAmount({ ); useEffect(() => { - if (isEditing) { - // A temporary solution to not prevent users from editing the currency - // We create a backup transaction and use it to save the currency and remove this transaction backup if we don't save the amount - // It should be removed after this issue https://github.com/Expensify/App/issues/34607 is fixed - TransactionEdit.createBackupTransaction(isEditingSplitBill && !lodashIsEmpty(splitDraftTransaction) ? splitDraftTransaction : transaction); - - return () => { - if (isSaveButtonPressed.current) { - return; - } - TransactionEdit.removeBackupTransaction(transaction.transactionID || ''); - }; + if (!isEditing) { + return; } + // A temporary solution to not prevent users from editing the currency + // We create a backup transaction and use it to save the currency and remove this transaction backup if we don't save the amount + // It should be removed after this issue https://github.com/Expensify/App/issues/34607 is fixed + TransactionEdit.createBackupTransaction(isEditingSplitBill && !lodashIsEmpty(splitDraftTransaction) ? splitDraftTransaction : transaction); + + return () => { + if (isSaveButtonPressed.current) { + return; + } + TransactionEdit.removeBackupTransaction(transaction.transactionID || ''); + }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); From dd6e0b307e0f2ff65581809151a0b6c1912621d6 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Fri, 5 Apr 2024 15:33:11 +0700 Subject: [PATCH 020/106] Add markdown for workspace description --- src/components/TextInput/BaseTextInput/index.tsx | 12 ++++++++++-- src/components/TextInput/BaseTextInput/types.ts | 6 +++++- .../workspace/WorkspaceProfileDescriptionPage.tsx | 1 + 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/components/TextInput/BaseTextInput/index.tsx b/src/components/TextInput/BaseTextInput/index.tsx index 9681f7e7fde5..5f80ef0a2260 100644 --- a/src/components/TextInput/BaseTextInput/index.tsx +++ b/src/components/TextInput/BaseTextInput/index.tsx @@ -8,12 +8,14 @@ import FormHelpMessage from '@components/FormHelpMessage'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; +import RNMarkdownTextInput from '@components/RNMarkdownTextInput'; import RNTextInput from '@components/RNTextInput'; import SwipeInterceptPanResponder from '@components/SwipeInterceptPanResponder'; import Text from '@components/Text'; import * as styleConst from '@components/TextInput/styleConst'; import TextInputLabel from '@components/TextInput/TextInputLabel'; import useLocalize from '@hooks/useLocalize'; +import useMarkdownStyle from '@hooks/useMarkdownStyle'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -56,10 +58,13 @@ function BaseTextInput( autoCorrect = true, prefixCharacter = '', inputID, + shouldEnableMarkdown = false, ...inputProps }: BaseTextInputProps, ref: ForwardedRef, ) { + const InputComponent = shouldEnableMarkdown ? RNMarkdownTextInput : RNTextInput; + const theme = useTheme(); const styles = useThemeStyles(); const {hasError = false} = inputProps; @@ -82,6 +87,8 @@ function BaseTextInput( const input = useRef(null); const isLabelActive = useRef(initialActiveLabel); + const markdownStyle = useMarkdownStyle(); + // AutoFocus which only works on mount: useEffect(() => { // We are manually managing focus to prevent this issue: https://github.com/Expensify/App/issues/4514 @@ -341,8 +348,8 @@ function BaseTextInput( )} - { + { if (typeof ref === 'function') { ref(element); } else if (ref && 'current' in ref) { @@ -396,6 +403,7 @@ function BaseTextInput( selection={inputProps.selection} readOnly={isReadOnly} defaultValue={defaultValue} + markdownStyle={markdownStyle} /> {inputProps.isLoading && ( { updateMultilineInputRange(el); }} From faeba252b9be8cec5ec6e28f5f85f04f90b5308c Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Fri, 5 Apr 2024 15:46:48 +0700 Subject: [PATCH 021/106] Add native markdown textinput --- .../TextInput/BaseTextInput/index.native.tsx | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/components/TextInput/BaseTextInput/index.native.tsx b/src/components/TextInput/BaseTextInput/index.native.tsx index e7f4507df7c6..5e539aba9d7e 100644 --- a/src/components/TextInput/BaseTextInput/index.native.tsx +++ b/src/components/TextInput/BaseTextInput/index.native.tsx @@ -21,6 +21,8 @@ import isInputAutoFilled from '@libs/isInputAutoFilled'; import useNativeDriver from '@libs/useNativeDriver'; import variables from '@styles/variables'; import CONST from '@src/CONST'; +import RNMarkdownTextInput from '@components/RNMarkdownTextInput'; +import useMarkdownStyle from '@hooks/useMarkdownStyle'; import type {BaseTextInputProps, BaseTextInputRef} from './types'; function BaseTextInput( @@ -54,6 +56,7 @@ function BaseTextInput( autoCorrect = true, prefixCharacter = '', inputID, + shouldEnableMarkdown = false, ...props }: BaseTextInputProps, ref: ForwardedRef, @@ -63,6 +66,7 @@ function BaseTextInput( const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const {translate} = useLocalize(); + const InputComponent = shouldEnableMarkdown ? RNMarkdownTextInput : RNTextInput; const {hasError = false} = inputProps; // Disabling this line for safeness as nullish coalescing works only if the value is undefined or null @@ -82,6 +86,8 @@ function BaseTextInput( const input = useRef(null); const isLabelActive = useRef(initialActiveLabel); + const markdownStyle = useMarkdownStyle(); + // AutoFocus which only works on mount: useEffect(() => { // We are manually managing focus to prevent this issue: https://github.com/Expensify/App/issues/4514 @@ -321,8 +327,8 @@ function BaseTextInput( )} - { + { if (typeof ref === 'function') { ref(element); } else if (ref && 'current' in ref) { @@ -330,7 +336,7 @@ function BaseTextInput( ref.current = element; } - input.current = element; + input.current = element as TextInput | null; }} // eslint-disable-next-line {...inputProps} @@ -370,6 +376,7 @@ function BaseTextInput( selection={inputProps.selection} readOnly={isReadOnly} defaultValue={defaultValue} + markdownStyle={markdownStyle} /> {inputProps.isLoading && ( Date: Fri, 5 Apr 2024 19:01:02 +0200 Subject: [PATCH 022/106] refactorings --- src/components/ReferralProgramCTA.tsx | 6 +- src/components/ScreenWrapper.tsx | 29 +- src/hooks/useScreenWrapperTransitionStatus.ts | 17 + src/hooks/useStyledSafeAreaInsets.ts | 37 ++ src/libs/OptionsListUtils.ts | 16 +- src/pages/NewChatPage.tsx | 459 +++++++++--------- src/pages/SearchPage/SearchPageFooter.tsx | 10 +- src/pages/SearchPage/index.tsx | 47 +- ...yForRefactorRequestParticipantsSelector.js | 37 +- .../MoneyRequestParticipantsSelector.js | 60 +-- 10 files changed, 384 insertions(+), 334 deletions(-) create mode 100644 src/hooks/useScreenWrapperTransitionStatus.ts create mode 100644 src/hooks/useStyledSafeAreaInsets.ts diff --git a/src/components/ReferralProgramCTA.tsx b/src/components/ReferralProgramCTA.tsx index c93b75bf11ad..ff5e8bc543e5 100644 --- a/src/components/ReferralProgramCTA.tsx +++ b/src/components/ReferralProgramCTA.tsx @@ -1,5 +1,6 @@ import React from 'react'; import type {OnyxEntry} from 'react-native-onyx'; +import type {ViewStyle} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import useLocalize from '@hooks/useLocalize'; import useTheme from '@hooks/useTheme'; @@ -26,9 +27,10 @@ type ReferralProgramCTAProps = ReferralProgramCTAOnyxProps & { | typeof CONST.REFERRAL_PROGRAM.CONTENT_TYPES.START_CHAT | typeof CONST.REFERRAL_PROGRAM.CONTENT_TYPES.SEND_MONEY | typeof CONST.REFERRAL_PROGRAM.CONTENT_TYPES.REFER_FRIEND; + style?: ViewStyle; }; -function ReferralProgramCTA({referralContentType, dismissedReferralBanners}: ReferralProgramCTAProps) { +function ReferralProgramCTA({referralContentType, dismissedReferralBanners, style}: ReferralProgramCTAProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); const theme = useTheme(); @@ -46,7 +48,7 @@ function ReferralProgramCTA({referralContentType, dismissedReferralBanners}: Ref onPress={() => { Navigation.navigate(ROUTES.REFERRAL_DETAILS_MODAL.getRoute(referralContentType, Navigation.getActiveRouteWithoutParams())); }} - style={[styles.w100, styles.br2, styles.highlightBG, styles.flexRow, styles.justifyContentBetween, styles.alignItemsCenter, {gap: 10, padding: 10}, styles.pl5]} + style={[styles.br2, styles.highlightBG, styles.flexRow, styles.justifyContentBetween, styles.alignItemsCenter, {gap: 10, padding: 10}, styles.pl5, style]} accessibilityLabel="referral" role={CONST.ACCESSIBILITY_ROLE.BUTTON} > diff --git a/src/components/ScreenWrapper.tsx b/src/components/ScreenWrapper.tsx index b78e274371ca..9dbb1a3c60fe 100644 --- a/src/components/ScreenWrapper.tsx +++ b/src/components/ScreenWrapper.tsx @@ -1,7 +1,7 @@ import {useNavigation} from '@react-navigation/native'; import type {StackNavigationProp} from '@react-navigation/stack'; import type {ForwardedRef, ReactNode} from 'react'; -import React, {forwardRef, useEffect, useRef, useState} from 'react'; +import React, {createContext, forwardRef, useEffect, useMemo, useRef, useState} from 'react'; import type {DimensionValue, StyleProp, ViewStyle} from 'react-native'; import {Keyboard, PanResponder, View} from 'react-native'; import {PickerAvoidingView} from 'react-native-picker-select'; @@ -99,6 +99,8 @@ type ScreenWrapperProps = { shouldShowOfflineIndicatorInWideScreen?: boolean; }; +const ScreenWrapperStatusContext = createContext({didScreenTransitionEnd: false}); + function ScreenWrapper( { shouldEnableMaxHeight = false, @@ -201,6 +203,7 @@ function ScreenWrapper( }, []); const isAvoidingViewportScroll = useTackInputFocus(shouldEnableMaxHeight && shouldAvoidScrollOnVirtualViewport && Browser.isMobileSafari()); + const contextValue = useMemo(() => ({didScreenTransitionEnd}), [didScreenTransitionEnd]); return ( @@ -251,16 +254,18 @@ function ScreenWrapper( {isDevelopment && } - { - // If props.children is a function, call it to provide the insets to the children. - typeof children === 'function' - ? children({ - insets, - safeAreaPaddingBottomStyle, - didScreenTransitionEnd, - }) - : children - } + + { + // If props.children is a function, call it to provide the insets to the children. + typeof children === 'function' + ? children({ + insets, + safeAreaPaddingBottomStyle, + didScreenTransitionEnd, + }) + : children + } + {isSmallScreenWidth && shouldShowOfflineIndicator && } {!isSmallScreenWidth && shouldShowOfflineIndicatorInWideScreen && ( ; - /** All of the personal details for everyone */ - personalDetails: OnyxEntry; - - betas: OnyxEntry; - - /** An object that holds data about which referral banners have been dismissed */ - dismissedReferralBanners: OnyxEntry; - /** Whether we are searching for reports in the server */ isSearchingForReports: OnyxEntry; }; @@ -48,282 +49,292 @@ type NewChatPageProps = NewChatPageWithOnyxProps & { isGroupChat?: boolean; }; +const EMPTY_ARRAY: Array>> = []; + const excludedGroupEmails = CONST.EXPENSIFY_EMAILS.filter((value) => value !== CONST.EMAIL.CONCIERGE); -function NewChatPage({betas, isGroupChat, personalDetails, isSearchingForReports, dismissedReferralBanners, newGroupDraft}: NewChatPageProps) { - const {translate} = useLocalize(); +function useOptions({isGroupChat, newGroupDraft}: Omit) { + const [searchTerm, debouncedSearchTerm, setSearchTerm] = useDebouncedState(''); + const [selectedOptions, setSelectedOptions] = useState>([]); + const betas = useBetas(); + const personalData = useCurrentUserPersonalDetails(); + const personalDetails = usePersonalDetails(); + const {didScreenTransitionEnd} = useScreenWrapperTranstionStatus(); + const {options: listOptions, areOptionsInitialized} = useOptionsList({ + shouldInitialize: didScreenTransitionEnd, + }); + console.log('newchatpage', didScreenTransitionEnd, areOptionsInitialized); - const styles = useThemeStyles(); + const options = useMemo(() => { + const filteredOptions = OptionsListUtils.getFilteredOptions( + listOptions.reports ?? [], + listOptions.personalDetails ?? [], + betas ?? [], + debouncedSearchTerm, + selectedOptions, + isGroupChat ? excludedGroupEmails : [], + false, + true, + false, + {}, + [], + false, + {}, + [], + true, + ); + const maxParticipantsReached = selectedOptions.length === CONST.REPORT.MAXIMUM_PARTICIPANTS; + + const headerMessage = OptionsListUtils.getHeaderMessage( + filteredOptions.personalDetails.length + filteredOptions.recentReports.length !== 0, + Boolean(filteredOptions.userToInvite), + debouncedSearchTerm.trim(), + maxParticipantsReached, + selectedOptions.some((participant) => participant?.searchText?.toLowerCase?.().includes(debouncedSearchTerm.trim().toLowerCase())), + ); + return {...filteredOptions, headerMessage, maxParticipantsReached}; + }, [betas, debouncedSearchTerm, isGroupChat, listOptions.personalDetails, listOptions.reports, selectedOptions]); - const personalData = useCurrentUserPersonalDetails(); + useEffect(() => { + if (!debouncedSearchTerm.length || options.maxParticipantsReached) { + return; + } + + Report.searchInServer(debouncedSearchTerm); + }, [debouncedSearchTerm, options.maxParticipantsReached]); - const getGroupParticipants = () => { + useEffect(() => { if (!newGroupDraft?.participants) { - return []; + return; } const selectedParticipants = newGroupDraft.participants.filter((participant) => participant.accountID !== personalData.accountID); const newSelectedOptions = selectedParticipants.map((participant): OptionData => { const baseOption = OptionsListUtils.getParticipantsOption({accountID: participant.accountID, login: participant.login, reportID: ''}, personalDetails); - return {...baseOption, reportID: baseOption.reportID ?? ''}; + return {...baseOption, reportID: baseOption.reportID ?? '', isSelected: true}; }); - return newSelectedOptions; - }; - - const [searchTerm, setSearchTerm] = useState(''); - const [filteredRecentReports, setFilteredRecentReports] = useState([]); - const [filteredPersonalDetails, setFilteredPersonalDetails] = useState([]); - const [filteredUserToInvite, setFilteredUserToInvite] = useState(); - const [selectedOptions, setSelectedOptions] = useState(getGroupParticipants); + setSelectedOptions(newSelectedOptions); + }, [newGroupDraft, personalData, personalDetails]); + + return {...options, searchTerm, debouncedSearchTerm, setSearchTerm, areOptionsInitialized: areOptionsInitialized && didScreenTransitionEnd, selectedOptions, setSelectedOptions}; +} + +function NewChatPage({isGroupChat, isSearchingForReports, newGroupDraft}: NewChatPageProps) { + const {translate} = useLocalize(); const {isOffline} = useNetwork(); const {isSmallScreenWidth} = useWindowDimensions(); - const [didScreenTransitionEnd, setDidScreenTransitionEnd] = useState(false); - const {options, areOptionsInitialized} = useOptionsList({ - shouldInitialize: didScreenTransitionEnd, - }); - - const maxParticipantsReached = selectedOptions.length === CONST.REPORT.MAXIMUM_PARTICIPANTS; - const setSearchTermAndSearchInServer = useSearchTermAndSearch(setSearchTerm, maxParticipantsReached); + const styles = useThemeStyles(); + const personalData = useCurrentUserPersonalDetails(); + const {insets} = useStyledSafeAreaInsets(); - const headerMessage = OptionsListUtils.getHeaderMessage( - filteredPersonalDetails.length + filteredRecentReports.length !== 0, - Boolean(filteredUserToInvite), - searchTerm.trim(), + const { + headerMessage, maxParticipantsReached, - selectedOptions.some((participant) => participant?.searchText?.toLowerCase().includes(searchTerm.trim().toLowerCase())), - ); + searchTerm, + debouncedSearchTerm, + setSearchTerm, + selectedOptions, + setSelectedOptions, + recentReports, + personalDetails, + userToInvite, + areOptionsInitialized, + } = useOptions({ + isGroupChat, + newGroupDraft, + }); - const sections = useMemo((): OptionsListUtils.CategorySection[] => { + const [sections, firstKeyForList] = useMemo((): [OptionsListUtils.CategorySection[], string] => { const sectionsList: OptionsListUtils.CategorySection[] = []; + let firstKey = ''; - const formatResults = OptionsListUtils.formatSectionsFromSearchTerm(searchTerm, selectedOptions, filteredRecentReports, filteredPersonalDetails, maxParticipantsReached); + const formatResults = OptionsListUtils.formatSectionsFromSearchTerm(debouncedSearchTerm, selectedOptions, recentReports, personalDetails, maxParticipantsReached); sectionsList.push(formatResults.section); + if (!firstKey) { + firstKey = OptionsListUtils.getFirstKeyForList(formatResults.section.data); + } + if (maxParticipantsReached) { - return sectionsList; + return [sectionsList, firstKey]; } sectionsList.push({ title: translate('common.recents'), - data: filteredRecentReports, - shouldShow: filteredRecentReports.length > 0, + data: recentReports, + shouldShow: !isEmpty(recentReports), }); + if (!firstKey) { + firstKey = OptionsListUtils.getFirstKeyForList(recentReports); + } sectionsList.push({ title: translate('common.contacts'), - data: filteredPersonalDetails, - shouldShow: filteredPersonalDetails.length > 0, + data: personalDetails, + shouldShow: !isEmpty(personalDetails), }); + if (!firstKey) { + firstKey = OptionsListUtils.getFirstKeyForList(personalDetails); + } - if (filteredUserToInvite) { + if (userToInvite) { sectionsList.push({ title: undefined, - data: [filteredUserToInvite], + data: [userToInvite], shouldShow: true, }); + if (!firstKey) { + firstKey = OptionsListUtils.getFirstKeyForList([userToInvite]); + } } - return sectionsList; - }, [translate, filteredPersonalDetails, filteredRecentReports, filteredUserToInvite, maxParticipantsReached, selectedOptions, searchTerm]); - - /** - * Removes a selected option from list if already selected. If not already selected add this option to the list. - */ - const toggleOption = (option: OptionData) => { - const isOptionInList = selectedOptions.some((selectedOption) => selectedOption.login === option.login); - - let newSelectedOptions; - - if (isOptionInList) { - newSelectedOptions = selectedOptions.filter((selectedOption) => selectedOption.login !== option.login); - } else { - newSelectedOptions = [...selectedOptions, option]; - } - - const { - recentReports, - personalDetails: newChatPersonalDetails, - userToInvite, - } = OptionsListUtils.getFilteredOptions( - options.reports ?? [], - options.personalDetails ?? [], - betas ?? [], - searchTerm, - newSelectedOptions, - isGroupChat ? excludedGroupEmails : [], - false, - true, - false, - {}, - [], - false, - {}, - [], - true, - ); - setSelectedOptions(newSelectedOptions); - setFilteredRecentReports(recentReports); - setFilteredPersonalDetails(newChatPersonalDetails); - setFilteredUserToInvite(userToInvite); - }; + return [sectionsList, firstKey]; + }, [debouncedSearchTerm, selectedOptions, recentReports, personalDetails, maxParticipantsReached, translate, userToInvite]); /** * Creates a new 1:1 chat with the option and the current user, * or navigates to the existing chat if one with those participants already exists. */ - const createChat = (option: OptionData) => { - let login = ''; + const createChat = useCallback( + (option?: OptionsListUtils.Option) => { + let login = ''; + + if (option?.login) { + login = option.login; + } else if (selectedOptions.length === 1) { + login = selectedOptions[0].login ?? ''; + } + if (!login) { + Log.warn('Tried to create chat with empty login'); + return; + } + Report.navigateToAndOpenReport([login]); + }, + [selectedOptions], + ); - if (option.login) { - login = option.login; - } else if (selectedOptions.length === 1) { - login = selectedOptions[0].login ?? ''; - } + const itemRightSideComponent = useCallback( + (item: OptionsListUtils.Option) => { + /** + * Removes a selected option from list if already selected. If not already selected add this option to the list. + * @param option + */ + function toggleOption(option: OptionsListUtils.Option) { + const isOptionInList = !!option.isSelected; - if (!login) { - Log.warn('Tried to create chat with empty login'); - return; - } + let newSelectedOptions; - Report.navigateToAndOpenReport([login]); - }; - /** - * Navigates to create group confirm page - */ - const navigateToConfirmPage = () => { - if (!personalData || !personalData.login || !personalData.accountID) { - return; - } - const selectedParticipants: SelectedParticipant[] = selectedOptions.map((option: OptionData) => ({login: option.login ?? '', accountID: option.accountID ?? -1})); - const logins = [...selectedParticipants, {login: personalData.login, accountID: personalData.accountID}]; - Report.setGroupDraft(logins); - Navigation.navigate(ROUTES.NEW_CHAT_CONFIRM); - }; - - const updateOptions = useCallback(() => { - const { - recentReports, - personalDetails: newChatPersonalDetails, - userToInvite, - } = OptionsListUtils.getFilteredOptions( - options.reports ?? [], - options.personalDetails ?? [], - betas ?? [], - searchTerm, - selectedOptions, - isGroupChat ? excludedGroupEmails : [], - false, - true, - false, - {}, - [], - false, - {}, - [], - true, - ); + if (isOptionInList) { + newSelectedOptions = reject(selectedOptions, (selectedOption) => selectedOption.login === option.login); + } else { + newSelectedOptions = [...selectedOptions, {...option, isSelected: true, selected: true, reportID: option.reportID ?? ''}]; + } - setFilteredRecentReports(recentReports); - setFilteredPersonalDetails(newChatPersonalDetails); - setFilteredUserToInvite(userToInvite); - // props.betas is not added as dependency since it doesn't change during the component lifecycle - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [options, searchTerm]); + setSelectedOptions(newSelectedOptions); + } - useEffect(() => { - const interactionTask = doInteractionTask(() => { - setDidScreenTransitionEnd(true); - }); + if (item.isSelected) { + return ( + toggleOption(item)} + disabled={item.isDisabled} + role={CONST.ACCESSIBILITY_ROLE.CHECKBOX} + accessibilityLabel={CONST.ACCESSIBILITY_ROLE.CHECKBOX} + style={[styles.flexRow, styles.alignItemsCenter, styles.ml3]} + > + + + ); + } - return () => { - if (!interactionTask) { + return ( +