From 086e464eda1d76fe94858aadd75feb6ac4af0b07 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Fri, 19 Jul 2024 17:47:42 +0500 Subject: [PATCH 001/111] feat: log network response and request. Allow filtering option in Debug Console --- src/libs/actions/OnyxUpdates.ts | 3 +- src/pages/settings/AboutPage/ConsolePage.tsx | 48 +++++++++++++++++++- 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/src/libs/actions/OnyxUpdates.ts b/src/libs/actions/OnyxUpdates.ts index 73c8b3d592bf..6769fa7e64a3 100644 --- a/src/libs/actions/OnyxUpdates.ts +++ b/src/libs/actions/OnyxUpdates.ts @@ -35,7 +35,8 @@ function applyHTTPSOnyxUpdates(request: Request, response: Response) { // apply successData or failureData. This ensures that we do not update any pending, loading, or other UI states contained // in successData/failureData until after the component has received and API data. const onyxDataUpdatePromise = response.onyxData ? updateHandler(response.onyxData) : Promise.resolve(); - + Log.info('[OnyxUpdateManager]-[Network]-[Request]', false, request); + Log.info('[OnyxUpdateManager]-[Network]-[Response]', false, response); return onyxDataUpdatePromise .then(() => { // Handle the request's success/failure data (client-side data) diff --git a/src/pages/settings/AboutPage/ConsolePage.tsx b/src/pages/settings/AboutPage/ConsolePage.tsx index aee11c89f22c..6f5942293f61 100644 --- a/src/pages/settings/AboutPage/ConsolePage.tsx +++ b/src/pages/settings/AboutPage/ConsolePage.tsx @@ -11,11 +11,13 @@ import ConfirmModal from '@components/ConfirmModal'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import * as Expensicons from '@components/Icon/Expensicons'; import InvertedFlatList from '@components/InvertedFlatList'; +import type {PopoverMenuItem} from '@components/PopoverMenu'; import ScreenWrapper from '@components/ScreenWrapper'; import Text from '@components/Text'; import TextInput from '@components/TextInput'; import useKeyboardShortcut from '@hooks/useKeyboardShortcut'; import useLocalize from '@hooks/useLocalize'; +import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import {addLog} from '@libs/actions/Console'; import {createLog, parseStringifiedMessages, sanitizeConsoleInput} from '@libs/Console'; @@ -40,16 +42,56 @@ type ConsolePageOnyxProps = { type ConsolePageProps = ConsolePageOnyxProps; +const filterBy = { + all: '', + network: '-[Network]-', +} as const; +type FilterBy = (typeof filterBy)[keyof typeof filterBy]; + function ConsolePage({capturedLogs, shouldStoreLogs}: ConsolePageProps) { const [input, setInput] = useState(''); const [logs, setLogs] = useState(capturedLogs); const [isGeneratingLogsFile, setIsGeneratingLogsFile] = useState(false); const [isLimitModalVisible, setIsLimitModalVisible] = useState(false); + const [activeFilterIndex, setActiveFilterIndex] = useState(filterBy.all); const {translate} = useLocalize(); const styles = useThemeStyles(); + const theme = useTheme(); const route = useRoute>(); + const menuItems: PopoverMenuItem[] = useMemo( + () => [ + { + text: translate('common.filterLogs'), + disabled: true, + }, + { + icon: Expensicons.All, + text: translate('common.all'), + iconFill: activeFilterIndex === filterBy.all ? theme.iconSuccessFill : theme.icon, + iconRight: Expensicons.Checkmark, + shouldShowRightIcon: activeFilterIndex === filterBy.all, + success: activeFilterIndex === filterBy.all, + onSelected: () => { + setActiveFilterIndex(filterBy.all); + }, + }, + { + icon: Expensicons.CardsAndDomains, + text: translate('common.network'), + iconFill: activeFilterIndex === filterBy.network ? theme.iconSuccessFill : theme.icon, + iconRight: Expensicons.CheckCircle, + shouldShowRightIcon: activeFilterIndex === filterBy.network, + success: activeFilterIndex === filterBy.network, + onSelected: () => { + setActiveFilterIndex(filterBy.network); + }, + }, + ], + [activeFilterIndex, theme.icon, theme.iconSuccessFill, translate], + ); + const logsList = useMemo( () => Object.entries(logs ?? {}) @@ -58,6 +100,8 @@ function ConsolePage({capturedLogs, shouldStoreLogs}: ConsolePageProps) { [logs], ); + const filteredLogsList = useMemo(() => logsList.filter((log) => log.message.includes(activeFilterIndex)), [activeFilterIndex, logsList]); + useEffect(() => { if (!shouldStoreLogs) { return; @@ -121,10 +165,12 @@ function ConsolePage({capturedLogs, shouldStoreLogs}: ConsolePageProps) { Navigation.goBack(route.params?.backTo)} + shouldShowThreeDotsButton + threeDotsMenuItems={menuItems} /> {translate('initialSettingsPage.debugConsole.noLogsAvailable')}} From 766f922bc0c50d5dc83dd01908c9232016ae8bcb Mon Sep 17 00:00:00 2001 From: hurali97 Date: Fri, 19 Jul 2024 17:48:06 +0500 Subject: [PATCH 002/111] feat: add locale values for filter options --- src/languages/en.ts | 2 ++ src/languages/es.ts | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/languages/en.ts b/src/languages/en.ts index a8281e609305..5e2654ebc847 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -368,6 +368,8 @@ export default { value: 'Value', downloadFailedTitle: 'Download failed', downloadFailedDescription: "Your download couldn't be completed. Please try again later.", + filterLogs: 'Filter Logs', + network: 'Network', }, location: { useCurrent: 'Use current location', diff --git a/src/languages/es.ts b/src/languages/es.ts index bbbbbfc79bac..2e383d04e451 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -358,6 +358,8 @@ export default { value: 'Valor', downloadFailedTitle: 'Error en la descarga', downloadFailedDescription: 'No se pudo completar la descarga. Por favor, inténtalo más tarde.', + filterLogs: 'Registros de filtrado', + network: 'La red', }, connectionComplete: { title: 'Conexión completa', From 347f9b26fc2f18ae3bd15f730299a7b04c175e71 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Mon, 22 Jul 2024 13:18:15 +0500 Subject: [PATCH 003/111] feat: use correct icons --- src/pages/settings/AboutPage/ConsolePage.tsx | 2 +- src/pages/settings/Troubleshoot/TroubleshootPage.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/settings/AboutPage/ConsolePage.tsx b/src/pages/settings/AboutPage/ConsolePage.tsx index 6f5942293f61..d6880a3ff052 100644 --- a/src/pages/settings/AboutPage/ConsolePage.tsx +++ b/src/pages/settings/AboutPage/ConsolePage.tsx @@ -78,7 +78,7 @@ function ConsolePage({capturedLogs, shouldStoreLogs}: ConsolePageProps) { }, }, { - icon: Expensicons.CardsAndDomains, + icon: Expensicons.Globe, text: translate('common.network'), iconFill: activeFilterIndex === filterBy.network ? theme.iconSuccessFill : theme.icon, iconRight: Expensicons.CheckCircle, diff --git a/src/pages/settings/Troubleshoot/TroubleshootPage.tsx b/src/pages/settings/Troubleshoot/TroubleshootPage.tsx index a3b914192284..10cce86ac08e 100644 --- a/src/pages/settings/Troubleshoot/TroubleshootPage.tsx +++ b/src/pages/settings/Troubleshoot/TroubleshootPage.tsx @@ -69,7 +69,7 @@ function TroubleshootPage({shouldStoreLogs, shouldMaskOnyxState}: TroubleshootPa const menuItems = useMemo(() => { const debugConsoleItem: BaseMenuItem = { translationKey: 'initialSettingsPage.troubleshoot.viewConsole', - icon: Expensicons.Gear, + icon: Expensicons.Bug, action: waitForNavigate(() => Navigation.navigate(ROUTES.SETTINGS_CONSOLE.getRoute(ROUTES.SETTINGS_TROUBLESHOOT))), }; From c158a4a643be3232dfe5f905c20d8f65182dc21d Mon Sep 17 00:00:00 2001 From: hurali97 Date: Wed, 24 Jul 2024 17:21:48 +0500 Subject: [PATCH 004/111] feat: add filter option to debug console --- src/pages/settings/AboutPage/ConsolePage.tsx | 33 +++++++++----------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/src/pages/settings/AboutPage/ConsolePage.tsx b/src/pages/settings/AboutPage/ConsolePage.tsx index d6880a3ff052..97961138b280 100644 --- a/src/pages/settings/AboutPage/ConsolePage.tsx +++ b/src/pages/settings/AboutPage/ConsolePage.tsx @@ -1,7 +1,7 @@ import type {RouteProp} from '@react-navigation/native'; import {useRoute} from '@react-navigation/native'; import {format} from 'date-fns'; -import React, {useCallback, useEffect, useMemo, useState} from 'react'; +import React, {useCallback, useMemo, useRef, useState} from 'react'; import {View} from 'react-native'; import type {ListRenderItem, ListRenderItemInfo} from 'react-native'; import {withOnyx} from 'react-native-onyx'; @@ -44,13 +44,12 @@ type ConsolePageProps = ConsolePageOnyxProps; const filterBy = { all: '', - network: '-[Network]-', + network: '[Network]', } as const; type FilterBy = (typeof filterBy)[keyof typeof filterBy]; function ConsolePage({capturedLogs, shouldStoreLogs}: ConsolePageProps) { const [input, setInput] = useState(''); - const [logs, setLogs] = useState(capturedLogs); const [isGeneratingLogsFile, setIsGeneratingLogsFile] = useState(false); const [isLimitModalVisible, setIsLimitModalVisible] = useState(false); const [activeFilterIndex, setActiveFilterIndex] = useState(filterBy.all); @@ -92,24 +91,22 @@ function ConsolePage({capturedLogs, shouldStoreLogs}: ConsolePageProps) { [activeFilterIndex, theme.icon, theme.iconSuccessFill, translate], ); - const logsList = useMemo( - () => - Object.entries(logs ?? {}) - .map(([key, value]) => ({key, ...value})) - .reverse(), - [logs], - ); - - const filteredLogsList = useMemo(() => logsList.filter((log) => log.message.includes(activeFilterIndex)), [activeFilterIndex, logsList]); - - useEffect(() => { + const prevLogs = useRef>({}); + const getLogs = useCallback(() => { if (!shouldStoreLogs) { - return; + return []; } - setLogs((prevLogs) => ({...prevLogs, ...capturedLogs})); + prevLogs.current = {...prevLogs.current, ...capturedLogs}; + return Object.entries(prevLogs.current ?? {}) + .map(([key, value]) => ({key, ...value})) + .reverse(); }, [capturedLogs, shouldStoreLogs]); + const logsList = useMemo(() => getLogs(), [getLogs]); + + const filteredLogsList = useMemo(() => logsList.filter((log) => log.message.includes(activeFilterIndex)), [activeFilterIndex, logsList]); + const executeArbitraryCode = () => { const sanitizedInput = sanitizeConsoleInput(input); @@ -121,14 +118,14 @@ function ConsolePage({capturedLogs, shouldStoreLogs}: ConsolePageProps) { useKeyboardShortcut(CONST.KEYBOARD_SHORTCUTS.ENTER, executeArbitraryCode); const saveLogs = () => { - const logsWithParsedMessages = parseStringifiedMessages(logsList); + const logsWithParsedMessages = parseStringifiedMessages(filteredLogsList); localFileDownload('logs', JSON.stringify(logsWithParsedMessages, null, 2)); }; const shareLogs = () => { setIsGeneratingLogsFile(true); - const logsWithParsedMessages = parseStringifiedMessages(logsList); + const logsWithParsedMessages = parseStringifiedMessages(filteredLogsList); // Generate a file with the logs and pass its path to the list of reports to share it with localFileCreate('logs', JSON.stringify(logsWithParsedMessages, null, 2)).then(({path, size}) => { From fa2d0b34ffa6ddccc03e7fadb824b75b5456d28f Mon Sep 17 00:00:00 2001 From: hurali97 Date: Wed, 24 Jul 2024 17:22:34 +0500 Subject: [PATCH 005/111] feat: add extraData parameter for Logs --- src/libs/Console/index.ts | 12 ++++++------ src/libs/Log.ts | 6 +++--- src/types/onyx/Console.ts | 3 +++ 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/libs/Console/index.ts b/src/libs/Console/index.ts index 9bbdb173e61b..153008e2b785 100644 --- a/src/libs/Console/index.ts +++ b/src/libs/Console/index.ts @@ -53,7 +53,7 @@ function logMessage(args: unknown[]) { return String(arg); }) .join(' '); - const newLog = {time: new Date(), level: CONST.DEBUG_CONSOLE.LEVELS.INFO, message}; + const newLog = {time: new Date(), level: CONST.DEBUG_CONSOLE.LEVELS.INFO, message, extraData: ''}; addLog(newLog); } @@ -105,15 +105,15 @@ function createLog(text: string) { if (result !== undefined) { return [ - {time, level: CONST.DEBUG_CONSOLE.LEVELS.INFO, message: `> ${text}`}, - {time, level: CONST.DEBUG_CONSOLE.LEVELS.RESULT, message: String(result)}, + {time, level: CONST.DEBUG_CONSOLE.LEVELS.INFO, message: `> ${text}`, extraData: ''}, + {time, level: CONST.DEBUG_CONSOLE.LEVELS.RESULT, message: String(result), extraData: ''}, ]; } - return [{time, level: CONST.DEBUG_CONSOLE.LEVELS.INFO, message: `> ${text}`}]; + return [{time, level: CONST.DEBUG_CONSOLE.LEVELS.INFO, message: `> ${text}`, extraData: ''}]; } catch (error) { return [ - {time, level: CONST.DEBUG_CONSOLE.LEVELS.ERROR, message: `> ${text}`}, - {time, level: CONST.DEBUG_CONSOLE.LEVELS.ERROR, message: `Error: ${(error as Error).message}`}, + {time, level: CONST.DEBUG_CONSOLE.LEVELS.ERROR, message: `> ${text}`, extraData: ''}, + {time, level: CONST.DEBUG_CONSOLE.LEVELS.ERROR, message: `Error: ${(error as Error).message}`, extraData: ''}, ]; } } diff --git a/src/libs/Log.ts b/src/libs/Log.ts index 83965807263a..eefe383034e1 100644 --- a/src/libs/Log.ts +++ b/src/libs/Log.ts @@ -66,15 +66,15 @@ function serverLoggingCallback(logger: Logger, params: ServerLoggingCallbackOpti // callback methods are passed in here so we can decouple the logging library from the logging methods. const Log = new Logger({ serverLoggingCallback, - clientLoggingCallback: (message) => { + clientLoggingCallback: (message, extraData) => { if (!shouldAttachLog(message)) { return; } - console.debug(message); + console.debug(message, extraData); if (shouldCollectLogs) { - addLog({time: new Date(), level: CONST.DEBUG_CONSOLE.LEVELS.DEBUG, message}); + addLog({time: new Date(), level: CONST.DEBUG_CONSOLE.LEVELS.DEBUG, message, extraData}); } }, isDebug: true, diff --git a/src/types/onyx/Console.ts b/src/types/onyx/Console.ts index c8d2b714ae2b..03c76b6912b5 100644 --- a/src/types/onyx/Console.ts +++ b/src/types/onyx/Console.ts @@ -10,6 +10,9 @@ type Log = { /** Log message */ message: string; + + /** Additional data */ + extraData: string | Record | Array> | Error; }; /** Record of captured logs */ From 7249b23a947d74e070c4f31103e4a1e5d2b6e4f7 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Wed, 24 Jul 2024 18:02:08 +0500 Subject: [PATCH 006/111] feat: clear the Logs onyx state each time the app is launched --- src/libs/actions/Console.ts | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/libs/actions/Console.ts b/src/libs/actions/Console.ts index 79276d3307ac..e2c4047c43b5 100644 --- a/src/libs/actions/Console.ts +++ b/src/libs/actions/Console.ts @@ -2,14 +2,27 @@ import Onyx from 'react-native-onyx'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Log} from '@src/types/onyx'; +let isNewAppLaunch = true; /** * Merge the new log into the existing logs in Onyx * @param log the log to add */ function addLog(log: Log) { - Onyx.merge(ONYXKEYS.LOGS, { - [log.time.getTime()]: log, - }); + /** + * If this is the new app launch, we want to reset the log state in Onyx. + * This is because we don't want to keep logs from previous sessions and + * blow up the Onyx state. + */ + if (isNewAppLaunch) { + isNewAppLaunch = false; + Onyx.set(ONYXKEYS.LOGS, { + [log.time.getTime()]: log, + }); + } else { + Onyx.merge(ONYXKEYS.LOGS, { + [log.time.getTime()]: log, + }); + } } /** From eeb7f33d9b4ce2e5f8c6f8895266bc662848e6e6 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Wed, 24 Jul 2024 18:04:23 +0500 Subject: [PATCH 007/111] feat: Do not include request and response for Pusher authentication --- src/libs/Middleware/Logging.ts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/libs/Middleware/Logging.ts b/src/libs/Middleware/Logging.ts index f10e8d2f5120..d327dd06becc 100644 --- a/src/libs/Middleware/Logging.ts +++ b/src/libs/Middleware/Logging.ts @@ -32,15 +32,25 @@ function logRequestDetails(message: string, request: Request, response?: Respons logParams.requestID = response.requestID; } - Log.info(message, false, logParams); + const extraData: Record = {}; + /** + * We don't want to log the request and response data for AuthenticatePusher + * requests because they contain sensitive information. + */ + if (request.command !== 'AuthenticatePusher') { + extraData.request = request; + extraData.response = response; + } + + Log.info(message, false, logParams, false, extraData); } const Logging: Middleware = (response, request) => { const startTime = Date.now(); - logRequestDetails('Making API request', request); + logRequestDetails('[Network] Making API request', request); return response .then((data) => { - logRequestDetails(`Finished API request in ${Date.now() - startTime}ms`, request, data); + logRequestDetails(`[Network] Finished API request in ${Date.now() - startTime}ms`, request, data); return data; }) .catch((error: HttpsError) => { From fe66ee143bcdeede810359f5c4f4e224140d490e Mon Sep 17 00:00:00 2001 From: hurali97 Date: Wed, 24 Jul 2024 18:04:47 +0500 Subject: [PATCH 008/111] refactor: remove unused code --- src/libs/actions/OnyxUpdates.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/libs/actions/OnyxUpdates.ts b/src/libs/actions/OnyxUpdates.ts index 6769fa7e64a3..672f325be58a 100644 --- a/src/libs/actions/OnyxUpdates.ts +++ b/src/libs/actions/OnyxUpdates.ts @@ -35,8 +35,6 @@ function applyHTTPSOnyxUpdates(request: Request, response: Response) { // apply successData or failureData. This ensures that we do not update any pending, loading, or other UI states contained // in successData/failureData until after the component has received and API data. const onyxDataUpdatePromise = response.onyxData ? updateHandler(response.onyxData) : Promise.resolve(); - Log.info('[OnyxUpdateManager]-[Network]-[Request]', false, request); - Log.info('[OnyxUpdateManager]-[Network]-[Response]', false, response); return onyxDataUpdatePromise .then(() => { // Handle the request's success/failure data (client-side data) From 92e67081c50bb6fe250a34aed5223d4f7e3bccf3 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Thu, 25 Jul 2024 11:57:16 +0800 Subject: [PATCH 009/111] clears routes when successfully created/updated transaction --- src/libs/actions/IOU.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 30f0ccfae328..925877bbdf01 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -710,6 +710,7 @@ function buildOnyxDataForMoneyRequest( value: { pendingAction: null, pendingFields: clearedPendingFields, + routes: null, }, }, @@ -1459,6 +1460,7 @@ function buildOnyxDataForTrackExpense( value: { pendingAction: null, pendingFields: clearedPendingFields, + routes: null, }, }, ); @@ -2753,6 +2755,7 @@ function getUpdateMoneyRequestParams( pendingFields: clearedPendingFields, isLoading: false, errorFields: null, + routes: null, }, }); @@ -2964,6 +2967,7 @@ function getUpdateTrackExpenseParams( pendingFields: clearedPendingFields, isLoading: false, errorFields: null, + routes: null, }, }); From 9591dbb32089fcfe301e75361b28359a9c773324 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Thu, 25 Jul 2024 12:57:51 +0800 Subject: [PATCH 010/111] remove pending --- src/components/ReportActionItem/MoneyRequestView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 6d9b8f75fcc9..bd3a838b895a 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -324,7 +324,7 @@ function MoneyRequestView({ Date: Thu, 25 Jul 2024 12:58:11 +0800 Subject: [PATCH 011/111] get distance in meters --- src/components/MoneyRequestConfirmationList.tsx | 12 +++--------- .../ReportActionItem/MoneyRequestView.tsx | 2 +- src/libs/TransactionUtils.ts | 14 +++++++++++--- src/libs/actions/IOU.ts | 2 +- .../request/step/IOURequestStepDistanceRate.tsx | 2 +- 5 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index 7d1ac66408ba..6b0a6c5cc0c5 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -256,13 +256,7 @@ function MoneyRequestConfirmationList({ const isMovingTransactionFromTrackExpense = IOUUtils.isMovingTransactionFromTrackExpense(action); - const distance = useMemo(() => { - const value = TransactionUtils.getDistance(transaction); - if (canUseP2PDistanceRequests && isMovingTransactionFromTrackExpense && unit && !TransactionUtils.isFetchingWaypointsFromServer(transaction)) { - return DistanceRequestUtils.convertToDistanceInMeters(value, unit); - } - return value; - }, [isMovingTransactionFromTrackExpense, unit, transaction, canUseP2PDistanceRequests]); + const distance = TransactionUtils.getDistanceInMeters(transaction, unit); const prevDistance = usePrevious(distance); const shouldCalculateDistanceAmount = isDistanceRequest && (iouAmount === 0 || prevRate !== rate || prevDistance !== distance); @@ -359,7 +353,7 @@ function MoneyRequestConfirmationList({ if (isDistanceRequest) { const customUnitRate = getCustomUnitRate(policy, customUnitRateID); taxCode = customUnitRate?.attributes?.taxRateExternalID ?? ''; - taxableAmount = DistanceRequestUtils.getTaxableAmount(policy, customUnitRateID, TransactionUtils.getDistance(transaction)); + taxableAmount = DistanceRequestUtils.getTaxableAmount(policy, customUnitRateID, distance); } else { taxableAmount = transaction?.amount ?? 0; taxCode = transaction?.taxCode ?? TransactionUtils.getDefaultTaxCode(policy, transaction) ?? ''; @@ -368,7 +362,7 @@ function MoneyRequestConfirmationList({ const taxAmount = TransactionUtils.calculateTaxAmount(taxPercentage, taxableAmount, currency); const taxAmountInSmallestCurrencyUnits = CurrencyUtils.convertToBackendAmount(Number.parseFloat(taxAmount.toString())); IOU.setMoneyRequestTaxAmount(transaction?.transactionID ?? '', taxAmountInSmallestCurrencyUnits); - }, [policy, shouldShowTax, previousTransactionAmount, previousTransactionCurrency, transaction, isDistanceRequest, customUnitRateID, currency]); + }, [policy, shouldShowTax, previousTransactionAmount, previousTransactionCurrency, transaction, isDistanceRequest, customUnitRateID, currency, distance]); // If completing a split expense fails, set didConfirm to false to allow the user to edit the fields again if (isEditingSplitBill && didConfirm) { diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index bd3a838b895a..41eae377dee6 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -222,7 +222,7 @@ function MoneyRequestView({ const {unit} = mileageRate; const rate = transaction?.comment?.customUnit?.defaultP2PRate ?? mileageRate.rate; - const distance = DistanceRequestUtils.convertToDistanceInMeters(TransactionUtils.getDistance(transaction), unit); + const distance = TransactionUtils.getDistanceInMeters(transaction, unit); const rateToDisplay = DistanceRequestUtils.getRateForDisplay(unit, rate, currency, translate, toLocaleDigit, isOffline); const distanceToDisplay = DistanceRequestUtils.getDistanceForDisplay(hasRoute, distance, unit, rate, translate); let merchantTitle = isEmptyMerchant ? '' : transactionMerchant; diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index c044702b2fc0..8102692a030e 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -6,6 +6,7 @@ import type {ValueOf} from 'type-fest'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Beta, OnyxInputOrEntry, Policy, RecentWaypoint, ReviewDuplicates, TaxRate, TaxRates, Transaction, TransactionViolation, TransactionViolations} from '@src/types/onyx'; +import {Unit} from '@src/types/onyx/Policy'; import type {Comment, Receipt, TransactionChanges, TransactionPendingFieldsKey, Waypoint, WaypointCollection} from '@src/types/onyx/Transaction'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import type {IOURequestType} from './actions/IOU'; @@ -13,6 +14,7 @@ import type {TransactionMergeParams} from './API/parameters'; import {isCorporateCard, isExpensifyCard} from './CardUtils'; import {getCurrencyDecimals} from './CurrencyUtils'; import DateUtils from './DateUtils'; +import DistanceRequestUtils from './DistanceRequestUtils'; import * as Localize from './Localize'; import * as NumberUtils from './NumberUtils'; import Permissions from './Permissions'; @@ -383,8 +385,14 @@ function getMerchant(transaction: OnyxInputOrEntry): string { return transaction?.modifiedMerchant ? transaction.modifiedMerchant : transaction?.merchant ?? ''; } -function getDistance(transaction: OnyxInputOrEntry): number { - return transaction?.comment?.customUnit?.quantity ?? 0; +function getDistanceInMeters(transaction: OnyxInputOrEntry, unit: Unit | undefined) { + if (transaction?.routes?.route0?.distance) { + return transaction.routes.route0.distance; + } + if (transaction?.comment?.customUnit?.quantity && unit) { + return DistanceRequestUtils.convertToDistanceInMeters(transaction.comment.customUnit.quantity, unit); + } + return 0; } /** @@ -998,7 +1006,7 @@ export { getTaxAmount, getTaxCode, getCurrency, - getDistance, + getDistanceInMeters, getCardID, getOriginalCurrency, getOriginalAmount, diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 925877bbdf01..ee50f33c71af 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -2492,7 +2492,7 @@ function calculateAmountForUpdatedWaypoint( ? DistanceRequestUtils.getRateForP2P(policyCurrency) : mileageRates?.[customUnitRateID] ?? DistanceRequestUtils.getDefaultMileageRate(policy); const {unit, rate} = mileageRate; - const distance = TransactionUtils.getDistance(transaction); + const distance = TransactionUtils.getDistanceInMeters(transaction, unit); const amount = DistanceRequestUtils.getDistanceRequestAmount(distance, unit, rate ?? 0); updatedAmount = ReportUtils.isExpenseReport(iouReport) ? -amount : amount; updatedMerchant = DistanceRequestUtils.getDistanceMerchant(true, distance, unit, rate, transaction?.currency ?? CONST.CURRENCY.USD, Localize.translateLocal, (digit) => diff --git a/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx b/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx index 4f70d3e4fee9..a55d491ea834 100644 --- a/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx @@ -79,7 +79,7 @@ function IOURequestStepDistanceRate({ if (shouldShowTax) { const policyCustomUnitRate = getCustomUnitRate(policy, customUnitRateID); const taxRateExternalID = policyCustomUnitRate?.attributes?.taxRateExternalID ?? '-1'; - const taxableAmount = DistanceRequestUtils.getTaxableAmount(policy, customUnitRateID, TransactionUtils.getDistance(transaction)); + const taxableAmount = DistanceRequestUtils.getTaxableAmount(policy, customUnitRateID, TransactionUtils.getDistanceInMeters(transaction, unit)); const taxPercentage = TransactionUtils.getTaxValue(policy, transaction, taxRateExternalID) ?? ''; const taxAmount = CurrencyUtils.convertToBackendAmount(TransactionUtils.calculateTaxAmount(taxPercentage, taxableAmount, rates[customUnitRateID].currency ?? CONST.CURRENCY.USD)); IOU.setMoneyRequestTaxAmount(transactionID, taxAmount); From 94eda7b1c0bebb0d6f6a79dbf4c1e08997329327 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Thu, 25 Jul 2024 15:41:13 +0800 Subject: [PATCH 012/111] restructure the file to avoid cyclic deps --- .../TransactionUtils/getDistanceInMeters.ts | 15 ++++++++ .../index.ts} | 37 +++++++------------ 2 files changed, 28 insertions(+), 24 deletions(-) create mode 100644 src/libs/TransactionUtils/getDistanceInMeters.ts rename src/libs/{TransactionUtils.ts => TransactionUtils/index.ts} (97%) diff --git a/src/libs/TransactionUtils/getDistanceInMeters.ts b/src/libs/TransactionUtils/getDistanceInMeters.ts new file mode 100644 index 000000000000..1e39078905e6 --- /dev/null +++ b/src/libs/TransactionUtils/getDistanceInMeters.ts @@ -0,0 +1,15 @@ +import DistanceRequestUtils from '@libs/DistanceRequestUtils'; +import type {OnyxInputOrEntry, Transaction} from '@src/types/onyx'; +import type {Unit} from '@src/types/onyx/Policy'; + +function getDistanceInMeters(transaction: OnyxInputOrEntry, unit: Unit | undefined) { + if (transaction?.routes?.route0?.distance) { + return transaction.routes.route0.distance; + } + if (transaction?.comment?.customUnit?.quantity && unit) { + return DistanceRequestUtils.convertToDistanceInMeters(transaction.comment.customUnit.quantity, unit); + } + return 0; +} + +export default getDistanceInMeters; diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils/index.ts similarity index 97% rename from src/libs/TransactionUtils.ts rename to src/libs/TransactionUtils/index.ts index 8102692a030e..a57ff7f940d2 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils/index.ts @@ -3,25 +3,24 @@ import lodashIsEqual from 'lodash/isEqual'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; +import type {TransactionMergeParams} from '@libs/API/parameters'; +import {isCorporateCard, isExpensifyCard} from '@libs/CardUtils'; +import {getCurrencyDecimals} from '@libs/CurrencyUtils'; +import DateUtils from '@libs/DateUtils'; +import * as Localize from '@libs/Localize'; +import * as NumberUtils from '@libs/NumberUtils'; +import Permissions from '@libs/Permissions'; +import {getCleanedTagName, getCustomUnitRate} from '@libs/PolicyUtils'; +// eslint-disable-next-line import/no-cycle +import * as ReportActionsUtils from '@libs/ReportActionsUtils'; +import * as ReportConnection from '@libs/ReportConnection'; +import type {IOURequestType} from '@userActions/IOU'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Beta, OnyxInputOrEntry, Policy, RecentWaypoint, ReviewDuplicates, TaxRate, TaxRates, Transaction, TransactionViolation, TransactionViolations} from '@src/types/onyx'; -import {Unit} from '@src/types/onyx/Policy'; import type {Comment, Receipt, TransactionChanges, TransactionPendingFieldsKey, Waypoint, WaypointCollection} from '@src/types/onyx/Transaction'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; -import type {IOURequestType} from './actions/IOU'; -import type {TransactionMergeParams} from './API/parameters'; -import {isCorporateCard, isExpensifyCard} from './CardUtils'; -import {getCurrencyDecimals} from './CurrencyUtils'; -import DateUtils from './DateUtils'; -import DistanceRequestUtils from './DistanceRequestUtils'; -import * as Localize from './Localize'; -import * as NumberUtils from './NumberUtils'; -import Permissions from './Permissions'; -import {getCleanedTagName, getCustomUnitRate} from './PolicyUtils'; -// eslint-disable-next-line import/no-cycle -import * as ReportActionsUtils from './ReportActionsUtils'; -import * as ReportConnection from './ReportConnection'; +import getDistanceInMeters from './getDistanceInMeters'; let allTransactions: OnyxCollection = {}; Onyx.connect({ @@ -385,16 +384,6 @@ function getMerchant(transaction: OnyxInputOrEntry): string { return transaction?.modifiedMerchant ? transaction.modifiedMerchant : transaction?.merchant ?? ''; } -function getDistanceInMeters(transaction: OnyxInputOrEntry, unit: Unit | undefined) { - if (transaction?.routes?.route0?.distance) { - return transaction.routes.route0.distance; - } - if (transaction?.comment?.customUnit?.quantity && unit) { - return DistanceRequestUtils.convertToDistanceInMeters(transaction.comment.customUnit.quantity, unit); - } - return 0; -} - /** * Return the reimbursable value. Defaults to true to match BE logic. */ From 46c3f7832638cfe40dadb42eeaab3bd7eb272aa8 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Thu, 25 Jul 2024 15:58:53 +0500 Subject: [PATCH 013/111] feat: flush all logs on each app launch --- src/libs/Log.ts | 13 +++++++------ src/libs/actions/Console.ts | 33 +++++++++++++++++---------------- 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/src/libs/Log.ts b/src/libs/Log.ts index eefe383034e1..72673b8d3f79 100644 --- a/src/libs/Log.ts +++ b/src/libs/Log.ts @@ -8,7 +8,7 @@ import type {Merge} from 'type-fest'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import pkg from '../../package.json'; -import {addLog} from './actions/Console'; +import {addLog, flushAllLogsOnAppLaunch} from './actions/Console'; import {shouldAttachLog} from './Console'; import getPlatform from './getPlatform'; import * as Network from './Network'; @@ -71,11 +71,12 @@ const Log = new Logger({ return; } - console.debug(message, extraData); - - if (shouldCollectLogs) { - addLog({time: new Date(), level: CONST.DEBUG_CONSOLE.LEVELS.DEBUG, message, extraData}); - } + flushAllLogsOnAppLaunch().then(() => { + console.debug(message, extraData); + if (shouldCollectLogs) { + addLog({time: new Date(), level: CONST.DEBUG_CONSOLE.LEVELS.DEBUG, message, extraData}); + } + }); }, isDebug: true, }); diff --git a/src/libs/actions/Console.ts b/src/libs/actions/Console.ts index e2c4047c43b5..6e585c6ad12d 100644 --- a/src/libs/actions/Console.ts +++ b/src/libs/actions/Console.ts @@ -8,21 +8,9 @@ let isNewAppLaunch = true; * @param log the log to add */ function addLog(log: Log) { - /** - * If this is the new app launch, we want to reset the log state in Onyx. - * This is because we don't want to keep logs from previous sessions and - * blow up the Onyx state. - */ - if (isNewAppLaunch) { - isNewAppLaunch = false; - Onyx.set(ONYXKEYS.LOGS, { - [log.time.getTime()]: log, - }); - } else { - Onyx.merge(ONYXKEYS.LOGS, { - [log.time.getTime()]: log, - }); - } + Onyx.merge(ONYXKEYS.LOGS, { + [log.time.getTime()]: log, + }); } /** @@ -41,4 +29,17 @@ function disableLoggingAndFlushLogs() { Onyx.set(ONYXKEYS.LOGS, null); } -export {addLog, setShouldStoreLogs, disableLoggingAndFlushLogs}; +/** + * Clears the persisted logs on app launch, + * so that we have fresh logs for the new app session. + */ +function flushAllLogsOnAppLaunch() { + if (!isNewAppLaunch) { + return Promise.resolve(); + } + + isNewAppLaunch = false; + return Onyx.set(ONYXKEYS.LOGS, {}); +} + +export {addLog, setShouldStoreLogs, disableLoggingAndFlushLogs, flushAllLogsOnAppLaunch}; From 87c12ef1861b87aa04890e935744d47736659f06 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Fri, 26 Jul 2024 04:48:30 +0300 Subject: [PATCH 014/111] rm padding for nested lists --- docs/_sass/_main.scss | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/_sass/_main.scss b/docs/_sass/_main.scss index f3bf1035ed56..485c54dbfc4c 100644 --- a/docs/_sass/_main.scss +++ b/docs/_sass/_main.scss @@ -374,6 +374,14 @@ button { } } + ol { + li { + ul { + padding-bottom: 0; + } + } + } + table { margin-bottom: 20px; border-radius: 8px; From 306d7657145b0cef01d5665ebc01c88bb52a5d8b Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Fri, 26 Jul 2024 04:54:54 +0300 Subject: [PATCH 015/111] use h3 heading --- .../Add-Expensify-Card-to-Apple-or-Google-Pay.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/articles/new-expensify/expensify-card/Add-Expensify-Card-to-Apple-or-Google-Pay.md b/docs/articles/new-expensify/expensify-card/Add-Expensify-Card-to-Apple-or-Google-Pay.md index 844a688e0011..6c41d6413f7c 100644 --- a/docs/articles/new-expensify/expensify-card/Add-Expensify-Card-to-Apple-or-Google-Pay.md +++ b/docs/articles/new-expensify/expensify-card/Add-Expensify-Card-to-Apple-or-Google-Pay.md @@ -9,16 +9,14 @@ You can use your Expensify Card for contactless in-person payments by adding it {% include selector.html values="mobile" %} {% include option.html value="mobile" %} -**Apple Pay** - +### Apple Pay 1. Open the Apple Pay app. 2. Tap the + button. 3. Tap **Debit or Credit Card**. 4. Tap **Continue**. 5. Follow the steps provided to add your virtual card. -**Google Pay** - +### Google Pay 1. Open the Google Pay app. 2. Tap **Add to Wallet**. 3. Tap **Payment Card**. From 50fb3dc6e55d4ed2f0335276a5671106605a3496 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Fri, 26 Jul 2024 15:05:07 +0500 Subject: [PATCH 016/111] feat: use correct filter icon and adjust the popup position for web and desktop --- assets/images/filter.svg | 11 +++++++++++ src/components/HeaderWithBackButton/index.tsx | 4 ++++ src/components/HeaderWithBackButton/types.ts | 6 ++++++ src/components/Icon/Expensicons.ts | 2 ++ src/pages/settings/AboutPage/ConsolePage.tsx | 6 +++++- 5 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 assets/images/filter.svg diff --git a/assets/images/filter.svg b/assets/images/filter.svg new file mode 100644 index 000000000000..9323573df12c --- /dev/null +++ b/assets/images/filter.svg @@ -0,0 +1,11 @@ + + + + + diff --git a/src/components/HeaderWithBackButton/index.tsx b/src/components/HeaderWithBackButton/index.tsx index 2d73e3c2dd24..f1e715bface8 100755 --- a/src/components/HeaderWithBackButton/index.tsx +++ b/src/components/HeaderWithBackButton/index.tsx @@ -53,6 +53,8 @@ function HeaderWithBackButton({ horizontal: 0, }, threeDotsMenuItems = [], + threeDotsMenuIcon, + threeDotsMenuIconFill, shouldEnableDetailPageNavigation = false, children = null, shouldOverlayDots = false, @@ -234,6 +236,8 @@ function HeaderWithBackButton({ {shouldShowPinButton && !!report && } {shouldShowThreeDotsButton && ( & { /** The anchor position of the menu */ threeDotsAnchorPosition?: AnchorPosition; + /** Icon displayed on the right of the title */ + threeDotsMenuIcon?: IconAsset; + + /** The fill color to pass into the icon. */ + threeDotsMenuIconFill?: string; + /** Whether we should show a close button */ shouldShowCloseButton?: boolean; diff --git a/src/components/Icon/Expensicons.ts b/src/components/Icon/Expensicons.ts index 487df5594212..bd5fb88112fb 100644 --- a/src/components/Icon/Expensicons.ts +++ b/src/components/Icon/Expensicons.ts @@ -81,6 +81,7 @@ import ExpensifyLogoNew from '@assets/images/expensify-logo-new.svg'; import ExpensifyWordmark from '@assets/images/expensify-wordmark.svg'; import EyeDisabled from '@assets/images/eye-disabled.svg'; import Eye from '@assets/images/eye.svg'; +import Filter from '@assets/images/filter.svg'; import Flag from '@assets/images/flag.svg'; import FlagLevelOne from '@assets/images/flag_level_01.svg'; import FlagLevelTwo from '@assets/images/flag_level_02.svg'; @@ -372,4 +373,5 @@ export { CheckCircle, CheckmarkCircle, NetSuiteSquare, + Filter, }; diff --git a/src/pages/settings/AboutPage/ConsolePage.tsx b/src/pages/settings/AboutPage/ConsolePage.tsx index 97961138b280..eb9b13608039 100644 --- a/src/pages/settings/AboutPage/ConsolePage.tsx +++ b/src/pages/settings/AboutPage/ConsolePage.tsx @@ -19,6 +19,7 @@ import useKeyboardShortcut from '@hooks/useKeyboardShortcut'; import useLocalize from '@hooks/useLocalize'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; +import useWindowDimensions from '@hooks/useWindowDimensions'; import {addLog} from '@libs/actions/Console'; import {createLog, parseStringifiedMessages, sanitizeConsoleInput} from '@libs/Console'; import type {Log} from '@libs/Console'; @@ -56,7 +57,7 @@ function ConsolePage({capturedLogs, shouldStoreLogs}: ConsolePageProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); const theme = useTheme(); - + const {windowWidth} = useWindowDimensions(); const route = useRoute>(); const menuItems: PopoverMenuItem[] = useMemo( @@ -164,6 +165,9 @@ function ConsolePage({capturedLogs, shouldStoreLogs}: ConsolePageProps) { onBackButtonPress={() => Navigation.goBack(route.params?.backTo)} shouldShowThreeDotsButton threeDotsMenuItems={menuItems} + threeDotsAnchorPosition={styles.threeDotsPopoverOffset(windowWidth)} + threeDotsMenuIcon={Expensicons.Filter} + threeDotsMenuIconFill={theme.icon} /> Date: Sat, 27 Jul 2024 01:19:14 +0900 Subject: [PATCH 017/111] fix: null check instead of setting an empty value --- src/components/ButtonWithDropdownMenu/index.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/ButtonWithDropdownMenu/index.tsx b/src/components/ButtonWithDropdownMenu/index.tsx index 094c26a2b387..8096ff8fe4d1 100644 --- a/src/components/ButtonWithDropdownMenu/index.tsx +++ b/src/components/ButtonWithDropdownMenu/index.tsx @@ -139,7 +139,12 @@ function ButtonWithDropdownMenu({ onClose={() => setIsMenuVisible(false)} onItemSelected={() => setIsMenuVisible(false)} anchorPosition={popoverAnchorPosition} - anchorRef={caretButton} + anchorRef={() => { + if (caretButton === null) { + return; + } + return caretButton; + }} withoutOverlay anchorAlignment={anchorAlignment} headerText={menuHeaderText} From 64c133933c2bdf26a77146be3d3c0205b3d1b64a Mon Sep 17 00:00:00 2001 From: jacobkim9881 Date: Sat, 27 Jul 2024 02:35:19 +0900 Subject: [PATCH 018/111] fix: type check failed --- src/components/ButtonWithDropdownMenu/index.tsx | 2 +- src/components/PopoverMenu.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/ButtonWithDropdownMenu/index.tsx b/src/components/ButtonWithDropdownMenu/index.tsx index 8096ff8fe4d1..63be95626420 100644 --- a/src/components/ButtonWithDropdownMenu/index.tsx +++ b/src/components/ButtonWithDropdownMenu/index.tsx @@ -141,7 +141,7 @@ function ButtonWithDropdownMenu({ anchorPosition={popoverAnchorPosition} anchorRef={() => { if (caretButton === null) { - return; + return null; } return caretButton; }} diff --git a/src/components/PopoverMenu.tsx b/src/components/PopoverMenu.tsx index bcec153491c9..34d988cc5b58 100644 --- a/src/components/PopoverMenu.tsx +++ b/src/components/PopoverMenu.tsx @@ -61,7 +61,7 @@ type PopoverMenuProps = Partial & { anchorPosition: AnchorPosition; /** Ref of the anchor */ - anchorRef: RefObject; + anchorRef: RefObject; /** Where the popover should be positioned relative to the anchor points. */ anchorAlignment?: AnchorAlignment; From ceb7b4152cec0125957cc63b6894d50eb611a37b Mon Sep 17 00:00:00 2001 From: jacobkim9881 Date: Sat, 27 Jul 2024 02:55:38 +0900 Subject: [PATCH 019/111] canceled null check --- src/components/ButtonWithDropdownMenu/index.tsx | 2 +- src/components/PopoverMenu.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/ButtonWithDropdownMenu/index.tsx b/src/components/ButtonWithDropdownMenu/index.tsx index 8096ff8fe4d1..63be95626420 100644 --- a/src/components/ButtonWithDropdownMenu/index.tsx +++ b/src/components/ButtonWithDropdownMenu/index.tsx @@ -141,7 +141,7 @@ function ButtonWithDropdownMenu({ anchorPosition={popoverAnchorPosition} anchorRef={() => { if (caretButton === null) { - return; + return null; } return caretButton; }} diff --git a/src/components/PopoverMenu.tsx b/src/components/PopoverMenu.tsx index bcec153491c9..34d988cc5b58 100644 --- a/src/components/PopoverMenu.tsx +++ b/src/components/PopoverMenu.tsx @@ -61,7 +61,7 @@ type PopoverMenuProps = Partial & { anchorPosition: AnchorPosition; /** Ref of the anchor */ - anchorRef: RefObject; + anchorRef: RefObject; /** Where the popover should be positioned relative to the anchor points. */ anchorAlignment?: AnchorAlignment; From a52a9975698ca43d3ad8c06bead7b4700f8280c8 Mon Sep 17 00:00:00 2001 From: jacobkim9881 Date: Sat, 27 Jul 2024 03:03:59 +0900 Subject: [PATCH 020/111] canceled null check --- src/components/ButtonWithDropdownMenu/index.tsx | 2 +- src/components/PopoverMenu.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/ButtonWithDropdownMenu/index.tsx b/src/components/ButtonWithDropdownMenu/index.tsx index 63be95626420..8096ff8fe4d1 100644 --- a/src/components/ButtonWithDropdownMenu/index.tsx +++ b/src/components/ButtonWithDropdownMenu/index.tsx @@ -141,7 +141,7 @@ function ButtonWithDropdownMenu({ anchorPosition={popoverAnchorPosition} anchorRef={() => { if (caretButton === null) { - return null; + return; } return caretButton; }} diff --git a/src/components/PopoverMenu.tsx b/src/components/PopoverMenu.tsx index 34d988cc5b58..bcec153491c9 100644 --- a/src/components/PopoverMenu.tsx +++ b/src/components/PopoverMenu.tsx @@ -61,7 +61,7 @@ type PopoverMenuProps = Partial & { anchorPosition: AnchorPosition; /** Ref of the anchor */ - anchorRef: RefObject; + anchorRef: RefObject; /** Where the popover should be positioned relative to the anchor points. */ anchorAlignment?: AnchorAlignment; From d11b1e931acb8908fbff8ef292ab44d41b451a03 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Sat, 27 Jul 2024 01:04:53 +0300 Subject: [PATCH 021/111] navigate to company address if not present --- src/pages/Travel/ManageTrips.tsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/pages/Travel/ManageTrips.tsx b/src/pages/Travel/ManageTrips.tsx index 8545cf38566c..793a6132d485 100644 --- a/src/pages/Travel/ManageTrips.tsx +++ b/src/pages/Travel/ManageTrips.tsx @@ -9,11 +9,13 @@ import useLocalize from '@hooks/useLocalize'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; +import {getPolicy} from '@libs/PolicyUtils'; import colors from '@styles/theme/colors'; import * as Link from '@userActions/Link'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; const tripsFeatures: FeatureListItem[] = [ { @@ -34,6 +36,7 @@ function ManageTrips() { const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID); const hasAcceptedTravelTerms = travelSettings?.hasAcceptedTerms; + const hasPolicyAddress = !isEmptyObject(getPolicy(activePolicyID)?.address); const navigateToBookTravelDemo = () => { Linking.openURL(CONST.BOOK_TRAVEL_DEMO_URL); @@ -49,6 +52,10 @@ function ManageTrips() { ctaText={translate('travel.bookTravel')} ctaAccessibilityLabel={translate('travel.bookTravel')} onCtaPress={() => { + if (!hasPolicyAddress) { + Navigation.navigate(ROUTES.WORKSPACE_PROFILE_ADDRESS.getRoute(activePolicyID ?? '-1')); + return; + } if (!hasAcceptedTravelTerms) { Navigation.navigate(ROUTES.TRAVEL_TCS); return; From 658144216485da8715c8f0c3d536c86465293222 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Sat, 27 Jul 2024 01:08:03 +0300 Subject: [PATCH 022/111] Revert "use h3 heading" This reverts commit 306d7657145b0cef01d5665ebc01c88bb52a5d8b. --- .../Add-Expensify-Card-to-Apple-or-Google-Pay.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/articles/new-expensify/expensify-card/Add-Expensify-Card-to-Apple-or-Google-Pay.md b/docs/articles/new-expensify/expensify-card/Add-Expensify-Card-to-Apple-or-Google-Pay.md index 6c41d6413f7c..844a688e0011 100644 --- a/docs/articles/new-expensify/expensify-card/Add-Expensify-Card-to-Apple-or-Google-Pay.md +++ b/docs/articles/new-expensify/expensify-card/Add-Expensify-Card-to-Apple-or-Google-Pay.md @@ -9,14 +9,16 @@ You can use your Expensify Card for contactless in-person payments by adding it {% include selector.html values="mobile" %} {% include option.html value="mobile" %} -### Apple Pay +**Apple Pay** + 1. Open the Apple Pay app. 2. Tap the + button. 3. Tap **Debit or Credit Card**. 4. Tap **Continue**. 5. Follow the steps provided to add your virtual card. -### Google Pay +**Google Pay** + 1. Open the Google Pay app. 2. Tap **Add to Wallet**. 3. Tap **Payment Card**. From e4ffa8a6614c5dfc56eff3a09c1f8bd6add388f0 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Sat, 27 Jul 2024 01:08:16 +0300 Subject: [PATCH 023/111] Revert "rm padding for nested lists" This reverts commit 87c12ef1861b87aa04890e935744d47736659f06. --- docs/_sass/_main.scss | 8 -------- 1 file changed, 8 deletions(-) diff --git a/docs/_sass/_main.scss b/docs/_sass/_main.scss index 2b8627921958..82446fe08b3a 100644 --- a/docs/_sass/_main.scss +++ b/docs/_sass/_main.scss @@ -374,14 +374,6 @@ button { } } - ol { - li { - ul { - padding-bottom: 0; - } - } - } - table { margin-bottom: 20px; border-radius: 8px; From 548c4fa1c8f5130c38dda0217eafdce86c7e85db Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Sat, 27 Jul 2024 01:08:27 +0300 Subject: [PATCH 024/111] undo unrelated commits --- src/libs/Permissions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/Permissions.ts b/src/libs/Permissions.ts index ee1ce27103de..862337a42f14 100644 --- a/src/libs/Permissions.ts +++ b/src/libs/Permissions.ts @@ -4,7 +4,7 @@ import type {IOUType} from '@src/CONST'; import type Beta from '@src/types/onyx/Beta'; function canUseAllBetas(betas: OnyxEntry): boolean { - return !!betas?.includes(CONST.BETAS.ALL); + return true; } function canUseChronos(betas: OnyxEntry): boolean { From 0e6502a04d8c296f0632986484c78da2f302065b Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Sat, 27 Jul 2024 01:08:45 +0300 Subject: [PATCH 025/111] undo unrelated commits --- src/libs/Permissions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/Permissions.ts b/src/libs/Permissions.ts index 862337a42f14..ee1ce27103de 100644 --- a/src/libs/Permissions.ts +++ b/src/libs/Permissions.ts @@ -4,7 +4,7 @@ import type {IOUType} from '@src/CONST'; import type Beta from '@src/types/onyx/Beta'; function canUseAllBetas(betas: OnyxEntry): boolean { - return true; + return !!betas?.includes(CONST.BETAS.ALL); } function canUseChronos(betas: OnyxEntry): boolean { From d3e3498db5a055c97d4eeb0b6d319e46f40a7400 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Sat, 27 Jul 2024 01:56:31 +0300 Subject: [PATCH 026/111] get policy from onyx and show loading indicator --- src/pages/Travel/ManageTrips.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/pages/Travel/ManageTrips.tsx b/src/pages/Travel/ManageTrips.tsx index 793a6132d485..04231ea50c28 100644 --- a/src/pages/Travel/ManageTrips.tsx +++ b/src/pages/Travel/ManageTrips.tsx @@ -3,13 +3,13 @@ import {Linking, View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import type {FeatureListItem} from '@components/FeatureList'; import FeatureList from '@components/FeatureList'; +import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import * as Illustrations from '@components/Icon/Illustrations'; import ScrollView from '@components/ScrollView'; import useLocalize from '@hooks/useLocalize'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; -import {getPolicy} from '@libs/PolicyUtils'; import colors from '@styles/theme/colors'; import * as Link from '@userActions/Link'; import CONST from '@src/CONST'; @@ -34,9 +34,14 @@ function ManageTrips() { const {translate} = useLocalize(); const [travelSettings] = useOnyx(ONYXKEYS.NVP_TRAVEL_SETTINGS); const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID); + const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${activePolicyID}`); + + if (isEmptyObject(policy)) { + return ; + } const hasAcceptedTravelTerms = travelSettings?.hasAcceptedTerms; - const hasPolicyAddress = !isEmptyObject(getPolicy(activePolicyID)?.address); + const hasPolicyAddress = !isEmptyObject(policy?.address); const navigateToBookTravelDemo = () => { Linking.openURL(CONST.BOOK_TRAVEL_DEMO_URL); From 186354a376c88c157fceba64e69cf8b31d29ab39 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Sat, 27 Jul 2024 14:02:22 +0300 Subject: [PATCH 027/111] show error message for cta --- src/components/FeatureList.tsx | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/components/FeatureList.tsx b/src/components/FeatureList.tsx index 37b798dcd66c..4db9267a2aa5 100644 --- a/src/components/FeatureList.tsx +++ b/src/components/FeatureList.tsx @@ -7,6 +7,7 @@ import variables from '@styles/variables'; import type {TranslationPaths} from '@src/languages/types'; import type IconAsset from '@src/types/utils/IconAsset'; import Button from './Button'; +import DotIndicatorMessage from './DotIndicatorMessage'; import type DotLottieAnimation from './LottieAnimations/types'; import MenuItem from './MenuItem'; import Section from './Section'; @@ -56,6 +57,9 @@ type FeatureListProps = { /** The style used for the title */ titleStyles?: StyleProp; + /** The error message to display for the CTA button */ + ctaErrorMessage?: string; + /** Padding for content on large screens */ contentPaddingOnLargeScreens?: {padding: number}; }; @@ -65,10 +69,11 @@ function FeatureList({ subtitle = '', ctaText = '', ctaAccessibilityLabel = '', - onCtaPress, + onCtaPress = () => {}, secondaryButtonText = '', secondaryButtonAccessibilityLabel = '', - onSecondaryButtonPress, + onSecondaryButtonPress = () => {}, + ctaErrorMessage, menuItems, illustration, illustrationStyle, @@ -120,6 +125,12 @@ function FeatureList({ large /> )} + {ctaErrorMessage && ( + + )}