From 71e6a2fd7d2f3489d9531246718ffae07e6d14ab Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Mon, 25 Sep 2023 16:13:49 +0200 Subject: [PATCH 001/844] ref: move LocalNotification to TS --- ...tifications.js => BrowserNotifications.ts} | 44 ++++++------------- .../{index.desktop.js => index.desktop.ts} | 5 ++- .../LocalNotification/focusApp/index.ts | 6 +++ .../focusApp/index.website.js | 2 - .../LocalNotification/focusApp/types.ts | 3 ++ .../{index.desktop.js => index.desktop.ts} | 8 ++-- .../{index.native.js => index.native.ts} | 0 .../{index.website.js => index.ts} | 7 ++- .../Notification/LocalNotification/types.ts | 24 ++++++++++ 9 files changed, 61 insertions(+), 38 deletions(-) rename src/libs/Notification/LocalNotification/{BrowserNotifications.js => BrowserNotifications.ts} (77%) rename src/libs/Notification/LocalNotification/focusApp/{index.desktop.js => index.desktop.ts} (59%) create mode 100644 src/libs/Notification/LocalNotification/focusApp/index.ts delete mode 100644 src/libs/Notification/LocalNotification/focusApp/index.website.js create mode 100644 src/libs/Notification/LocalNotification/focusApp/types.ts rename src/libs/Notification/LocalNotification/{index.desktop.js => index.desktop.ts} (57%) rename src/libs/Notification/LocalNotification/{index.native.js => index.native.ts} (100%) rename src/libs/Notification/LocalNotification/{index.website.js => index.ts} (68%) create mode 100644 src/libs/Notification/LocalNotification/types.ts diff --git a/src/libs/Notification/LocalNotification/BrowserNotifications.js b/src/libs/Notification/LocalNotification/BrowserNotifications.ts similarity index 77% rename from src/libs/Notification/LocalNotification/BrowserNotifications.js rename to src/libs/Notification/LocalNotification/BrowserNotifications.ts index e55c0430fe17..fcf795c6769c 100644 --- a/src/libs/Notification/LocalNotification/BrowserNotifications.js +++ b/src/libs/Notification/LocalNotification/BrowserNotifications.ts @@ -1,18 +1,16 @@ // Web and desktop implementation only. Do not import for direct use. Use LocalNotification. -import _ from 'underscore'; -import focusApp from './focusApp'; import * as AppUpdate from '../../actions/AppUpdate'; import EXPENSIFY_ICON_URL from '../../../../assets/images/expensify-logo-round-clearspace.png'; import * as ReportUtils from '../../ReportUtils'; +import {PushParams, ReportCommentParams} from './types'; +import focusApp from './focusApp'; const DEFAULT_DELAY = 4000; /** * Checks if the user has granted permission to show browser notifications - * - * @return {Promise} */ -function canUseBrowserNotifications() { +function canUseBrowserNotifications(): Promise { return new Promise((resolve) => { // They have no browser notifications so we can't use this feature if (!window.Notification) { @@ -36,18 +34,9 @@ function canUseBrowserNotifications() { /** * Light abstraction around browser push notifications. * Checks for permission before determining whether to send. - * - * @param {Object} params - * @param {String} params.title - * @param {String} params.body - * @param {String} [params.icon] Path to icon - * @param {Number} [params.delay] - * @param {Function} [params.onClick] - * @param {String} [params.tag] - * - * @return {Promise} - resolves with Notification object or undefined + * @return resolves with Notification object or undefined */ -function push({title, body, delay = DEFAULT_DELAY, onClick = () => {}, tag = '', icon}) { +function push({title, body, delay = DEFAULT_DELAY, onClick = () => {}, tag = '', icon}: PushParams): Promise { return new Promise((resolve) => { if (!title || !body) { throw new Error('BrowserNotification must include title and body parameter.'); @@ -62,7 +51,7 @@ function push({title, body, delay = DEFAULT_DELAY, onClick = () => {}, tag = '', const notification = new Notification(title, { body, tag, - icon, + icon: String(icon), }); // If we pass in a delay param greater than 0 the notification @@ -91,27 +80,22 @@ function push({title, body, delay = DEFAULT_DELAY, onClick = () => {}, tag = '', export default { /** * Create a report comment notification - * - * @param {Object} params - * @param {Object} params.report - * @param {Object} params.reportAction - * @param {Function} params.onClick - * @param {Boolean} usesIcon true if notification uses right circular icon + * @param usesIcon true if notification uses right circular icon */ - pushReportCommentNotification({report, reportAction, onClick}, usesIcon = false) { - let title; - let body; + pushReportCommentNotification({report, reportAction, onClick}: ReportCommentParams, usesIcon = false) { + let title: string | undefined; + let body: string | undefined; const isChatRoom = ReportUtils.isChatRoom(report); const {person, message} = reportAction; - const plainTextPerson = _.map(person, (f) => f.text).join(); + const plainTextPerson = person?.map((f) => f.text).join(); // Specifically target the comment part of the message - const plainTextMessage = (_.find(message, (f) => f.type === 'COMMENT') || {}).text; + const plainTextMessage = message?.find((f) => f.type === 'COMMENT')?.text; if (isChatRoom) { - const roomName = _.get(report, 'displayName', ''); + const roomName = report.displayName ?? ''; title = roomName; body = `${plainTextPerson}: ${plainTextMessage}`; } else { @@ -120,7 +104,7 @@ export default { } push({ - title, + title: title ?? '', body, delay: 0, onClick, diff --git a/src/libs/Notification/LocalNotification/focusApp/index.desktop.js b/src/libs/Notification/LocalNotification/focusApp/index.desktop.ts similarity index 59% rename from src/libs/Notification/LocalNotification/focusApp/index.desktop.js rename to src/libs/Notification/LocalNotification/focusApp/index.desktop.ts index f00c72dc1af8..2b6f01457f44 100644 --- a/src/libs/Notification/LocalNotification/focusApp/index.desktop.js +++ b/src/libs/Notification/LocalNotification/focusApp/index.desktop.ts @@ -1,5 +1,8 @@ import ELECTRON_EVENTS from '../../../../../desktop/ELECTRON_EVENTS'; +import FocusApp from './types'; -export default () => { +const focusApp: FocusApp = () => { window.electron.send(ELECTRON_EVENTS.REQUEST_FOCUS_APP); }; + +export default focusApp; diff --git a/src/libs/Notification/LocalNotification/focusApp/index.ts b/src/libs/Notification/LocalNotification/focusApp/index.ts new file mode 100644 index 000000000000..8504fde1bcb2 --- /dev/null +++ b/src/libs/Notification/LocalNotification/focusApp/index.ts @@ -0,0 +1,6 @@ +import FocusApp from './types'; + +// On web this is up to the browser that shows the notifications +const focusApp: FocusApp = () => {}; + +export default focusApp; diff --git a/src/libs/Notification/LocalNotification/focusApp/index.website.js b/src/libs/Notification/LocalNotification/focusApp/index.website.js deleted file mode 100644 index ce7439d94cc3..000000000000 --- a/src/libs/Notification/LocalNotification/focusApp/index.website.js +++ /dev/null @@ -1,2 +0,0 @@ -// On web this is up to the browser that shows the notifications -export default () => {}; diff --git a/src/libs/Notification/LocalNotification/focusApp/types.ts b/src/libs/Notification/LocalNotification/focusApp/types.ts new file mode 100644 index 000000000000..534b7500fc71 --- /dev/null +++ b/src/libs/Notification/LocalNotification/focusApp/types.ts @@ -0,0 +1,3 @@ +type FocusApp = () => void; + +export default FocusApp; diff --git a/src/libs/Notification/LocalNotification/index.desktop.js b/src/libs/Notification/LocalNotification/index.desktop.ts similarity index 57% rename from src/libs/Notification/LocalNotification/index.desktop.js rename to src/libs/Notification/LocalNotification/index.desktop.ts index 2bef51cea0a6..acc3b8857c0f 100644 --- a/src/libs/Notification/LocalNotification/index.desktop.js +++ b/src/libs/Notification/LocalNotification/index.desktop.ts @@ -1,6 +1,7 @@ -import BrowserNotifications from './BrowserNotifications'; +import BrowserNotifications from './BrowserNotifications.js'; +import {LocalNotificationModule, ReportCommentParams} from './types.js'; -function showCommentNotification({report, reportAction, onClick}) { +function showCommentNotification({report, reportAction, onClick}: ReportCommentParams) { BrowserNotifications.pushReportCommentNotification({report, reportAction, onClick}); } @@ -8,7 +9,8 @@ function showUpdateAvailableNotification() { BrowserNotifications.pushUpdateAvailableNotification(); } -export default { +const LocalNotification: LocalNotificationModule = { showCommentNotification, showUpdateAvailableNotification, }; +export default LocalNotification; diff --git a/src/libs/Notification/LocalNotification/index.native.js b/src/libs/Notification/LocalNotification/index.native.ts similarity index 100% rename from src/libs/Notification/LocalNotification/index.native.js rename to src/libs/Notification/LocalNotification/index.native.ts diff --git a/src/libs/Notification/LocalNotification/index.website.js b/src/libs/Notification/LocalNotification/index.ts similarity index 68% rename from src/libs/Notification/LocalNotification/index.website.js rename to src/libs/Notification/LocalNotification/index.ts index 3410b3144caf..c0182d156d46 100644 --- a/src/libs/Notification/LocalNotification/index.website.js +++ b/src/libs/Notification/LocalNotification/index.ts @@ -1,6 +1,7 @@ import BrowserNotifications from './BrowserNotifications'; +import {LocalNotificationModule, ReportCommentParams} from './types'; -function showCommentNotification({report, reportAction, onClick}) { +function showCommentNotification({report, reportAction, onClick}: ReportCommentParams) { BrowserNotifications.pushReportCommentNotification({report, reportAction, onClick}, true); } @@ -8,7 +9,9 @@ function showUpdateAvailableNotification() { BrowserNotifications.pushUpdateAvailableNotification(); } -export default { +const LocalNotification: LocalNotificationModule = { showCommentNotification, showUpdateAvailableNotification, }; + +export default LocalNotification; diff --git a/src/libs/Notification/LocalNotification/types.ts b/src/libs/Notification/LocalNotification/types.ts new file mode 100644 index 000000000000..576297467155 --- /dev/null +++ b/src/libs/Notification/LocalNotification/types.ts @@ -0,0 +1,24 @@ +import {ImageSourcePropType} from 'react-native'; +import {ReportAction, Report} from '../../../types/onyx'; + +type PushParams = { + title: string; + body?: string; + icon?: string | ImageSourcePropType; + delay?: number; + onClick?: () => void; + tag?: string; +}; + +type ReportCommentParams = { + report: Report; + reportAction: ReportAction; + onClick: () => void; +}; + +type LocalNotificationModule = { + showCommentNotification: (reportCommentParams: ReportCommentParams) => void; + showUpdateAvailableNotification: () => void; +}; + +export type {PushParams, ReportCommentParams, LocalNotificationModule}; From 0354131250232a1bcffa04acf90cdf6d0b3f890e Mon Sep 17 00:00:00 2001 From: miljakljajic Date: Fri, 29 Sep 2023 16:08:36 +0800 Subject: [PATCH 002/844] Update Uber.md Updating the Uber integration page --- .../integrations/travel-integrations/Uber.md | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/docs/articles/expensify-classic/integrations/travel-integrations/Uber.md b/docs/articles/expensify-classic/integrations/travel-integrations/Uber.md index 3ee1c8656b4b..b88cbc0aca39 100644 --- a/docs/articles/expensify-classic/integrations/travel-integrations/Uber.md +++ b/docs/articles/expensify-classic/integrations/travel-integrations/Uber.md @@ -1,5 +1,26 @@ --- -title: Coming Soon -description: Coming Soon +Uber integration +Connecting your Uber account to Expensify --- -## Resource Coming Soon! +## Overview + +Link Expensify directly to your Uber account so your Uber for Business receipts populate automatically in Expensify. + +# How to connect Uber to Expensify + +You can do this right in the Uber app: + +1. Head to Account > Business hub > Get started +2. Tap Create an individual account > Get started +3. Enter your business email and tap Next +4. Select the payment card you'd like to use for your business profile +5. Choose how frequently you’d like to receive travel summaries +6. Select Expensify as your expense provider +Expensify and Uber are now connected! + +Now, every time you use Uber for Business – be it for rides or meals – the receipt will be imported and scanned into Expensify automatically. + +![Alt text for accessibility] +(https://help.expensify.com/assets/images/Uber1.png){:width="100%"} +![Alt text for accessibility] +(https://help.expensify.com/assets/images/Uber2.png){:width="100%"} From ebaea0f0a39ff5750d1ef077748e49013462fd30 Mon Sep 17 00:00:00 2001 From: miljakljajic Date: Tue, 3 Oct 2023 10:59:43 +0200 Subject: [PATCH 003/844] Update docs/articles/expensify-classic/integrations/travel-integrations/Uber.md Co-authored-by: Rushat Gabhane --- .../expensify-classic/integrations/travel-integrations/Uber.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/articles/expensify-classic/integrations/travel-integrations/Uber.md b/docs/articles/expensify-classic/integrations/travel-integrations/Uber.md index b88cbc0aca39..52b46a631367 100644 --- a/docs/articles/expensify-classic/integrations/travel-integrations/Uber.md +++ b/docs/articles/expensify-classic/integrations/travel-integrations/Uber.md @@ -1,5 +1,5 @@ --- -Uber integration +title: Uber integration Connecting your Uber account to Expensify --- ## Overview From b88fc1fe768e3aea1aac6d31d8f919b3d417be81 Mon Sep 17 00:00:00 2001 From: miljakljajic Date: Tue, 3 Oct 2023 10:59:50 +0200 Subject: [PATCH 004/844] Update docs/articles/expensify-classic/integrations/travel-integrations/Uber.md Co-authored-by: Rushat Gabhane --- .../expensify-classic/integrations/travel-integrations/Uber.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/articles/expensify-classic/integrations/travel-integrations/Uber.md b/docs/articles/expensify-classic/integrations/travel-integrations/Uber.md index 52b46a631367..c4f2ed78f2cd 100644 --- a/docs/articles/expensify-classic/integrations/travel-integrations/Uber.md +++ b/docs/articles/expensify-classic/integrations/travel-integrations/Uber.md @@ -1,6 +1,6 @@ --- title: Uber integration -Connecting your Uber account to Expensify +description: Connecting your Uber account to Expensify --- ## Overview From 5bbde269e9a4d8a16c67558bc5dcde8cea007f05 Mon Sep 17 00:00:00 2001 From: miljakljajic Date: Wed, 4 Oct 2023 10:11:44 +0200 Subject: [PATCH 005/844] Update Uber.md Made the suggested updates to the accessibility description --- .../integrations/travel-integrations/Uber.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/articles/expensify-classic/integrations/travel-integrations/Uber.md b/docs/articles/expensify-classic/integrations/travel-integrations/Uber.md index c4f2ed78f2cd..73ad2c050cd4 100644 --- a/docs/articles/expensify-classic/integrations/travel-integrations/Uber.md +++ b/docs/articles/expensify-classic/integrations/travel-integrations/Uber.md @@ -20,7 +20,7 @@ Expensify and Uber are now connected! Now, every time you use Uber for Business – be it for rides or meals – the receipt will be imported and scanned into Expensify automatically. -![Alt text for accessibility] +![Uber integration set up steps: Connecting your account] (https://help.expensify.com/assets/images/Uber1.png){:width="100%"} -![Alt text for accessibility] +![Uber integration set up steps: Selecting Expensify] (https://help.expensify.com/assets/images/Uber2.png){:width="100%"} From 197dcc3acf5823e3c8eb470aa4f24e07238b0bca Mon Sep 17 00:00:00 2001 From: tienifr Date: Fri, 6 Oct 2023 20:19:50 +0700 Subject: [PATCH 006/844] fix: 28969 --- src/pages/home/ReportScreen.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index 78f6edcf7dd3..c13858d1ff05 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -39,6 +39,7 @@ import DragAndDropProvider from '../../components/DragAndDrop/Provider'; import usePrevious from '../../hooks/usePrevious'; import CONST from '../../CONST'; import withCurrentReportID, {withCurrentReportIDPropTypes, withCurrentReportIDDefaultProps} from '../../components/withCurrentReportID'; +import useNetwork from '../../hooks/useNetwork'; const propTypes = { /** Navigation route context info provided by react navigation */ @@ -156,6 +157,7 @@ function ReportScreen({ const prevReport = usePrevious(report); const prevUserLeavingStatus = usePrevious(userLeavingStatus); const [isBannerVisible, setIsBannerVisible] = useState(true); + const {isOffline} = useNetwork(); const reportID = getReportID(route); const {addWorkspaceRoomOrChatPendingAction, addWorkspaceRoomOrChatErrors} = ReportUtils.getReportOfflinePendingActionAndErrors(report); @@ -362,8 +364,8 @@ function ReportScreen({ // eslint-disable-next-line rulesdir/no-negated-variables const shouldShowNotFoundPage = useMemo( - () => (!firstRenderRef.current && !report.reportID && !isOptimisticDelete && !reportMetadata.isLoadingReportActions && !isLoading && !userLeavingStatus) || shouldHideReport, - [report, reportMetadata, isLoading, shouldHideReport, isOptimisticDelete, userLeavingStatus], + () => (!firstRenderRef.current && !report.reportID && (isOffline || (!reportMetadata.isLoadingReportActions && !isOptimisticDelete)) && !isLoading && !userLeavingStatus) || shouldHideReport, + [report, reportMetadata, isLoading, shouldHideReport, isOptimisticDelete, userLeavingStatus, isOffline], ); return ( From 9117c61bae77f3cd18e3d7668bbd4e800bc60b42 Mon Sep 17 00:00:00 2001 From: Viktoryia Kliushun Date: Tue, 10 Oct 2023 09:22:16 +0200 Subject: [PATCH 007/844] [TS migration] Migrate 'AppNavigator' lib --- src/ONYXKEYS.ts | 1 + src/SCREENS.ts | 148 ++++++++++++ .../{AuthScreens.js => AuthScreens.tsx} | 123 +++++----- .../AppNavigator/ModalStackNavigators.js | 226 ------------------ .../AppNavigator/ModalStackNavigators.tsx | 226 ++++++++++++++++++ ...eNavigator.js => CentralPaneNavigator.tsx} | 5 +- .../Navigators/{Overlay.js => Overlay.tsx} | 25 +- ...alNavigator.js => RightModalNavigator.tsx} | 18 +- .../{PublicScreens.js => PublicScreens.tsx} | 11 +- ...HPScreenOptions.js => RHPScreenOptions.ts} | 4 +- ...eenIDSetter.js => ReportScreenIDSetter.ts} | 88 +++---- .../AppNavigator/ReportScreenWrapper.js | 43 ---- .../AppNavigator/ReportScreenWrapper.tsx | 25 ++ .../{CustomRouter.js => CustomRouter.ts} | 27 ++- .../createCustomStackNavigator/index.js | 60 ----- .../createCustomStackNavigator/index.tsx | 46 ++++ ...reenOptions.js => defaultScreenOptions.ts} | 4 +- ...ns.js => getRootNavigatorScreenOptions.ts} | 22 +- .../AppNavigator/{index.js => index.tsx} | 8 +- ...lator.js => modalCardStyleInterpolator.ts} | 3 +- src/libs/Navigation/AppNavigator/types.ts | 87 +++++++ src/libs/Navigation/Navigation.js | 2 +- src/libs/compose.ts | 27 +-- src/styles/cardStyles/types.ts | 2 +- src/styles/getModalStyles.ts | 1 + src/types/onyx/Report.ts | 3 + 26 files changed, 714 insertions(+), 521 deletions(-) rename src/libs/Navigation/AppNavigator/{AuthScreens.js => AuthScreens.tsx} (82%) delete mode 100644 src/libs/Navigation/AppNavigator/ModalStackNavigators.js create mode 100644 src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx rename src/libs/Navigation/AppNavigator/Navigators/{CentralPaneNavigator.js => CentralPaneNavigator.tsx} (89%) rename src/libs/Navigation/AppNavigator/Navigators/{Overlay.js => Overlay.tsx} (67%) rename src/libs/Navigation/AppNavigator/Navigators/{RightModalNavigator.js => RightModalNavigator.tsx} (88%) rename src/libs/Navigation/AppNavigator/{PublicScreens.js => PublicScreens.tsx} (84%) rename src/libs/Navigation/AppNavigator/{RHPScreenOptions.js => RHPScreenOptions.ts} (72%) rename src/libs/Navigation/AppNavigator/{ReportScreenIDSetter.js => ReportScreenIDSetter.ts} (54%) delete mode 100644 src/libs/Navigation/AppNavigator/ReportScreenWrapper.js create mode 100644 src/libs/Navigation/AppNavigator/ReportScreenWrapper.tsx rename src/libs/Navigation/AppNavigator/createCustomStackNavigator/{CustomRouter.js => CustomRouter.ts} (60%) delete mode 100644 src/libs/Navigation/AppNavigator/createCustomStackNavigator/index.js create mode 100644 src/libs/Navigation/AppNavigator/createCustomStackNavigator/index.tsx rename src/libs/Navigation/AppNavigator/{defaultScreenOptions.js => defaultScreenOptions.ts} (68%) rename src/libs/Navigation/AppNavigator/{getRootNavigatorScreenOptions.js => getRootNavigatorScreenOptions.ts} (71%) rename src/libs/Navigation/AppNavigator/{index.js => index.tsx} (73%) rename src/libs/Navigation/AppNavigator/{modalCardStyleInterpolator.js => modalCardStyleInterpolator.ts} (70%) create mode 100644 src/libs/Navigation/AppNavigator/types.ts diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 0a17d3a1d2f7..6685dd29a9c7 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -353,6 +353,7 @@ type OnyxValues = { [ONYXKEYS.IS_LOADING_REPORT_DATA]: boolean; [ONYXKEYS.IS_SHORTCUTS_MODAL_OPEN]: boolean; [ONYXKEYS.IS_TEST_TOOLS_MODAL_OPEN]: boolean; + [ONYXKEYS.IS_LOADING_APP]: boolean; [ONYXKEYS.WALLET_TRANSFER]: OnyxTypes.WalletTransfer; [ONYXKEYS.LAST_ACCESSED_WORKSPACE_POLICY_ID]: string; [ONYXKEYS.SHOULD_SHOW_COMPOSE_INPUT]: boolean; diff --git a/src/SCREENS.ts b/src/SCREENS.ts index eb125a43c239..1320d2541d93 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -10,6 +10,7 @@ export default { NOT_FOUND: 'not-found', TRANSITION_BETWEEN_APPS: 'TransitionBetweenApps', VALIDATE_LOGIN: 'ValidateLogin', + UNLINK_LOGIN: 'UnlinkLogin', CONCIERGE: 'Concierge', SETTINGS: { ROOT: 'Settings_Root', @@ -17,11 +18,158 @@ export default { WORKSPACES: 'Settings_Workspaces', SECURITY: 'Settings_Security', STATUS: 'Settings_Status', + PROFILE: 'Settings_Profile', + PRONOUNS: 'Settings_Pronouns', + DISPLAY_NAME: 'Settings_Display_Name', + TIMEZONE: 'Settings_Timezone', + TIMEZONE_SELECT: 'Settings_Timezone_Select', + PERSONAL_DETAILS_INITIAL: 'Settings_PersonalDetails_Initial', + PERSONAL_DETAILS_LEGAL_NAME: 'Settings_PersonalDetails_LegalName', + PERSONAL_DETAILS_DATE_OF_BIRTH: 'Settings_PersonalDetails_DateOfBirth', + PERSONAL_DETAILS_ADDRESS: 'Settings_PersonalDetails_Address', + PERSONAL_DETAILS_ADDRESS_COUNTRY: 'Settings_PersonalDetails_Address_Country', + CONTACT_METHODS: 'Settings_ContactMethods', + CONTACT_METHOD_DETAILS: 'Settings_ContactMethodDetails', + NEW_CONTACT_METHOD: 'Settings_NewContactMethod', + SHARE_CODE: 'Settings_Share_Code', + ABOUT: 'Settings_About', + APP_DOWNLOAD_LINKS: 'Settings_App_Download_Links', + LOUNGE_ACCESS: 'Settings_Lounge_Access', + WALLET: 'Settings_Wallet', + WALLET_DOMAIN_CARDS: 'Settings_Wallet_DomainCards', + WALLET_TRANSFER_BALANCE: 'Settings_Wallet_Transfer_Balance', + WALLET_CHOOSE_TRANSFER_ACCOUNT: 'Settings_Wallet_Choose_Transfer_Account', + WALLET_ENABLE_PAYMENTS: 'Settings_Wallet_EnablePayments', + ADD_DEBIT_CARD: 'Settings_Add_Debit_Card', + ADD_BANK_ACCOUNT: 'Settings_Add_Bank_Account', + PREFERENCES_PRIORITY_MODE: 'Settings_Preferences_PriorityMode', + PREFERENCES_LANGUAGE: 'Settings_Preferences_Language', + CLOSE: 'Settings_Close', + STATUS_SET: 'Settings_Status_Set', + TWO_FACTOR_AUTH: 'Settings_TwoFactorAuth', }, SAVE_THE_WORLD: { ROOT: 'SaveTheWorld_Root', }, + RIGHT_MODAL: { + SETTINGS: 'Settings', + NEW_CHAT: 'NewChat', + SEARCH: 'Search', + DETAILS: 'Details', + PROFILE: 'Profile', + REPORT_DETAILS: 'Report_Details', + REPORT_SETTINGS: 'Report_Settings', + REPORT_WELCOME_MESSAGE: 'Report_WelcomeMessage', + PARTICIPANTS: 'Participants', + MONEY_REQUEST: 'MoneyRequest', + NEW_TASK: 'NewTask', + TEACHERS_UNITE: 'TeachersUnite', + TASK_DETAILS: 'Task_Details', + ENABLE_PAYMENTS: 'EnablePayments', + SPLIT_DETAILS: 'SplitDetails', + ADD_PERSONAL_BANK_ACCOUNT: 'AddPersonalBankAccount', + WALLET_STATEMENT: 'Wallet_Statement', + FLAG_COMMENT: 'Flag_Comment', + EDIT_REQUEST: 'EditRequest', + SIGN_IN: 'SignIn', + PRIVATE_NOTES: 'Private_Notes', + }, SIGN_IN_WITH_APPLE_DESKTOP: 'AppleSignInDesktop', SIGN_IN_WITH_GOOGLE_DESKTOP: 'GoogleSignInDesktop', DESKTOP_SIGN_IN_REDIRECT: 'DesktopSignInRedirect', + + MONEY_REQUEST: { + ROOT: 'Money_Request', + AMOUNT: 'Money_Request_Amount', + PARTICIPANTS: 'Money_Request_Participants', + CONFIRMATION: 'Money_Request_Confirmation', + CURRENCY: 'Money_Request_Currency', + DATE: 'Money_Request_Date', + DESCRIPTION: 'Money_Request_Description', + CATEGORY: 'Money_Request_Category', + TAG: 'Money_Request_Tag', + MERCHANT: 'Money_Request_Merchant', + WAYPOINT: 'Money_Request_Waypoint', + EDIT_WAYPOINT: 'Money_Request_Edit_Waypoint', + DISTANCE: 'Money_Request_Distance', + RECEIPT: 'Money_Request_Receipt', + }, + + IOU_SEND: { + ADD_BANK_ACCOUNT: 'IOU_Send_Add_Bank_Account', + ADD_DEBIT_CARD: 'IOU_Send_Add_Debit_Card', + ENABLE_PAYMENTS: 'IOU_Send_Enable_Payments', + }, + + REPORT_SETTINGS: { + ROOT: 'Report_Settings_Root', + ROOM_NAME: 'Report_Settings_Room_Name', + NOTIFICATION_PREFERENCES: 'Report_Settings_Notification_Preferences', + WRITE_CAPABILITY: 'Report_Settings_Write_Capability', + }, + + NEW_TASK: { + ROOT: 'NewTask_Root', + TASK_ASSIGNEE_SELECTOR: 'NewTask_TaskAssigneeSelector', + TASK_SHARE_DESTINATION_SELECTOR: 'NewTask_TaskShareDestinationSelector', + DETAILS: 'NewTask_Details', + TITLE: 'NewTask_Title', + DESCRIPTION: 'NewTask_Description', + }, + + TASK: { + TITLE: 'Task_Title', + DESCRIPTION: 'Task_Description', + ASSIGNEE: 'Task_Assignee', + }, + + PRIVATE_NOTES: { + VIEW: 'PrivateNotes_View', + LIST: 'PrivateNotes_List', + EDIT: 'PrivateNotes_Edit', + }, + + REPORT_DETAILS: { + ROOT: 'Report_Details_Root', + SHARE_CODE: 'Report_Details_Share_Code', + }, + + WORKSPACE: { + INITIAL: 'Workspace_Initial', + SETTINGS: 'Workspace_Settings', + CARD: 'Workspace_Card', + REIMBURSE: 'Workspace_Reimburse', + RATE_AND_UNIT: 'Workspace_RateAndUnit', + BILLS: 'Workspace_Bills', + INVOICES: 'Workspace_Invoices', + TRAVEL: 'Workspace_Travel', + MEMBERS: 'Workspace_Members', + INVITE: 'Workspace_Invite', + INVITE_MESSAGE: 'Workspace_Invite_Message', + }, + + EDIT_REQUEST: { + ROOT: 'EditRequest_Root', + CURRENCY: 'EditRequest_Currency', + }, + + I_KNOW_A_TEACHER: 'I_Know_A_Teacher', + INTRO_SCHOOL_PRINCIPAL: 'Intro_School_Principal', + I_AM_A_TEACHER: 'I_Am_A_Teacher', + + ENABLE_PAYMENTS_ROOT: 'EnablePayments_Root', + ADD_PERSONAL_BANK_ACCOUNT_ROOT: 'AddPersonalBankAccount_Root', + REIMBURSEMENT_ACCOUNT_ROOT: 'Reimbursement_Account_Root', + WALLET_STATEMENT_ROOT: 'WalletStatement_Root', + SIGN_IN_ROOT: 'SignIn_Root', + SPLIT_DETAILS_ROOT: 'SplitDetails_Root', + DETAILS_ROOT: 'Details_Root', + PROFILE_ROOT: 'Profile_Root', + REPORT_WELCOME_MESSAGE_ROOT: 'Report_WelcomeMessage_Root', + REPORT_PARTICIPANTS_ROOT: 'ReportParticipants_Root', + SEARCH_ROOT: 'Search_Root', + NEW_CHAT_ROOT: 'NewChat_Root', + FLAG_COMMENT_ROOT: 'FlagComment_Root', + REIMBURSEMENT_ACCOUNT: 'ReimbursementAccount', + GET_ASSISTANCE: 'GetAssistance', } as const; diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.js b/src/libs/Navigation/AppNavigator/AuthScreens.tsx similarity index 82% rename from src/libs/Navigation/AppNavigator/AuthScreens.js rename to src/libs/Navigation/AppNavigator/AuthScreens.tsx index 428550a43aa8..5e8f471d4a79 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.js +++ b/src/libs/Navigation/AppNavigator/AuthScreens.tsx @@ -1,10 +1,7 @@ import React from 'react'; -import Onyx, {withOnyx} from 'react-native-onyx'; -import PropTypes from 'prop-types'; -import _ from 'underscore'; -import lodashGet from 'lodash/get'; +import Onyx, {OnyxEntry, withOnyx} from 'react-native-onyx'; import {View} from 'react-native'; -import withWindowDimensions, {windowDimensionsPropTypes} from '../../../components/withWindowDimensions'; +import withWindowDimensions from '../../../components/withWindowDimensions'; import CONST from '../../../CONST'; import compose from '../../compose'; import * as PersonalDetails from '../../actions/PersonalDetails'; @@ -35,16 +32,43 @@ import * as SessionUtils from '../../SessionUtils'; import NotFoundPage from '../../../pages/ErrorPage/NotFoundPage'; import getRootNavigatorScreenOptions from './getRootNavigatorScreenOptions'; import DemoSetupPage from '../../../pages/DemoSetupPage'; +import * as OnyxTypes from '../../../types/onyx'; +import type {WindowDimensions} from '../../../styles/getModalStyles'; +import type {AuthScreensStackParamList} from './types'; -let timezone; -let currentAccountID; -let isLoadingApp; +type AuthScreensOnyxProps = { + /** Session of currently logged in user */ + session: OnyxEntry; + + /** The report ID of the last opened public room as anonymous user */ + lastOpenedPublicRoomID: OnyxEntry; + + /** Opt-in experimental mode that prevents certain Onyx keys from persisting to disk */ + isUsingMemoryOnlyKeys: OnyxEntry; + + /** The last Onyx update ID was applied to the client */ + lastUpdateIDAppliedToClient: OnyxEntry; +}; + +type AuthScreensProps = WindowDimensions & AuthScreensOnyxProps; + +type Timezone = { + automatic?: boolean; + selected?: string; +}; + +type UnsubscribeChatShortcut = () => void; +type UnsubscribeSearchShortcut = () => void; + +let timezone: Timezone | null; +let currentAccountID: number; +let isLoadingApp: boolean; Onyx.connect({ key: ONYXKEYS.SESSION, callback: (val) => { // When signed out, val hasn't accountID - if (!_.has(val, 'accountID')) { + if (!val?.accountID) { timezone = null; return; } @@ -52,7 +76,7 @@ Onyx.connect({ currentAccountID = val.accountID; if (Navigation.isActiveRoute(ROUTES.SIGN_IN_MODAL)) { // This means sign in in RHP was successful, so we can dismiss the modal and subscribe to user events - Navigation.dismissModal(); + Navigation.dismissModal(undefined); User.subscribeToUserEvents(); } }, @@ -65,12 +89,12 @@ Onyx.connect({ return; } - timezone = lodashGet(val, [currentAccountID, 'timezone'], {}); + timezone = val?.[currentAccountID]?.timezone ?? {}; const currentTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone; // If the current timezone is different than the user's timezone, and their timezone is set to automatic // then update their timezone. - if (_.isObject(timezone) && timezone.automatic && timezone.selected !== currentTimezone) { + if (timezone !== null && timezone.automatic && timezone.selected !== currentTimezone) { timezone.selected = currentTimezone; PersonalDetails.updateAutomaticTimezone({ automatic: true, @@ -83,11 +107,11 @@ Onyx.connect({ Onyx.connect({ key: ONYXKEYS.IS_LOADING_APP, callback: (val) => { - isLoadingApp = val; + isLoadingApp = !!val; }, }); -const RootStack = createCustomStackNavigator(); +const RootStack = createCustomStackNavigator(); // We want to delay the re-rendering for components(e.g. ReportActionCompose) // that depends on modal visibility until Modal is completely closed and its focused @@ -102,35 +126,14 @@ const modalScreenListeners = { }, }; -const propTypes = { - /** Session of currently logged in user */ - session: PropTypes.shape({ - email: PropTypes.string.isRequired, - }), - - /** The report ID of the last opened public room as anonymous user */ - lastOpenedPublicRoomID: PropTypes.string, +class AuthScreens extends React.Component { + unsubscribeSearchShortcut?: UnsubscribeSearchShortcut; - /** Opt-in experimental mode that prevents certain Onyx keys from persisting to disk */ - isUsingMemoryOnlyKeys: PropTypes.bool, + unsubscribeChatShortcut?: UnsubscribeChatShortcut; - /** The last Onyx update ID was applied to the client */ - lastUpdateIDAppliedToClient: PropTypes.number, - - ...windowDimensionsPropTypes, -}; + interval?: number; -const defaultProps = { - isUsingMemoryOnlyKeys: false, - session: { - email: null, - }, - lastOpenedPublicRoomID: null, - lastUpdateIDAppliedToClient: null, -}; - -class AuthScreens extends React.Component { - constructor(props) { + constructor(props: AuthScreensProps) { super(props); Timing.start(CONST.TIMING.HOMEPAGE_INITIAL_RENDER); @@ -142,7 +145,7 @@ class AuthScreens extends React.Component { if (isLoadingApp) { App.openApp(); } else { - App.reconnectApp(this.props.lastUpdateIDAppliedToClient); + App.reconnectApp(this.props.lastUpdateIDAppliedToClient ?? 0); } }); PusherConnectionManager.init(); @@ -159,14 +162,16 @@ class AuthScreens extends React.Component { // Note: If a Guide has enabled the memory only key mode then we do want to run OpenApp as their app will not be rehydrated with // the correct state on refresh. They are explicitly opting out of storing data they would need (i.e. reports_) to take advantage of // the optimizations performed during ReconnectApp. - const shouldGetAllData = this.props.isUsingMemoryOnlyKeys || SessionUtils.didUserLogInDuringSession(); + const shouldGetAllData = !!this.props.isUsingMemoryOnlyKeys || SessionUtils.didUserLogInDuringSession(); if (shouldGetAllData) { App.openApp(); } else { - App.reconnectApp(this.props.lastUpdateIDAppliedToClient); + App.reconnectApp(this.props.lastUpdateIDAppliedToClient ?? 0); } - App.setUpPoliciesAndNavigate(this.props.session, !this.props.isSmallScreenWidth); + if (this.props.session) { + App.setUpPoliciesAndNavigate(this.props.session, !this.props.isSmallScreenWidth); + } App.redirectThirdPartyDesktopSignIn(); if (this.props.lastOpenedPublicRoomID) { @@ -193,9 +198,9 @@ class AuthScreens extends React.Component { }); }, searchShortcutConfig.descriptionKey, - searchShortcutConfig.modifiers, + [...searchShortcutConfig.modifiers], true, - ); + ) as UnsubscribeSearchShortcut; this.unsubscribeChatShortcut = KeyboardShortcut.subscribe( chatShortcutConfig.shortcutKey, () => { @@ -207,12 +212,12 @@ class AuthScreens extends React.Component { }); }, chatShortcutConfig.descriptionKey, - chatShortcutConfig.modifiers, + [...chatShortcutConfig.modifiers], true, - ); + ) as UnsubscribeChatShortcut; } - shouldComponentUpdate(nextProps) { + shouldComponentUpdate(nextProps: AuthScreensProps) { return nextProps.windowHeight !== this.props.windowHeight || nextProps.isSmallScreenWidth !== this.props.isSmallScreenWidth; } @@ -225,7 +230,7 @@ class AuthScreens extends React.Component { } Session.cleanupSession(); clearInterval(this.interval); - this.interval = null; + this.interval = undefined; } render() { @@ -235,18 +240,16 @@ class AuthScreens extends React.Component { { - const SidebarScreen = require('../../../pages/home/sidebar/SidebarScreen').default; + const SidebarScreen = require('../../../pages/home/sidebar/SidebarScreen').default as React.ComponentType; return SidebarScreen; }} /> @@ -263,7 +266,7 @@ class AuthScreens extends React.Component { title: 'New Expensify', }} getComponent={() => { - const ValidateLoginPage = require('../../../pages/ValidateLoginPage').default; + const ValidateLoginPage = require('../../../pages/ValidateLoginPage').default as React.ComponentType; return ValidateLoginPage; }} /> @@ -271,7 +274,7 @@ class AuthScreens extends React.Component { name={SCREENS.TRANSITION_BETWEEN_APPS} options={defaultScreenOptions} getComponent={() => { - const LogOutPreviousUserPage = require('../../../pages/LogOutPreviousUserPage').default; + const LogOutPreviousUserPage = require('../../../pages/LogOutPreviousUserPage').default as React.ComponentType; return LogOutPreviousUserPage; }} /> @@ -279,7 +282,7 @@ class AuthScreens extends React.Component { name={SCREENS.CONCIERGE} options={defaultScreenOptions} getComponent={() => { - const ConciergePage = require('../../../pages/ConciergePage').default; + const ConciergePage = require('../../../pages/ConciergePage').default as React.ComponentType; return ConciergePage; }} /> @@ -300,7 +303,7 @@ class AuthScreens extends React.Component { presentation: 'transparentModal', }} getComponent={() => { - const ReportAttachments = require('../../../pages/home/report/ReportAttachments').default; + const ReportAttachments = require('../../../pages/home/report/ReportAttachments').default as React.ComponentType; return ReportAttachments; }} listeners={modalScreenListeners} @@ -327,11 +330,9 @@ class AuthScreens extends React.Component { } } -AuthScreens.propTypes = propTypes; -AuthScreens.defaultProps = defaultProps; export default compose( withWindowDimensions, - withOnyx({ + withOnyx({ session: { key: ONYXKEYS.SESSION, }, diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js deleted file mode 100644 index 6636702592c0..000000000000 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js +++ /dev/null @@ -1,226 +0,0 @@ -import _ from 'underscore'; -import React from 'react'; -import {createStackNavigator, CardStyleInterpolators} from '@react-navigation/stack'; -import styles from '../../../styles/styles'; -import SCREENS from '../../../SCREENS'; - -const defaultSubRouteOptions = { - cardStyle: styles.navigationScreenCardStyle, - headerShown: false, - cardStyleInterpolator: CardStyleInterpolators.forHorizontalIOS, -}; - -/** - * Create a modal stack navigator with an array of sub-screens. - * - * @param {Object} screens key/value pairs where the key is the name of the screen and the value is a functon that returns the lazy-loaded component - * @returns {Function} - */ -function createModalStackNavigator(screens) { - const ModalStackNavigator = createStackNavigator(); - return () => ( - - {_.map(screens, (getComponent, name) => ( - - ))} - - ); -} - -const MoneyRequestModalStackNavigator = createModalStackNavigator({ - Money_Request: () => require('../../../pages/iou/MoneyRequestSelectorPage').default, - Money_Request_Amount: () => require('../../../pages/iou/steps/NewRequestAmountPage').default, - Money_Request_Participants: () => require('../../../pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage').default, - Money_Request_Confirmation: () => require('../../../pages/iou/steps/MoneyRequestConfirmPage').default, - Money_Request_Currency: () => require('../../../pages/iou/IOUCurrencySelection').default, - Money_Request_Date: () => require('../../../pages/iou/MoneyRequestDatePage').default, - Money_Request_Description: () => require('../../../pages/iou/MoneyRequestDescriptionPage').default, - Money_Request_Category: () => require('../../../pages/iou/MoneyRequestCategoryPage').default, - Money_Request_Tag: () => require('../../../pages/iou/MoneyRequestTagPage').default, - Money_Request_Merchant: () => require('../../../pages/iou/MoneyRequestMerchantPage').default, - IOU_Send_Add_Bank_Account: () => require('../../../pages/AddPersonalBankAccountPage').default, - IOU_Send_Add_Debit_Card: () => require('../../../pages/settings/Wallet/AddDebitCardPage').default, - IOU_Send_Enable_Payments: () => require('../../../pages/EnablePayments/EnablePaymentsPage').default, - Money_Request_Waypoint: () => require('../../../pages/iou/NewDistanceRequestWaypointEditorPage').default, - Money_Request_Edit_Waypoint: () => require('../../../pages/iou/MoneyRequestEditWaypointPage').default, - Money_Request_Distance: () => require('../../../pages/iou/NewDistanceRequestPage').default, - Money_Request_Receipt: () => require('../../../pages/EditRequestReceiptPage').default, -}); - -const SplitDetailsModalStackNavigator = createModalStackNavigator({ - SplitDetails_Root: () => require('../../../pages/iou/SplitBillDetailsPage').default, -}); - -const DetailsModalStackNavigator = createModalStackNavigator({ - Details_Root: () => require('../../../pages/DetailsPage').default, -}); - -const ProfileModalStackNavigator = createModalStackNavigator({ - Profile_Root: () => require('../../../pages/ProfilePage').default, -}); - -const ReportDetailsModalStackNavigator = createModalStackNavigator({ - Report_Details_Root: () => require('../../../pages/ReportDetailsPage').default, - Report_Details_Share_Code: () => require('../../../pages/home/report/ReportDetailsShareCodePage').default, -}); - -const ReportSettingsModalStackNavigator = createModalStackNavigator({ - Report_Settings_Root: () => require('../../../pages/settings/Report/ReportSettingsPage').default, - Report_Settings_Room_Name: () => require('../../../pages/settings/Report/RoomNamePage').default, - Report_Settings_Notification_Preferences: () => require('../../../pages/settings/Report/NotificationPreferencePage').default, - Report_Settings_Write_Capability: () => require('../../../pages/settings/Report/WriteCapabilityPage').default, -}); - -const TaskModalStackNavigator = createModalStackNavigator({ - Task_Title: () => require('../../../pages/tasks/TaskTitlePage').default, - Task_Description: () => require('../../../pages/tasks/TaskDescriptionPage').default, - Task_Assignee: () => require('../../../pages/tasks/TaskAssigneeSelectorModal').default, -}); - -const ReportWelcomeMessageModalStackNavigator = createModalStackNavigator({ - Report_WelcomeMessage_Root: () => require('../../../pages/ReportWelcomeMessagePage').default, -}); - -const ReportParticipantsModalStackNavigator = createModalStackNavigator({ - ReportParticipants_Root: () => require('../../../pages/ReportParticipantsPage').default, -}); - -const SearchModalStackNavigator = createModalStackNavigator({ - Search_Root: () => require('../../../pages/SearchPage').default, -}); - -const NewChatModalStackNavigator = createModalStackNavigator({ - NewChat_Root: () => require('../../../pages/NewChatSelectorPage').default, -}); - -const NewTaskModalStackNavigator = createModalStackNavigator({ - NewTask_Root: () => require('../../../pages/tasks/NewTaskPage').default, - NewTask_TaskAssigneeSelector: () => require('../../../pages/tasks/TaskAssigneeSelectorModal').default, - NewTask_TaskShareDestinationSelector: () => require('../../../pages/tasks/TaskShareDestinationSelectorModal').default, - NewTask_Details: () => require('../../../pages/tasks/NewTaskDetailsPage').default, - NewTask_Title: () => require('../../../pages/tasks/NewTaskTitlePage').default, - NewTask_Description: () => require('../../../pages/tasks/NewTaskDescriptionPage').default, -}); - -const NewTeachersUniteNavigator = createModalStackNavigator({ - [SCREENS.SAVE_THE_WORLD.ROOT]: () => require('../../../pages/TeachersUnite/SaveTheWorldPage').default, - I_Know_A_Teacher: () => require('../../../pages/TeachersUnite/KnowATeacherPage').default, - Intro_School_Principal: () => require('../../../pages/TeachersUnite/ImTeacherPage').default, - I_Am_A_Teacher: () => require('../../../pages/TeachersUnite/ImTeacherPage').default, -}); - -const SettingsModalStackNavigator = createModalStackNavigator({ - [SCREENS.SETTINGS.ROOT]: () => require('../../../pages/settings/InitialSettingsPage').default, - Settings_Share_Code: () => require('../../../pages/ShareCodePage').default, - [SCREENS.SETTINGS.WORKSPACES]: () => require('../../../pages/workspace/WorkspacesListPage').default, - Settings_Profile: () => require('../../../pages/settings/Profile/ProfilePage').default, - Settings_Pronouns: () => require('../../../pages/settings/Profile/PronounsPage').default, - Settings_Display_Name: () => require('../../../pages/settings/Profile/DisplayNamePage').default, - Settings_Timezone: () => require('../../../pages/settings/Profile/TimezoneInitialPage').default, - Settings_Timezone_Select: () => require('../../../pages/settings/Profile/TimezoneSelectPage').default, - Settings_PersonalDetails_Initial: () => require('../../../pages/settings/Profile/PersonalDetails/PersonalDetailsInitialPage').default, - Settings_PersonalDetails_LegalName: () => require('../../../pages/settings/Profile/PersonalDetails/LegalNamePage').default, - Settings_PersonalDetails_DateOfBirth: () => require('../../../pages/settings/Profile/PersonalDetails/DateOfBirthPage').default, - Settings_PersonalDetails_Address: () => require('../../../pages/settings/Profile/PersonalDetails/AddressPage').default, - Settings_PersonalDetails_Address_Country: () => require('../../../pages/settings/Profile/PersonalDetails/CountrySelectionPage').default, - Settings_ContactMethods: () => require('../../../pages/settings/Profile/Contacts/ContactMethodsPage').default, - Settings_ContactMethodDetails: () => require('../../../pages/settings/Profile/Contacts/ContactMethodDetailsPage').default, - Settings_NewContactMethod: () => require('../../../pages/settings/Profile/Contacts/NewContactMethodPage').default, - [SCREENS.SETTINGS.PREFERENCES]: () => require('../../../pages/settings/Preferences/PreferencesPage').default, - Settings_Preferences_PriorityMode: () => require('../../../pages/settings/Preferences/PriorityModePage').default, - Settings_Preferences_Language: () => require('../../../pages/settings/Preferences/LanguagePage').default, - // Will be uncommented as part of https://github.com/Expensify/App/issues/21670 - // Settings_Preferences_Theme: () => require('../../../pages/settings/Preferences/ThemePage').default, - Settings_Close: () => require('../../../pages/settings/Security/CloseAccountPage').default, - [SCREENS.SETTINGS.SECURITY]: () => require('../../../pages/settings/Security/SecuritySettingsPage').default, - Settings_About: () => require('../../../pages/settings/AboutPage/AboutPage').default, - Settings_App_Download_Links: () => require('../../../pages/settings/AppDownloadLinks').default, - Settings_Lounge_Access: () => require('../../../pages/settings/Profile/LoungeAccessPage').default, - Settings_Wallet: () => require('../../../pages/settings/Wallet/WalletPage').default, - Settings_Wallet_DomainCards: () => require('../../../pages/settings/Wallet/ExpensifyCardPage').default, - Settings_Wallet_Transfer_Balance: () => require('../../../pages/settings/Wallet/TransferBalancePage').default, - Settings_Wallet_Choose_Transfer_Account: () => require('../../../pages/settings/Wallet/ChooseTransferAccountPage').default, - Settings_Wallet_EnablePayments: () => require('../../../pages/EnablePayments/EnablePaymentsPage').default, - Settings_Add_Debit_Card: () => require('../../../pages/settings/Wallet/AddDebitCardPage').default, - Settings_Add_Bank_Account: () => require('../../../pages/AddPersonalBankAccountPage').default, - [SCREENS.SETTINGS.STATUS]: () => require('../../../pages/settings/Profile/CustomStatus/StatusPage').default, - Settings_Status_Set: () => require('../../../pages/settings/Profile/CustomStatus/StatusSetPage').default, - Workspace_Initial: () => require('../../../pages/workspace/WorkspaceInitialPage').default, - Workspace_Settings: () => require('../../../pages/workspace/WorkspaceSettingsPage').default, - Workspace_Card: () => require('../../../pages/workspace/card/WorkspaceCardPage').default, - Workspace_Reimburse: () => require('../../../pages/workspace/reimburse/WorkspaceReimbursePage').default, - Workspace_RateAndUnit: () => require('../../../pages/workspace/reimburse/WorkspaceRateAndUnitPage').default, - Workspace_Bills: () => require('../../../pages/workspace/bills/WorkspaceBillsPage').default, - Workspace_Invoices: () => require('../../../pages/workspace/invoices/WorkspaceInvoicesPage').default, - Workspace_Travel: () => require('../../../pages/workspace/travel/WorkspaceTravelPage').default, - Workspace_Members: () => require('../../../pages/workspace/WorkspaceMembersPage').default, - Workspace_Invite: () => require('../../../pages/workspace/WorkspaceInvitePage').default, - Workspace_Invite_Message: () => require('../../../pages/workspace/WorkspaceInviteMessagePage').default, - ReimbursementAccount: () => require('../../../pages/ReimbursementAccount/ReimbursementAccountPage').default, - GetAssistance: () => require('../../../pages/GetAssistancePage').default, - Settings_TwoFactorAuth: () => require('../../../pages/settings/Security/TwoFactorAuth/TwoFactorAuthPage').default, -}); - -const EnablePaymentsStackNavigator = createModalStackNavigator({ - EnablePayments_Root: () => require('../../../pages/EnablePayments/EnablePaymentsPage').default, -}); - -const AddPersonalBankAccountModalStackNavigator = createModalStackNavigator({ - AddPersonalBankAccount_Root: () => require('../../../pages/AddPersonalBankAccountPage').default, -}); - -const ReimbursementAccountModalStackNavigator = createModalStackNavigator({ - ReimbursementAccount_Root: () => require('../../../pages/ReimbursementAccount/ReimbursementAccountPage').default, -}); - -const WalletStatementStackNavigator = createModalStackNavigator({ - WalletStatement_Root: () => require('../../../pages/wallet/WalletStatementPage').default, -}); - -const FlagCommentStackNavigator = createModalStackNavigator({ - FlagComment_Root: () => require('../../../pages/FlagCommentPage').default, -}); - -const EditRequestStackNavigator = createModalStackNavigator({ - EditRequest_Root: () => require('../../../pages/EditRequestPage').default, - EditRequest_Currency: () => require('../../../pages/iou/IOUCurrencySelection').default, -}); - -const PrivateNotesModalStackNavigator = createModalStackNavigator({ - PrivateNotes_View: () => require('../../../pages/PrivateNotes/PrivateNotesViewPage').default, - PrivateNotes_List: () => require('../../../pages/PrivateNotes/PrivateNotesListPage').default, - PrivateNotes_Edit: () => require('../../../pages/PrivateNotes/PrivateNotesEditPage').default, -}); - -const SignInModalStackNavigator = createModalStackNavigator({ - SignIn_Root: () => require('../../../pages/signin/SignInModal').default, -}); - -export { - MoneyRequestModalStackNavigator, - SplitDetailsModalStackNavigator, - DetailsModalStackNavigator, - ProfileModalStackNavigator, - ReportDetailsModalStackNavigator, - TaskModalStackNavigator, - ReportSettingsModalStackNavigator, - ReportWelcomeMessageModalStackNavigator, - ReportParticipantsModalStackNavigator, - SearchModalStackNavigator, - NewChatModalStackNavigator, - NewTaskModalStackNavigator, - SettingsModalStackNavigator, - EnablePaymentsStackNavigator, - AddPersonalBankAccountModalStackNavigator, - ReimbursementAccountModalStackNavigator, - WalletStatementStackNavigator, - FlagCommentStackNavigator, - EditRequestStackNavigator, - PrivateNotesModalStackNavigator, - NewTeachersUniteNavigator, - SignInModalStackNavigator, -}; diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx new file mode 100644 index 000000000000..9e8cf9680e73 --- /dev/null +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx @@ -0,0 +1,226 @@ +import React from 'react'; +import {createStackNavigator, CardStyleInterpolators} from '@react-navigation/stack'; +import styles from '../../../styles/styles'; +import SCREENS from '../../../SCREENS'; + +const defaultSubRouteOptions = { + cardStyle: styles.navigationScreenCardStyle, + headerShown: false, + cardStyleInterpolator: CardStyleInterpolators.forHorizontalIOS, +}; + +type Screens = Record React.ComponentType>; + +/** + * Create a modal stack navigator with an array of sub-screens. + * + * @param screens key/value pairs where the key is the name of the screen and the value is a functon that returns the lazy-loaded component + */ +function createModalStackNavigator(screens: Screens): () => React.JSX.Element { + const ModalStackNavigator = createStackNavigator(); + return () => ( + + {Object.keys(screens).map((name) => ( + + ))} + + ); +} + +const MoneyRequestModalStackNavigator = createModalStackNavigator({ + [SCREENS.MONEY_REQUEST.ROOT]: () => require('../../../pages/iou/MoneyRequestSelectorPage').default as React.ComponentType, + [SCREENS.MONEY_REQUEST.AMOUNT]: () => require('../../../pages/iou/steps/NewRequestAmountPage').default as React.ComponentType, + [SCREENS.MONEY_REQUEST.PARTICIPANTS]: () => require('../../../pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage').default as React.ComponentType, + [SCREENS.MONEY_REQUEST.CONFIRMATION]: () => require('../../../pages/iou/steps/MoneyRequestConfirmPage').default as React.ComponentType, + [SCREENS.MONEY_REQUEST.CURRENCY]: () => require('../../../pages/iou/IOUCurrencySelection').default as React.ComponentType, + [SCREENS.MONEY_REQUEST.DATE]: () => require('../../../pages/iou/MoneyRequestDatePage').default as React.ComponentType, + [SCREENS.MONEY_REQUEST.DESCRIPTION]: () => require('../../../pages/iou/MoneyRequestDescriptionPage').default as React.ComponentType, + [SCREENS.MONEY_REQUEST.CATEGORY]: () => require('../../../pages/iou/MoneyRequestCategoryPage').default as React.ComponentType, + [SCREENS.MONEY_REQUEST.TAG]: () => require('../../../pages/iou/MoneyRequestTagPage').default as React.ComponentType, + [SCREENS.MONEY_REQUEST.MERCHANT]: () => require('../../../pages/iou/MoneyRequestMerchantPage').default as React.ComponentType, + [SCREENS.IOU_SEND.ADD_BANK_ACCOUNT]: () => require('../../../pages/AddPersonalBankAccountPage').default as React.ComponentType, + [SCREENS.IOU_SEND.ADD_DEBIT_CARD]: () => require('../../../pages/settings/Wallet/AddDebitCardPage').default as React.ComponentType, + [SCREENS.IOU_SEND.ENABLE_PAYMENTS]: () => require('../../../pages/EnablePayments/EnablePaymentsPage').default as React.ComponentType, + [SCREENS.MONEY_REQUEST.WAYPOINT]: () => require('../../../pages/iou/NewDistanceRequestWaypointEditorPage').default as React.ComponentType, + [SCREENS.MONEY_REQUEST.EDIT_WAYPOINT]: () => require('../../../pages/iou/MoneyRequestEditWaypointPage').default as React.ComponentType, + [SCREENS.MONEY_REQUEST.DISTANCE]: () => require('../../../pages/iou/NewDistanceRequestPage').default as React.ComponentType, + [SCREENS.MONEY_REQUEST.RECEIPT]: () => require('../../../pages/EditRequestReceiptPage').default as React.ComponentType, +}); + +const SplitDetailsModalStackNavigator = createModalStackNavigator({ + [SCREENS.SPLIT_DETAILS_ROOT]: () => require('../../../pages/iou/SplitBillDetailsPage').default as React.ComponentType, +}); + +const DetailsModalStackNavigator = createModalStackNavigator({ + [SCREENS.DETAILS_ROOT]: () => require('../../../pages/DetailsPage').default as React.ComponentType, +}); + +const ProfileModalStackNavigator = createModalStackNavigator({ + [SCREENS.PROFILE_ROOT]: () => require('../../../pages/ProfilePage').default as React.ComponentType, +}); + +const ReportDetailsModalStackNavigator = createModalStackNavigator({ + [SCREENS.REPORT_DETAILS.ROOT]: () => require('../../../pages/ReportDetailsPage').default as React.ComponentType, + [SCREENS.REPORT_DETAILS.SHARE_CODE]: () => require('../../../pages/home/report/ReportDetailsShareCodePage').default as React.ComponentType, +}); + +const ReportSettingsModalStackNavigator = createModalStackNavigator({ + [SCREENS.REPORT_SETTINGS.ROOT]: () => require('../../../pages/settings/Report/ReportSettingsPage').default as React.ComponentType, + [SCREENS.REPORT_SETTINGS.ROOM_NAME]: () => require('../../../pages/settings/Report/RoomNamePage').default as React.ComponentType, + [SCREENS.REPORT_SETTINGS.NOTIFICATION_PREFERENCES]: () => require('../../../pages/settings/Report/NotificationPreferencePage').default as React.ComponentType, + [SCREENS.REPORT_SETTINGS.WRITE_CAPABILITY]: () => require('../../../pages/settings/Report/WriteCapabilityPage').default as React.ComponentType, +}); + +const TaskModalStackNavigator = createModalStackNavigator({ + [SCREENS.TASK.TITLE]: () => require('../../../pages/tasks/TaskTitlePage').default as React.ComponentType, + [SCREENS.TASK.DESCRIPTION]: () => require('../../../pages/tasks/TaskDescriptionPage').default as React.ComponentType, + [SCREENS.TASK.ASSIGNEE]: () => require('../../../pages/tasks/TaskAssigneeSelectorModal').default as React.ComponentType, +}); + +const ReportWelcomeMessageModalStackNavigator = createModalStackNavigator({ + [SCREENS.REPORT_WELCOME_MESSAGE_ROOT]: () => require('../../../pages/ReportWelcomeMessagePage').default as React.ComponentType, +}); + +const ReportParticipantsModalStackNavigator = createModalStackNavigator({ + [SCREENS.REPORT_PARTICIPANTS_ROOT]: () => require('../../../pages/ReportParticipantsPage').default as React.ComponentType, +}); + +const SearchModalStackNavigator = createModalStackNavigator({ + [SCREENS.SEARCH_ROOT]: () => require('../../../pages/SearchPage').default as React.ComponentType, +}); + +const NewChatModalStackNavigator = createModalStackNavigator({ + [SCREENS.NEW_CHAT_ROOT]: () => require('../../../pages/NewChatSelectorPage').default as React.ComponentType, +}); + +const NewTaskModalStackNavigator = createModalStackNavigator({ + [SCREENS.NEW_TASK.ROOT]: () => require('../../../pages/tasks/NewTaskPage').default as React.ComponentType, + [SCREENS.NEW_TASK.TASK_ASSIGNEE_SELECTOR]: () => require('../../../pages/tasks/TaskAssigneeSelectorModal').default as React.ComponentType, + [SCREENS.NEW_TASK.TASK_SHARE_DESTINATION_SELECTOR]: () => require('../../../pages/tasks/TaskShareDestinationSelectorModal').default as React.ComponentType, + [SCREENS.NEW_TASK.DETAILS]: () => require('../../../pages/tasks/NewTaskDetailsPage').default as React.ComponentType, + [SCREENS.NEW_TASK.TITLE]: () => require('../../../pages/tasks/NewTaskTitlePage').default as React.ComponentType, + [SCREENS.NEW_TASK.DESCRIPTION]: () => require('../../../pages/tasks/NewTaskDescriptionPage').default as React.ComponentType, +}); + +const NewTeachersUniteNavigator = createModalStackNavigator({ + [SCREENS.SAVE_THE_WORLD.ROOT]: () => require('../../../pages/TeachersUnite/SaveTheWorldPage').default as React.ComponentType, + [SCREENS.I_KNOW_A_TEACHER]: () => require('../../../pages/TeachersUnite/KnowATeacherPage').default as React.ComponentType, + [SCREENS.INTRO_SCHOOL_PRINCIPAL]: () => require('../../../pages/TeachersUnite/ImTeacherPage').default as React.ComponentType, + [SCREENS.I_AM_A_TEACHER]: () => require('../../../pages/TeachersUnite/ImTeacherPage').default as React.ComponentType, +}); + +const SettingsModalStackNavigator = createModalStackNavigator({ + [SCREENS.SETTINGS.ROOT]: () => require('../../../pages/settings/InitialSettingsPage').default as React.ComponentType, + [SCREENS.SETTINGS.SHARE_CODE]: () => require('../../../pages/ShareCodePage').default as React.ComponentType, + [SCREENS.SETTINGS.WORKSPACES]: () => require('../../../pages/workspace/WorkspacesListPage').default as React.ComponentType, + [SCREENS.SETTINGS.PROFILE]: () => require('../../../pages/settings/Profile/ProfilePage').default as React.ComponentType, + [SCREENS.SETTINGS.PRONOUNS]: () => require('../../../pages/settings/Profile/PronounsPage').default as React.ComponentType, + [SCREENS.SETTINGS.DISPLAY_NAME]: () => require('../../../pages/settings/Profile/DisplayNamePage').default as React.ComponentType, + [SCREENS.SETTINGS.TIMEZONE]: () => require('../../../pages/settings/Profile/TimezoneInitialPage').default as React.ComponentType, + [SCREENS.SETTINGS.TIMEZONE_SELECT]: () => require('../../../pages/settings/Profile/TimezoneSelectPage').default as React.ComponentType, + [SCREENS.SETTINGS.PERSONAL_DETAILS_INITIAL]: () => require('../../../pages/settings/Profile/PersonalDetails/PersonalDetailsInitialPage').default as React.ComponentType, + [SCREENS.SETTINGS.PERSONAL_DETAILS_LEGAL_NAME]: () => require('../../../pages/settings/Profile/PersonalDetails/LegalNamePage').default as React.ComponentType, + [SCREENS.SETTINGS.PERSONAL_DETAILS_DATE_OF_BIRTH]: () => require('../../../pages/settings/Profile/PersonalDetails/DateOfBirthPage').default as React.ComponentType, + [SCREENS.SETTINGS.PERSONAL_DETAILS_ADDRESS]: () => require('../../../pages/settings/Profile/PersonalDetails/AddressPage').default as React.ComponentType, + [SCREENS.SETTINGS.PERSONAL_DETAILS_ADDRESS_COUNTRY]: () => require('../../../pages/settings/Profile/PersonalDetails/CountrySelectionPage').default as React.ComponentType, + [SCREENS.SETTINGS.CONTACT_METHODS]: () => require('../../../pages/settings/Profile/Contacts/ContactMethodsPage').default as React.ComponentType, + [SCREENS.SETTINGS.CONTACT_METHOD_DETAILS]: () => require('../../../pages/settings/Profile/Contacts/ContactMethodDetailsPage').default as React.ComponentType, + [SCREENS.SETTINGS.NEW_CONTACT_METHOD]: () => require('../../../pages/settings/Profile/Contacts/NewContactMethodPage').default as React.ComponentType, + [SCREENS.SETTINGS.PREFERENCES]: () => require('../../../pages/settings/Preferences/PreferencesPage').default as React.ComponentType, + [SCREENS.SETTINGS.PREFERENCES_PRIORITY_MODE]: () => require('../../../pages/settings/Preferences/PriorityModePage').default as React.ComponentType, + [SCREENS.SETTINGS.PREFERENCES_LANGUAGE]: () => require('../../../pages/settings/Preferences/LanguagePage').default as React.ComponentType, + // Will be uncommented as part of https://github.com/Expensify/App/issues/21670 + // Settings_Preferences_Theme: () => require('../../../pages/settings/Preferences/ThemePage').default as React.ComponentType, + [SCREENS.SETTINGS.CLOSE]: () => require('../../../pages/settings/Security/CloseAccountPage').default as React.ComponentType, + [SCREENS.SETTINGS.SECURITY]: () => require('../../../pages/settings/Security/SecuritySettingsPage').default as React.ComponentType, + [SCREENS.SETTINGS.ABOUT]: () => require('../../../pages/settings/AboutPage/AboutPage').default as React.ComponentType, + [SCREENS.SETTINGS.APP_DOWNLOAD_LINKS]: () => require('../../../pages/settings/AppDownloadLinks').default as React.ComponentType, + [SCREENS.SETTINGS.LOUNGE_ACCESS]: () => require('../../../pages/settings/Profile/LoungeAccessPage').default as React.ComponentType, + [SCREENS.SETTINGS.WALLET]: () => require('../../../pages/settings/Wallet/WalletPage').default as React.ComponentType, + [SCREENS.SETTINGS.WALLET_DOMAIN_CARDS]: () => require('../../../pages/settings/Wallet/ExpensifyCardPage').default as React.ComponentType, + [SCREENS.SETTINGS.WALLET_TRANSFER_BALANCE]: () => require('../../../pages/settings/Wallet/TransferBalancePage').default as React.ComponentType, + [SCREENS.SETTINGS.WALLET_CHOOSE_TRANSFER_ACCOUNT]: () => require('../../../pages/settings/Wallet/ChooseTransferAccountPage').default as React.ComponentType, + [SCREENS.SETTINGS.WALLET_ENABLE_PAYMENTS]: () => require('../../../pages/EnablePayments/EnablePaymentsPage').default as React.ComponentType, + [SCREENS.SETTINGS.ADD_DEBIT_CARD]: () => require('../../../pages/settings/Wallet/AddDebitCardPage').default as React.ComponentType, + [SCREENS.SETTINGS.ADD_BANK_ACCOUNT]: () => require('../../../pages/AddPersonalBankAccountPage').default as React.ComponentType, + [SCREENS.SETTINGS.STATUS]: () => require('../../../pages/settings/Profile/CustomStatus/StatusPage').default as React.ComponentType, + [SCREENS.SETTINGS.STATUS_SET]: () => require('../../../pages/settings/Profile/CustomStatus/StatusSetPage').default as React.ComponentType, + [SCREENS.WORKSPACE.INITIAL]: () => require('../../../pages/workspace/WorkspaceInitialPage').default as React.ComponentType, + [SCREENS.WORKSPACE.SETTINGS]: () => require('../../../pages/workspace/WorkspaceSettingsPage').default as React.ComponentType, + [SCREENS.WORKSPACE.CARD]: () => require('../../../pages/workspace/card/WorkspaceCardPage').default as React.ComponentType, + [SCREENS.WORKSPACE.REIMBURSE]: () => require('../../../pages/workspace/reimburse/WorkspaceReimbursePage').default as React.ComponentType, + [SCREENS.WORKSPACE.RATE_AND_UNIT]: () => require('../../../pages/workspace/reimburse/WorkspaceRateAndUnitPage').default as React.ComponentType, + [SCREENS.WORKSPACE.BILLS]: () => require('../../../pages/workspace/bills/WorkspaceBillsPage').default as React.ComponentType, + [SCREENS.WORKSPACE.INVOICES]: () => require('../../../pages/workspace/invoices/WorkspaceInvoicesPage').default as React.ComponentType, + [SCREENS.WORKSPACE.TRAVEL]: () => require('../../../pages/workspace/travel/WorkspaceTravelPage').default as React.ComponentType, + [SCREENS.WORKSPACE.MEMBERS]: () => require('../../../pages/workspace/WorkspaceMembersPage').default as React.ComponentType, + [SCREENS.WORKSPACE.INVITE]: () => require('../../../pages/workspace/WorkspaceInvitePage').default as React.ComponentType, + [SCREENS.WORKSPACE.INVITE_MESSAGE]: () => require('../../../pages/workspace/WorkspaceInviteMessagePage').default as React.ComponentType, + [SCREENS.REIMBURSEMENT_ACCOUNT]: () => require('../../../pages/ReimbursementAccount/ReimbursementAccountPage').default as React.ComponentType, + [SCREENS.GET_ASSISTANCE]: () => require('../../../pages/GetAssistancePage').default as React.ComponentType, + [SCREENS.SETTINGS.TWO_FACTOR_AUTH]: () => require('../../../pages/settings/Security/TwoFactorAuth/TwoFactorAuthPage').default as React.ComponentType, +}); + +const EnablePaymentsStackNavigator = createModalStackNavigator({ + [SCREENS.ENABLE_PAYMENTS_ROOT]: () => require('../../../pages/EnablePayments/EnablePaymentsPage').default as React.ComponentType, +}); + +const AddPersonalBankAccountModalStackNavigator = createModalStackNavigator({ + [SCREENS.ADD_PERSONAL_BANK_ACCOUNT_ROOT]: () => require('../../../pages/AddPersonalBankAccountPage').default as React.ComponentType, +}); + +const ReimbursementAccountModalStackNavigator = createModalStackNavigator({ + [SCREENS.REIMBURSEMENT_ACCOUNT_ROOT]: () => require('../../../pages/ReimbursementAccount/ReimbursementAccountPage').default as React.ComponentType, +}); + +const WalletStatementStackNavigator = createModalStackNavigator({ + [SCREENS.WALLET_STATEMENT_ROOT]: () => require('../../../pages/wallet/WalletStatementPage').default as React.ComponentType, +}); + +const FlagCommentStackNavigator = createModalStackNavigator({ + [SCREENS.FLAG_COMMENT_ROOT]: () => require('../../../pages/FlagCommentPage').default as React.ComponentType, +}); + +const EditRequestStackNavigator = createModalStackNavigator({ + [SCREENS.EDIT_REQUEST.ROOT]: () => require('../../../pages/EditRequestPage').default as React.ComponentType, + [SCREENS.EDIT_REQUEST.CURRENCY]: () => require('../../../pages/iou/IOUCurrencySelection').default as React.ComponentType, +}); + +const PrivateNotesModalStackNavigator = createModalStackNavigator({ + [SCREENS.PRIVATE_NOTES.VIEW]: () => require('../../../pages/PrivateNotes/PrivateNotesViewPage').default as React.ComponentType, + [SCREENS.PRIVATE_NOTES.LIST]: () => require('../../../pages/PrivateNotes/PrivateNotesListPage').default as React.ComponentType, + [SCREENS.PRIVATE_NOTES.EDIT]: () => require('../../../pages/PrivateNotes/PrivateNotesEditPage').default as React.ComponentType, +}); + +const SignInModalStackNavigator = createModalStackNavigator({ + [SCREENS.SIGN_IN_ROOT]: () => require('../../../pages/signin/SignInModal').default as React.ComponentType, +}); + +export { + MoneyRequestModalStackNavigator, + SplitDetailsModalStackNavigator, + DetailsModalStackNavigator, + ProfileModalStackNavigator, + ReportDetailsModalStackNavigator, + TaskModalStackNavigator, + ReportSettingsModalStackNavigator, + ReportWelcomeMessageModalStackNavigator, + ReportParticipantsModalStackNavigator, + SearchModalStackNavigator, + NewChatModalStackNavigator, + NewTaskModalStackNavigator, + SettingsModalStackNavigator, + EnablePaymentsStackNavigator, + AddPersonalBankAccountModalStackNavigator, + ReimbursementAccountModalStackNavigator, + WalletStatementStackNavigator, + FlagCommentStackNavigator, + EditRequestStackNavigator, + PrivateNotesModalStackNavigator, + NewTeachersUniteNavigator, + SignInModalStackNavigator, +}; diff --git a/src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator.js b/src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator.tsx similarity index 89% rename from src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator.js rename to src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator.tsx index 64eadcbe06c3..49c880bb2252 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator.js +++ b/src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator.tsx @@ -5,8 +5,9 @@ import ReportScreenWrapper from '../ReportScreenWrapper'; import getCurrentUrl from '../../currentUrl'; import styles from '../../../../styles/styles'; import FreezeWrapper from '../../FreezeWrapper'; +import type {CentralPaneStackParamList} from '../types'; -const Stack = createStackNavigator(); +const Stack = createStackNavigator(); const url = getCurrentUrl(); const openOnAdminRoom = url ? new URL(url).searchParams.get('openOnAdminRoom') : undefined; @@ -18,7 +19,7 @@ function CentralPaneNavigator() { unknown; }; -function Overlay(props) { +function Overlay(props: OverlayProps) { const {current} = useCardAnimation(); - const {translate} = useLocalize(); + // TODO: remove type assertion when useLocalize is migrated + const {translate} = useLocalize() as unknown as {translate: (phrase: string) => string}; return ( - {/* In the latest Electron version buttons can't be both clickable and draggable. - That's why we added this workaround. Because of two Pressable components on the desktop app + {/* In the latest Electron version buttons can't be both clickable and draggable. + That's why we added this workaround. Because of two Pressable components on the desktop app we have 30px draggable ba at the top and the rest of the dimmed area is clickable. On other devices, everything behaves normally like one big pressable */} (); -const propTypes = { - ...withNavigationPropTypes, -}; - -function RightModalNavigator(props) { - const {isSmallScreenWidth} = useWindowDimensions(); +function RightModalNavigator(props: StackScreenProps) { + // TODO: remove type assertion when useWindowDimensions is migrated to TS + const {isSmallScreenWidth} = useWindowDimensions() as WindowDimensions; return ( @@ -118,7 +117,6 @@ function RightModalNavigator(props) { ); } -RightModalNavigator.propTypes = propTypes; RightModalNavigator.displayName = 'RightModalNavigator'; export default RightModalNavigator; diff --git a/src/libs/Navigation/AppNavigator/PublicScreens.js b/src/libs/Navigation/AppNavigator/PublicScreens.tsx similarity index 84% rename from src/libs/Navigation/AppNavigator/PublicScreens.js rename to src/libs/Navigation/AppNavigator/PublicScreens.tsx index 7a87530a2d9e..fa3c9dc8d806 100644 --- a/src/libs/Navigation/AppNavigator/PublicScreens.js +++ b/src/libs/Navigation/AppNavigator/PublicScreens.tsx @@ -8,8 +8,9 @@ import defaultScreenOptions from './defaultScreenOptions'; import UnlinkLoginPage from '../../../pages/UnlinkLoginPage'; import AppleSignInDesktopPage from '../../../pages/signin/AppleSignInDesktopPage'; import GoogleSignInDesktopPage from '../../../pages/signin/GoogleSignInDesktopPage'; +import {PublicScreensStackParamList} from './types'; -const RootStack = createStackNavigator(); +const RootStack = createStackNavigator(); function PublicScreens() { return ( @@ -25,22 +26,22 @@ function PublicScreens() { component={LogInWithShortLivedAuthTokenPage} /> diff --git a/src/libs/Navigation/AppNavigator/RHPScreenOptions.js b/src/libs/Navigation/AppNavigator/RHPScreenOptions.ts similarity index 72% rename from src/libs/Navigation/AppNavigator/RHPScreenOptions.js rename to src/libs/Navigation/AppNavigator/RHPScreenOptions.ts index d7448dcf2314..b21f29fc466e 100644 --- a/src/libs/Navigation/AppNavigator/RHPScreenOptions.js +++ b/src/libs/Navigation/AppNavigator/RHPScreenOptions.ts @@ -1,4 +1,4 @@ -import {CardStyleInterpolators} from '@react-navigation/stack'; +import {CardStyleInterpolators, StackNavigationOptions} from '@react-navigation/stack'; import styles from '../../../styles/styles'; const RHPScreenOptions = { @@ -7,6 +7,6 @@ const RHPScreenOptions = { gestureDirection: 'horizontal', cardStyle: styles.navigationScreenCardStyle, cardStyleInterpolator: CardStyleInterpolators.forHorizontalIOS, -}; +} as StackNavigationOptions; export default RHPScreenOptions; diff --git a/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.js b/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts similarity index 54% rename from src/libs/Navigation/AppNavigator/ReportScreenIDSetter.js rename to src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts index 24f855645870..de725a59f42c 100644 --- a/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.js +++ b/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts @@ -1,91 +1,66 @@ import {useEffect} from 'react'; -import PropTypes from 'prop-types'; -import lodashGet from 'lodash/get'; -import {withOnyx} from 'react-native-onyx'; +import {OnyxCollection, OnyxEntry, withOnyx} from 'react-native-onyx'; import ONYXKEYS from '../../../ONYXKEYS'; import * as ReportUtils from '../../ReportUtils'; -import reportPropTypes from '../../../pages/reportPropTypes'; -import {withNavigationPropTypes} from '../../../components/withNavigation'; import * as App from '../../actions/App'; import usePermissions from '../../../hooks/usePermissions'; import CONST from '../../../CONST'; import Navigation from '../Navigation'; +import {Policy, Report} from '../../../types/onyx'; +import {ReportScreenWrapperProps} from './types'; -const propTypes = { +type ReportScreenIDSetterComponentProps = { /** Available reports that would be displayed in this navigator */ - reports: PropTypes.objectOf(reportPropTypes), + reports: OnyxCollection; /** The policies which the user has access to */ - policies: PropTypes.objectOf( - PropTypes.shape({ - /** The policy name */ - name: PropTypes.string, + policies: OnyxCollection; - /** The type of the policy */ - type: PropTypes.string, - }), - ), - - isFirstTimeNewExpensifyUser: PropTypes.bool, - - /** Navigation route context info provided by react navigation */ - route: PropTypes.shape({ - /** Route specific parameters used on this screen */ - params: PropTypes.shape({ - /** If the admin room should be opened */ - openOnAdminRoom: PropTypes.bool, - - /** The ID of the report this screen should display */ - reportID: PropTypes.string, - }), - }).isRequired, - - ...withNavigationPropTypes, + isFirstTimeNewExpensifyUser: OnyxEntry; }; -const defaultProps = { - reports: {}, - policies: {}, - isFirstTimeNewExpensifyUser: false, -}; +type ReportScreenIDSetterProps = ReportScreenIDSetterComponentProps & ReportScreenWrapperProps; /** * Get the most recently accessed report for the user - * - * @param {Object} reports - * @param {Boolean} ignoreDefaultRooms - * @param {Object} policies - * @param {Boolean} isFirstTimeNewExpensifyUser - * @param {Boolean} openOnAdminRoom - * @returns {Number} */ -const getLastAccessedReportID = (reports, ignoreDefaultRooms, policies, isFirstTimeNewExpensifyUser, openOnAdminRoom) => { +const getLastAccessedReportID = ( + reports: OnyxCollection | Report[], + ignoreDefaultRooms: boolean, + policies: OnyxCollection, + isFirstTimeNewExpensifyUser: OnyxEntry, + openOnAdminRoom: boolean, +): number | string => { // If deeplink url is of an attachment, we should show the report that the attachment comes from. const currentRoute = Navigation.getActiveRoute(); const matches = CONST.REGEX.ATTACHMENT_ROUTE.exec(currentRoute); - const reportID = lodashGet(matches, 1, null); + const reportID = matches?.[1] ?? null; if (reportID) { return reportID; } - const lastReport = ReportUtils.findLastAccessedReport(reports, ignoreDefaultRooms, policies, isFirstTimeNewExpensifyUser, openOnAdminRoom); + // TODO: get rid of ignore when ReportUtils is migrated to TS + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore till ReportUtils file is migrated + const lastReport = ReportUtils.findLastAccessedReport(reports, ignoreDefaultRooms, policies, isFirstTimeNewExpensifyUser, openOnAdminRoom) as Report; - return lodashGet(lastReport, 'reportID'); + return lastReport?.reportID; }; // This wrapper is reponsible for opening the last accessed report if there is no reportID specified in the route params -function ReportScreenIDSetter({route, reports, policies, isFirstTimeNewExpensifyUser, navigation}) { - const {canUseDefaultRooms} = usePermissions(); +function ReportScreenIDSetter({route, reports, policies, isFirstTimeNewExpensifyUser, navigation}: ReportScreenIDSetterProps): null { + // TODO: remove type assertion when usePermissions is migrated + const {canUseDefaultRooms} = usePermissions() as {canUseDefaultRooms: boolean}; useEffect(() => { // Don't update if there is a reportID in the params already - if (lodashGet(route, 'params.reportID', null)) { + if (route?.params?.reportID ?? null) { App.confirmReadyToOpenApp(); return; } // If there is no reportID in route, try to find last accessed and use it for setParams - const reportID = getLastAccessedReportID(reports, !canUseDefaultRooms, policies, isFirstTimeNewExpensifyUser, lodashGet(route, 'params.openOnAdminRoom', false)); + const reportID = getLastAccessedReportID(reports, !canUseDefaultRooms, policies, isFirstTimeNewExpensifyUser, reports?.params?.openOnAdminRoom ?? false); // It's possible that reports aren't fully loaded yet // in that case the reportID is undefined @@ -101,21 +76,26 @@ function ReportScreenIDSetter({route, reports, policies, isFirstTimeNewExpensify return null; } -ReportScreenIDSetter.propTypes = propTypes; -ReportScreenIDSetter.defaultProps = defaultProps; ReportScreenIDSetter.displayName = 'ReportScreenIDSetter'; -export default withOnyx({ +export default withOnyx({ reports: { key: ONYXKEYS.COLLECTION.REPORT, + // TODO: I think we need to update onyx mapping types to include allowStaleData/initialValue keys + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore allowStaleData: true, }, policies: { key: ONYXKEYS.COLLECTION.POLICY, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore allowStaleData: true, }, isFirstTimeNewExpensifyUser: { key: ONYXKEYS.NVP_IS_FIRST_TIME_NEW_EXPENSIFY_USER, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore initialValue: false, }, })(ReportScreenIDSetter); diff --git a/src/libs/Navigation/AppNavigator/ReportScreenWrapper.js b/src/libs/Navigation/AppNavigator/ReportScreenWrapper.js deleted file mode 100644 index 767bd9793ac2..000000000000 --- a/src/libs/Navigation/AppNavigator/ReportScreenWrapper.js +++ /dev/null @@ -1,43 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import {withNavigationPropTypes} from '../../../components/withNavigation'; -import ReportScreen from '../../../pages/home/ReportScreen'; -import ReportScreenIDSetter from './ReportScreenIDSetter'; - -const propTypes = { - /** Navigation route context info provided by react navigation */ - route: PropTypes.shape({ - /** Route specific parameters used on this screen */ - params: PropTypes.shape({ - /** If the admin room should be opened */ - openOnAdminRoom: PropTypes.bool, - - /** The ID of the report this screen should display */ - reportID: PropTypes.string, - }), - }).isRequired, - - ...withNavigationPropTypes, -}; - -const defaultProps = {}; - -function ReportScreenWrapper(props) { - // The ReportScreen without the reportID set will display a skeleton - // until the reportID is loaded and set in the route param - return ( - <> - - - - ); -} - -ReportScreenWrapper.propTypes = propTypes; -ReportScreenWrapper.defaultProps = defaultProps; -ReportScreenWrapper.displayName = 'ReportScreenWrapper'; - -export default ReportScreenWrapper; diff --git a/src/libs/Navigation/AppNavigator/ReportScreenWrapper.tsx b/src/libs/Navigation/AppNavigator/ReportScreenWrapper.tsx new file mode 100644 index 000000000000..2c17bf4b2ed0 --- /dev/null +++ b/src/libs/Navigation/AppNavigator/ReportScreenWrapper.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import ReportScreen from '../../../pages/home/ReportScreen'; +import ReportScreenIDSetter from './ReportScreenIDSetter'; +import {ReportScreenWrapperProps} from './types'; + +function ReportScreenWrapper(props: ReportScreenWrapperProps) { + // The ReportScreen without the reportID set will display a skeleton + // until the reportID is loaded and set in the route param + return ( + <> + {/* TODO: Remove when ReportScreen is migrated */} + {/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */} + {/* @ts-ignore */} + + + + ); +} + +ReportScreenWrapper.displayName = 'ReportScreenWrapper'; + +export default ReportScreenWrapper; diff --git a/src/libs/Navigation/AppNavigator/createCustomStackNavigator/CustomRouter.js b/src/libs/Navigation/AppNavigator/createCustomStackNavigator/CustomRouter.ts similarity index 60% rename from src/libs/Navigation/AppNavigator/createCustomStackNavigator/CustomRouter.js rename to src/libs/Navigation/AppNavigator/createCustomStackNavigator/CustomRouter.ts index a3d8398a22b0..845a37a95c20 100644 --- a/src/libs/Navigation/AppNavigator/createCustomStackNavigator/CustomRouter.js +++ b/src/libs/Navigation/AppNavigator/createCustomStackNavigator/CustomRouter.ts @@ -1,31 +1,32 @@ -import _ from 'underscore'; -import {StackRouter} from '@react-navigation/native'; +import {StackRouter, RouterConfigOptions, ParamListBase, StackNavigationState, PartialState} from '@react-navigation/native'; import NAVIGATORS from '../../../../NAVIGATORS'; +import {ResponsiveStackNavigatorRouterOptions} from '../types'; -/** - * @param {Object} state - react-navigation state - * @returns {Boolean} - */ -const isAtLeastOneCentralPaneNavigatorInState = (state) => _.find(state.routes, (r) => r.name === NAVIGATORS.CENTRAL_PANE_NAVIGATOR); +type MutableState = { + index: number; + stale: true; +}; + +type State = Omit>, 'stale'> & MutableState; + +const isAtLeastOneCentralPaneNavigatorInState = (state: State): boolean => !!state.routes.find((r) => r.name === NAVIGATORS.CENTRAL_PANE_NAVIGATOR); /** * Adds report route without any specific reportID to the state. * The report screen will self set proper reportID param based on the helper function findLastAccessedReport (look at ReportScreenWrapper for more info) - * - * @param {Object} state - react-navigation state */ -const addCentralPaneNavigatorRoute = (state) => { +const addCentralPaneNavigatorRoute = (state: State) => { state.routes.splice(1, 0, {name: NAVIGATORS.CENTRAL_PANE_NAVIGATOR}); // eslint-disable-next-line no-param-reassign state.index = state.routes.length - 1; }; -function CustomRouter(options) { +function CustomRouter(options: ResponsiveStackNavigatorRouterOptions) { const stackRouter = StackRouter(options); return { ...stackRouter, - getRehydratedState(partialState, {routeNames, routeParamList}) { + getRehydratedState(partialState: State, {routeNames, routeParamList, routeGetIdList}: RouterConfigOptions) { // Make sure that there is at least one CentralPaneNavigator (ReportScreen by default) in the state if this is a wide layout if (!isAtLeastOneCentralPaneNavigatorInState(partialState) && !options.getIsSmallScreenWidth()) { // If we added a route we need to make sure that the state.stale is true to generate new key for this route @@ -33,7 +34,7 @@ function CustomRouter(options) { partialState.stale = true; addCentralPaneNavigatorRoute(partialState); } - const state = stackRouter.getRehydratedState(partialState, {routeNames, routeParamList}); + const state = stackRouter.getRehydratedState(partialState, {routeNames, routeParamList, routeGetIdList}); return state; }, }; diff --git a/src/libs/Navigation/AppNavigator/createCustomStackNavigator/index.js b/src/libs/Navigation/AppNavigator/createCustomStackNavigator/index.js deleted file mode 100644 index 58be3d2af3da..000000000000 --- a/src/libs/Navigation/AppNavigator/createCustomStackNavigator/index.js +++ /dev/null @@ -1,60 +0,0 @@ -import React, {useRef} from 'react'; -import PropTypes from 'prop-types'; -import {useNavigationBuilder, createNavigatorFactory} from '@react-navigation/native'; -import {StackView} from '@react-navigation/stack'; -import CustomRouter from './CustomRouter'; -import useWindowDimensions from '../../../../hooks/useWindowDimensions'; - -const propTypes = { - /* Determines if the navigator should render the StackView (narrow) or ThreePaneView (wide) */ - isSmallScreenWidth: PropTypes.bool.isRequired, - - /* Children for the useNavigationBuilder hook */ - children: PropTypes.oneOfType([PropTypes.func, PropTypes.node]).isRequired, - - /* initialRouteName for this navigator */ - initialRouteName: PropTypes.oneOf([PropTypes.string, PropTypes.undefined]), - - /* Screen options defined for this navigator */ - // eslint-disable-next-line react/forbid-prop-types - screenOptions: PropTypes.object, -}; - -const defaultProps = { - initialRouteName: undefined, - screenOptions: undefined, -}; - -function ResponsiveStackNavigator(props) { - const {isSmallScreenWidth} = useWindowDimensions(); - - const isSmallScreenWidthRef = useRef(isSmallScreenWidth); - - isSmallScreenWidthRef.current = isSmallScreenWidth; - - const {navigation, state, descriptors, NavigationContent} = useNavigationBuilder(CustomRouter, { - children: props.children, - screenOptions: props.screenOptions, - initialRouteName: props.initialRouteName, - // Options for useNavigationBuilder won't update on prop change, so we need to pass a getter for the router to have the current state of isSmallScreenWidth. - getIsSmallScreenWidth: () => isSmallScreenWidthRef.current, - }); - - return ( - - - - ); -} - -ResponsiveStackNavigator.defaultProps = defaultProps; -ResponsiveStackNavigator.propTypes = propTypes; -ResponsiveStackNavigator.displayName = 'ResponsiveStackNavigator'; - -export default createNavigatorFactory(ResponsiveStackNavigator); diff --git a/src/libs/Navigation/AppNavigator/createCustomStackNavigator/index.tsx b/src/libs/Navigation/AppNavigator/createCustomStackNavigator/index.tsx new file mode 100644 index 000000000000..a97bb3de9611 --- /dev/null +++ b/src/libs/Navigation/AppNavigator/createCustomStackNavigator/index.tsx @@ -0,0 +1,46 @@ +import React, {useRef} from 'react'; +import {useNavigationBuilder, createNavigatorFactory, ParamListBase, StackActionHelpers, StackNavigationState} from '@react-navigation/native'; +import {StackNavigationEventMap, StackNavigationOptions, StackView} from '@react-navigation/stack'; +import CustomRouter from './CustomRouter'; +import useWindowDimensions from '../../../../hooks/useWindowDimensions'; +import {ResponsiveStackNavigatorProps, ResponsiveStackNavigatorRouterOptions} from '../types'; +import type {WindowDimensions} from '../../../../styles/getModalStyles'; + +function ResponsiveStackNavigator(props: ResponsiveStackNavigatorProps) { + // TODO: remove type assertion when useWindowDimensions is migrated to TS + const {isSmallScreenWidth} = useWindowDimensions() as WindowDimensions; + + const isSmallScreenWidthRef = useRef(isSmallScreenWidth); + + isSmallScreenWidthRef.current = isSmallScreenWidth; + + const {navigation, state, descriptors, NavigationContent} = useNavigationBuilder< + StackNavigationState, + ResponsiveStackNavigatorRouterOptions, + StackActionHelpers, + StackNavigationOptions, + StackNavigationEventMap + >(CustomRouter, { + children: props.children, + screenOptions: props.screenOptions, + initialRouteName: props.initialRouteName, + // Options for useNavigationBuilder won't update on prop change, so we need to pass a getter for the router to have the current state of isSmallScreenWidth. + getIsSmallScreenWidth: () => isSmallScreenWidthRef.current, + }); + + return ( + + + + ); +} + +ResponsiveStackNavigator.displayName = 'ResponsiveStackNavigator'; + +export default createNavigatorFactory, StackNavigationOptions, StackNavigationEventMap, typeof ResponsiveStackNavigator>(ResponsiveStackNavigator); diff --git a/src/libs/Navigation/AppNavigator/defaultScreenOptions.js b/src/libs/Navigation/AppNavigator/defaultScreenOptions.ts similarity index 68% rename from src/libs/Navigation/AppNavigator/defaultScreenOptions.js rename to src/libs/Navigation/AppNavigator/defaultScreenOptions.ts index 3ccffb5f09ab..745e66c52e94 100644 --- a/src/libs/Navigation/AppNavigator/defaultScreenOptions.js +++ b/src/libs/Navigation/AppNavigator/defaultScreenOptions.ts @@ -1,3 +1,5 @@ +import {StackNavigationOptions} from '@react-navigation/stack'; + const defaultScreenOptions = { cardStyle: { overflow: 'visible', @@ -5,6 +7,6 @@ const defaultScreenOptions = { }, headerShown: false, animationTypeForReplace: 'push', -}; +} as StackNavigationOptions; export default defaultScreenOptions; diff --git a/src/libs/Navigation/AppNavigator/getRootNavigatorScreenOptions.js b/src/libs/Navigation/AppNavigator/getRootNavigatorScreenOptions.ts similarity index 71% rename from src/libs/Navigation/AppNavigator/getRootNavigatorScreenOptions.js rename to src/libs/Navigation/AppNavigator/getRootNavigatorScreenOptions.ts index a7456fb071b4..48dac2d13051 100644 --- a/src/libs/Navigation/AppNavigator/getRootNavigatorScreenOptions.js +++ b/src/libs/Navigation/AppNavigator/getRootNavigatorScreenOptions.ts @@ -1,3 +1,4 @@ +import {StackCardInterpolationProps, StackNavigationOptions} from '@react-navigation/stack'; import modalCardStyleInterpolator from './modalCardStyleInterpolator'; import styles from '../../../styles/styles'; import variables from '../../../styles/variables'; @@ -10,12 +11,12 @@ const commonScreenOptions = { animationEnabled: true, cardOverlayEnabled: true, animationTypeForReplace: 'push', -}; +} as StackNavigationOptions; -export default (isSmallScreenWidth) => ({ +export default (isSmallScreenWidth: boolean) => ({ rightModalNavigator: { ...commonScreenOptions, - cardStyleInterpolator: (props) => modalCardStyleInterpolator(isSmallScreenWidth, false, props), + cardStyleInterpolator: (props: StackCardInterpolationProps) => modalCardStyleInterpolator(isSmallScreenWidth, false, props), presentation: 'transparentModal', // We want pop in RHP since there are some flows that would work weird otherwise @@ -28,12 +29,12 @@ export default (isSmallScreenWidth) => ({ // Excess space should be on the left so we need to position from right. right: 0, }, - }, + } as StackNavigationOptions, homeScreen: { title: CONFIG.SITE_TITLE, ...commonScreenOptions, - cardStyleInterpolator: (props) => modalCardStyleInterpolator(isSmallScreenWidth, false, props), + cardStyleInterpolator: (props: StackCardInterpolationProps) => modalCardStyleInterpolator(isSmallScreenWidth, false, props), cardStyle: { ...getNavigationModalCardStyle(), @@ -43,28 +44,27 @@ export default (isSmallScreenWidth) => ({ transform: [{translateX: isSmallScreenWidth ? 0 : -variables.sideBarWidth}], ...(isSmallScreenWidth ? {} : styles.borderRight), }, - }, - // eslint-disable-next-line rulesdir/no-negated-variables + } as StackNavigationOptions, fullScreen: { ...commonScreenOptions, - cardStyleInterpolator: (props) => modalCardStyleInterpolator(isSmallScreenWidth, true, props), + cardStyleInterpolator: (props: StackCardInterpolationProps) => modalCardStyleInterpolator(isSmallScreenWidth, true, props), cardStyle: { ...getNavigationModalCardStyle(), // This is necessary to cover whole screen. Including translated sidebar. marginLeft: isSmallScreenWidth ? 0 : -variables.sideBarWidth, }, - }, + } as StackNavigationOptions, centralPaneNavigator: { title: CONFIG.SITE_TITLE, ...commonScreenOptions, animationEnabled: isSmallScreenWidth, - cardStyleInterpolator: (props) => modalCardStyleInterpolator(isSmallScreenWidth, true, props), + cardStyleInterpolator: (props: StackCardInterpolationProps) => modalCardStyleInterpolator(isSmallScreenWidth, true, props), cardStyle: { ...getNavigationModalCardStyle(), paddingRight: isSmallScreenWidth ? 0 : variables.sideBarWidth, }, - }, + } as StackNavigationOptions, }); diff --git a/src/libs/Navigation/AppNavigator/index.js b/src/libs/Navigation/AppNavigator/index.tsx similarity index 73% rename from src/libs/Navigation/AppNavigator/index.js rename to src/libs/Navigation/AppNavigator/index.tsx index dee8027b2f30..d2cf587b8fa8 100644 --- a/src/libs/Navigation/AppNavigator/index.js +++ b/src/libs/Navigation/AppNavigator/index.tsx @@ -1,12 +1,11 @@ import React from 'react'; -import PropTypes from 'prop-types'; -const propTypes = { +type AppNavigatorProps = { /** If we have an authToken this is true */ - authenticated: PropTypes.bool.isRequired, + authenticated: boolean; }; -function AppNavigator(props) { +function AppNavigator(props: AppNavigatorProps) { if (props.authenticated) { const AuthScreens = require('./AuthScreens').default; @@ -17,6 +16,5 @@ function AppNavigator(props) { return ; } -AppNavigator.propTypes = propTypes; AppNavigator.displayName = 'AppNavigator'; export default AppNavigator; diff --git a/src/libs/Navigation/AppNavigator/modalCardStyleInterpolator.js b/src/libs/Navigation/AppNavigator/modalCardStyleInterpolator.ts similarity index 70% rename from src/libs/Navigation/AppNavigator/modalCardStyleInterpolator.js rename to src/libs/Navigation/AppNavigator/modalCardStyleInterpolator.ts index ec442efbba86..7dd26be93252 100644 --- a/src/libs/Navigation/AppNavigator/modalCardStyleInterpolator.js +++ b/src/libs/Navigation/AppNavigator/modalCardStyleInterpolator.ts @@ -1,8 +1,9 @@ import {Animated} from 'react-native'; +import type {StackCardInterpolatedStyle, StackCardInterpolationProps} from '@react-navigation/stack'; import variables from '../../../styles/variables'; import getCardStyles from '../../../styles/cardStyles'; -export default (isSmallScreenWidth, isFullScreenModal, {current: {progress}, inverted, layouts: {screen}}) => { +export default (isSmallScreenWidth: boolean, isFullScreenModal: boolean, {current: {progress}, inverted, layouts: {screen}}: StackCardInterpolationProps): StackCardInterpolatedStyle => { const translateX = Animated.multiply( progress.interpolate({ inputRange: [0, 1], diff --git a/src/libs/Navigation/AppNavigator/types.ts b/src/libs/Navigation/AppNavigator/types.ts new file mode 100644 index 000000000000..67286a6cbd85 --- /dev/null +++ b/src/libs/Navigation/AppNavigator/types.ts @@ -0,0 +1,87 @@ +import {StackScreenProps, StackNavigationOptions, StackNavigationEventMap} from '@react-navigation/stack'; +import {DefaultNavigatorOptions, ParamListBase, StackNavigationState, DefaultRouterOptions, NavigatorScreenParams} from '@react-navigation/native'; +import {ValueOf} from 'type-fest'; + +import SCREENS from '../../../SCREENS'; +import NAVIGATORS from '../../../NAVIGATORS'; +import CONST from '../../../CONST'; + +type AccountValidationParams = { + /** AccountID associated with the validation link */ + accountID: string; + + /** Validation code associated with the validation link */ + validateCode: string; +}; + +type CentralPaneStackParamList = { + [SCREENS.REPORT]: { + /** If the admin room should be opened */ + openOnAdminRoom?: boolean; + + /** The ID of the report this screen should display */ + reportID: string; + }; +}; +type ReportScreenWrapperProps = StackScreenProps; + +type PublicScreensStackParamList = { + [SCREENS.HOME]: undefined; + [SCREENS.TRANSITION_BETWEEN_APPS]: { + /** Short-lived authToken to sign in a user */ + shortLivedAuthToken: string; + + /** Short-lived authToken to sign in as a user, if they are coming from the old mobile app */ + shortLivedToken: string; + + /** The email of the transitioning user */ + email: string; + }; + [SCREENS.VALIDATE_LOGIN]: AccountValidationParams; + [SCREENS.UNLINK_LOGIN]: AccountValidationParams; + [SCREENS.SIGN_IN_WITH_APPLE_DESKTOP]: undefined; + [SCREENS.SIGN_IN_WITH_GOOGLE_DESKTOP]: undefined; +}; + +type AuthScreensStackParamList = { + [SCREENS.HOME]: undefined; + [NAVIGATORS.CENTRAL_PANE_NAVIGATOR]: NavigatorScreenParams; + [SCREENS.VALIDATE_LOGIN]: AccountValidationParams; + [SCREENS.TRANSITION_BETWEEN_APPS]: undefined; + [SCREENS.CONCIERGE]: undefined; + [CONST.DEMO_PAGES.SAASTR]: {name: string}; + [CONST.DEMO_PAGES.SBE]: {name: string}; + [SCREENS.REPORT_ATTACHMENTS]: { + /** The report ID which the attachment is associated with */ + reportID: string; + + /** The uri encoded source of the attachment */ + source: string; + }; + [SCREENS.NOT_FOUND]: undefined; + [NAVIGATORS.RIGHT_MODAL_NAVIGATOR]: NavigatorScreenParams; + [SCREENS.DESKTOP_SIGN_IN_REDIRECT]: undefined; +}; + +// TODO: describe all of the nested navigators +type RightModalNavigatorStackParamList = Record, undefined>; + +type ResponsiveStackNavigatorConfig = { + isSmallScreenWidth: boolean; +}; +type ResponsiveStackNavigatorRouterOptions = DefaultRouterOptions & { + getIsSmallScreenWidth: () => boolean; +}; +type ResponsiveStackNavigatorProps = DefaultNavigatorOptions, StackNavigationOptions, StackNavigationEventMap> & + ResponsiveStackNavigatorConfig; + +export type { + CentralPaneStackParamList, + ReportScreenWrapperProps, + PublicScreensStackParamList, + RightModalNavigatorStackParamList, + ResponsiveStackNavigatorRouterOptions, + ResponsiveStackNavigatorProps, + ResponsiveStackNavigatorConfig, + AuthScreensStackParamList, +}; diff --git a/src/libs/Navigation/Navigation.js b/src/libs/Navigation/Navigation.js index de6162685079..5cbd8e9b0af6 100644 --- a/src/libs/Navigation/Navigation.js +++ b/src/libs/Navigation/Navigation.js @@ -77,7 +77,7 @@ const getActiveRouteIndex = function (route, index) { /** * Main navigation method for redirecting to a route. * @param {String} route - * @param {String} type - Type of action to perform. Currently UP is supported. + * @param {String} [type] - Type of action to perform. Currently UP is supported. */ function navigate(route = ROUTES.HOME, type) { if (!canNavigate('navigate', {route})) { diff --git a/src/libs/compose.ts b/src/libs/compose.ts index dadc586d0f0d..043e1e9f1bd5 100644 --- a/src/libs/compose.ts +++ b/src/libs/compose.ts @@ -17,41 +17,40 @@ * hoc3(config3), * )(Component) */ + +type Func = (...a: T) => R; + export default function compose(): (a: R) => R; export default function compose(f: F): F; /* two functions */ -export default function compose(f1: (...args: A) => R1, f2: (a: R1) => R2): (...args: A) => R2; +export default function compose(f1: (args: A) => R1, f2: Func): Func; /* three functions */ -export default function compose(f1: (...args: A) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3): (...args: A) => R3; +export default function compose(f1: (args: A) => R1, f2: (a: R1) => R2, f3: Func): Func; /* four functions */ -export default function compose(f1: (...args: A) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3, f4: (a: R3) => R4): (...args: A) => R4; +export default function compose(f1: (args: A) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3, f4: Func): Func; /* five functions */ -export default function compose( - f1: (...args: A) => R1, - f2: (a: R1) => R2, - f3: (a: R2) => R3, - f4: (a: R3) => R4, - f5: (a: R4) => R5, -): (...args: A) => R5; +export default function compose(f1: (args: A) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3, f4: (a: R3) => R4, f5: Func): Func; /* six functions */ -export default function compose( - f1: (...args: A) => R1, +export default function compose( + f1: (args: A) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3, f4: (a: R3) => R4, f5: (a: R4) => R5, - f6: (a: R5) => R6, -): (...args: A) => R6; + f6: Func, +): Func; /* rest */ export default function compose(f1: (a: unknown) => R, ...funcs: Function[]): (...args: unknown[]) => R; +export default function compose(...funcs: Function[]): (...args: unknown[]) => R; + export default function compose(...funcs: Function[]): Function { if (funcs.length === 0) { // infer the argument type so it is usable in inference down the line diff --git a/src/styles/cardStyles/types.ts b/src/styles/cardStyles/types.ts index e1598b7696ff..ce3e377bdc8f 100644 --- a/src/styles/cardStyles/types.ts +++ b/src/styles/cardStyles/types.ts @@ -1,6 +1,6 @@ import {CSSProperties} from 'react'; import {ViewStyle} from 'react-native'; -type GetCardStyles = (screenWidth: number) => Partial>; +type GetCardStyles = (screenWidth: number) => Partial>; export default GetCardStyles; diff --git a/src/styles/getModalStyles.ts b/src/styles/getModalStyles.ts index 32e6cf6f98e7..6af1354a4112 100644 --- a/src/styles/getModalStyles.ts +++ b/src/styles/getModalStyles.ts @@ -300,3 +300,4 @@ export default function getModalStyles( shouldAddTopSafeAreaPadding, }; } +export type {WindowDimensions}; diff --git a/src/types/onyx/Report.ts b/src/types/onyx/Report.ts index e2d4da88a0fb..5d22eaedd788 100644 --- a/src/types/onyx/Report.ts +++ b/src/types/onyx/Report.ts @@ -57,6 +57,9 @@ type Report = { /** The report type */ type?: string; + /** If the admin room should be opened */ + openOnAdminRoom?: boolean; + parentReportID?: string; parentReportActionID?: string; isOptimisticReport?: boolean; From fa528388f44aefd2a2fc59648b106274b2c928a3 Mon Sep 17 00:00:00 2001 From: Viktoryia Kliushun Date: Tue, 10 Oct 2023 09:53:43 +0200 Subject: [PATCH 008/844] Fix ts error --- src/libs/NetworkConnection.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/NetworkConnection.ts b/src/libs/NetworkConnection.ts index 663a9c1b37d5..7c1bcaa0b339 100644 --- a/src/libs/NetworkConnection.ts +++ b/src/libs/NetworkConnection.ts @@ -13,7 +13,7 @@ let hasPendingNetworkCheck = false; // Holds all of the callbacks that need to be triggered when the network reconnects let callbackID = 0; -const reconnectionCallbacks: Record Promise> = {}; +const reconnectionCallbacks: Record void> = {}; /** * Loop over all reconnection callbacks and fire each one @@ -122,7 +122,7 @@ function listenForReconnect() { * Register callback to fire when we reconnect * @returns unsubscribe method */ -function onReconnect(callback: () => Promise): () => void { +function onReconnect(callback: () => void): () => void { const currentID = callbackID; callbackID++; reconnectionCallbacks[currentID] = callback; From fd857e72213f76804c836e1a8901f705de907b5d Mon Sep 17 00:00:00 2001 From: Getabalew Tesfaye Date: Wed, 11 Oct 2023 20:11:05 +0300 Subject: [PATCH 009/844] Feat: update the report when a payment is refunded --- src/CONST.ts | 4 ++++ src/components/ReportActionItem/MoneyRequestPreview.js | 2 ++ src/languages/en.ts | 3 +++ src/languages/es.ts | 3 +++ src/languages/types.ts | 3 +++ src/pages/home/report/ReportActionItem.js | 6 ++++++ 6 files changed, 21 insertions(+) diff --git a/src/CONST.ts b/src/CONST.ts index 23957827d140..91942313fa47 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -486,6 +486,7 @@ const CONST = { IOU: 'IOU', MODIFIEDEXPENSE: 'MODIFIEDEXPENSE', REIMBURSEMENTQUEUED: 'REIMBURSEMENTQUEUED', + REIMBURSEMENTDEQUEUED: 'REIMBURSEMENTDEQUEUED', RENAMED: 'RENAMED', REPORTPREVIEW: 'REPORTPREVIEW', SUBMITTED: 'SUBMITTED', @@ -1118,6 +1119,9 @@ const CONST = { DOCX: 'docx', SVG: 'svg', }, + CANCEL_REASON: { + PAYMENT_EXPIRED: 'CANCEL_REASON_PAYMENT_EXPIRED' + }, }, GROWL: { diff --git a/src/components/ReportActionItem/MoneyRequestPreview.js b/src/components/ReportActionItem/MoneyRequestPreview.js index 35215cadd15d..783c52c0553b 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview.js +++ b/src/components/ReportActionItem/MoneyRequestPreview.js @@ -206,6 +206,8 @@ function MoneyRequestPreview(props) { message += ` • ${props.translate('iou.pending')}`; } else if (ReportUtils.isSettled(props.iouReport.reportID)) { message += ` • ${props.translate('iou.settledExpensify')}`; + } else if(props.iouReport.originalMessage.reason === CONST.IOU.CANCEL_REASON.PAYMENT_EXPIRED) { + message += ` • ${props.translate('iou.canceled')}`; } return message; }; diff --git a/src/languages/en.ts b/src/languages/en.ts index 7133ed88579e..3f47db2ecffd 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -76,6 +76,7 @@ import type { TagSelectionParams, TranslationBase, WalletProgramParams, + CanceledRequestParams, } from './types'; import * as ReportActionsUtils from '../libs/ReportActionsUtils'; @@ -522,6 +523,7 @@ export default { pay: 'Pay', viewDetails: 'View details', pending: 'Pending', + canceled: 'Canceled', deleteReceipt: 'Delete receipt', receiptScanning: 'Receipt scan in progress…', receiptMissingDetails: 'Receipt missing details', @@ -545,6 +547,7 @@ export default { managerApproved: ({manager}: ManagerApprovedParams) => `${manager} approved:`, payerSettled: ({amount}: PayerSettledParams) => `paid ${amount}`, waitingOnBankAccount: ({submitterDisplayName}: WaitingOnBankAccountParams) => `started settling up, payment is held until ${submitterDisplayName} adds a bank account`, + canceledRequest: ({amount, submitterDisplayName}: CanceledRequestParams) => `Canceled the ${amount} payment, because ${submitterDisplayName} did not enable their Expensify Wallet within 30 days`, settledAfterAddedBankAccount: ({submitterDisplayName, amount}: SettledAfterAddedBankAccountParams) => `${submitterDisplayName} added a bank account. The ${amount} payment has been made.`, paidElsewhereWithAmount: ({payer, amount}: PaidElsewhereWithAmountParams) => `${payer} paid ${amount} elsewhere`, diff --git a/src/languages/es.ts b/src/languages/es.ts index a98ddfaff7d0..db8e0d7c43c7 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -76,6 +76,7 @@ import type { TagSelectionParams, EnglishTranslation, WalletProgramParams, + CanceledRequestParams, } from './types'; /* eslint-disable max-len */ @@ -514,6 +515,7 @@ export default { pay: 'Pagar', viewDetails: 'Ver detalles', pending: 'Pendiente', + canceled: 'Canceled', deleteReceipt: 'Eliminar recibo', receiptScanning: 'Escaneo de recibo en curso…', receiptMissingDetails: 'Recibo con campos vacíos', @@ -537,6 +539,7 @@ export default { managerApproved: ({manager}: ManagerApprovedParams) => `${manager} aprobó:`, payerSettled: ({amount}: PayerSettledParams) => `pagó ${amount}`, waitingOnBankAccount: ({submitterDisplayName}: WaitingOnBankAccountParams) => `inicio el pago, pero no se procesará hasta que ${submitterDisplayName} añada una cuenta bancaria`, + canceledRequest: ({amount, submitterDisplayName}: CanceledRequestParams) => `Canceled the ${amount} payment, because ${submitterDisplayName} did not enable their Expensify Wallet within 30 days`, settledAfterAddedBankAccount: ({submitterDisplayName, amount}: SettledAfterAddedBankAccountParams) => `${submitterDisplayName} añadió una cuenta bancaria. El pago de ${amount} se ha realizado.`, paidElsewhereWithAmount: ({payer, amount}: PaidElsewhereWithAmountParams) => `${payer} pagó ${amount} de otra forma`, diff --git a/src/languages/types.ts b/src/languages/types.ts index 3ee504ccddd7..b3ce20500f85 100644 --- a/src/languages/types.ts +++ b/src/languages/types.ts @@ -122,6 +122,8 @@ type PayerSettledParams = {amount: number}; type WaitingOnBankAccountParams = {submitterDisplayName: string}; +type CanceledRequestParams = {amount: string, submitterDisplayName: string} + type SettledAfterAddedBankAccountParams = {submitterDisplayName: string; amount: string}; type PaidElsewhereWithAmountParams = {payer: string; amount: string}; @@ -277,6 +279,7 @@ export type { ManagerApprovedParams, PayerSettledParams, WaitingOnBankAccountParams, + CanceledRequestParams, SettledAfterAddedBankAccountParams, PaidElsewhereWithAmountParams, PaidWithExpensifyWithAmountParams, diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index d0e84499a443..36c8476df976 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -70,6 +70,7 @@ import themeColors from '../../../styles/themes/default'; import ReportActionItemBasicMessage from './ReportActionItemBasicMessage'; import RenderHTML from '../../../components/RenderHTML'; import ReportAttachmentsContext from './ReportAttachmentsContext'; +import * as CurrencyUtils from "../../../libs/CurrencyUtils"; const propTypes = { ...windowDimensionsPropTypes, @@ -361,6 +362,11 @@ function ReportActionItem(props) { ) : null} ); + } else if(props.action.actionName === CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENTDEQUEUED) { + const submitterDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(props.personalDetailsList, [props.iouReport.ownerAccountID, 'displayName'], props.iouReport.ownerEmail); + const amount = CurrencyUtils.convertToDisplayString(props.iouReport.total, props.iouReport.currency); + + children = ; } else if (props.action.actionName === CONST.REPORT.ACTIONS.TYPE.MODIFIEDEXPENSE) { children = ; } else { From 2b33b90d6da6399626bcaafac99dbaee4592a1b3 Mon Sep 17 00:00:00 2001 From: Getabalew Tesfaye Date: Wed, 11 Oct 2023 20:49:27 +0300 Subject: [PATCH 010/844] run prettier --- src/CONST.ts | 2 +- src/components/ReportActionItem/MoneyRequestPreview.js | 2 +- src/languages/en.ts | 3 ++- src/languages/es.ts | 3 ++- src/languages/types.ts | 2 +- src/pages/home/report/ReportActionItem.js | 4 ++-- 6 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 91942313fa47..308d83766d93 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1120,7 +1120,7 @@ const CONST = { SVG: 'svg', }, CANCEL_REASON: { - PAYMENT_EXPIRED: 'CANCEL_REASON_PAYMENT_EXPIRED' + PAYMENT_EXPIRED: 'CANCEL_REASON_PAYMENT_EXPIRED', }, }, diff --git a/src/components/ReportActionItem/MoneyRequestPreview.js b/src/components/ReportActionItem/MoneyRequestPreview.js index 783c52c0553b..a408041dbf5c 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview.js +++ b/src/components/ReportActionItem/MoneyRequestPreview.js @@ -206,7 +206,7 @@ function MoneyRequestPreview(props) { message += ` • ${props.translate('iou.pending')}`; } else if (ReportUtils.isSettled(props.iouReport.reportID)) { message += ` • ${props.translate('iou.settledExpensify')}`; - } else if(props.iouReport.originalMessage.reason === CONST.IOU.CANCEL_REASON.PAYMENT_EXPIRED) { + } else if (props.iouReport.originalMessage.reason === CONST.IOU.CANCEL_REASON.PAYMENT_EXPIRED) { message += ` • ${props.translate('iou.canceled')}`; } return message; diff --git a/src/languages/en.ts b/src/languages/en.ts index 3f47db2ecffd..e5ab33014b4a 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -547,7 +547,8 @@ export default { managerApproved: ({manager}: ManagerApprovedParams) => `${manager} approved:`, payerSettled: ({amount}: PayerSettledParams) => `paid ${amount}`, waitingOnBankAccount: ({submitterDisplayName}: WaitingOnBankAccountParams) => `started settling up, payment is held until ${submitterDisplayName} adds a bank account`, - canceledRequest: ({amount, submitterDisplayName}: CanceledRequestParams) => `Canceled the ${amount} payment, because ${submitterDisplayName} did not enable their Expensify Wallet within 30 days`, + canceledRequest: ({amount, submitterDisplayName}: CanceledRequestParams) => + `Canceled the ${amount} payment, because ${submitterDisplayName} did not enable their Expensify Wallet within 30 days`, settledAfterAddedBankAccount: ({submitterDisplayName, amount}: SettledAfterAddedBankAccountParams) => `${submitterDisplayName} added a bank account. The ${amount} payment has been made.`, paidElsewhereWithAmount: ({payer, amount}: PaidElsewhereWithAmountParams) => `${payer} paid ${amount} elsewhere`, diff --git a/src/languages/es.ts b/src/languages/es.ts index db8e0d7c43c7..c88d6fb290ac 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -539,7 +539,8 @@ export default { managerApproved: ({manager}: ManagerApprovedParams) => `${manager} aprobó:`, payerSettled: ({amount}: PayerSettledParams) => `pagó ${amount}`, waitingOnBankAccount: ({submitterDisplayName}: WaitingOnBankAccountParams) => `inicio el pago, pero no se procesará hasta que ${submitterDisplayName} añada una cuenta bancaria`, - canceledRequest: ({amount, submitterDisplayName}: CanceledRequestParams) => `Canceled the ${amount} payment, because ${submitterDisplayName} did not enable their Expensify Wallet within 30 days`, + canceledRequest: ({amount, submitterDisplayName}: CanceledRequestParams) => + `Canceled the ${amount} payment, because ${submitterDisplayName} did not enable their Expensify Wallet within 30 days`, settledAfterAddedBankAccount: ({submitterDisplayName, amount}: SettledAfterAddedBankAccountParams) => `${submitterDisplayName} añadió una cuenta bancaria. El pago de ${amount} se ha realizado.`, paidElsewhereWithAmount: ({payer, amount}: PaidElsewhereWithAmountParams) => `${payer} pagó ${amount} de otra forma`, diff --git a/src/languages/types.ts b/src/languages/types.ts index b3ce20500f85..4ba148a7869d 100644 --- a/src/languages/types.ts +++ b/src/languages/types.ts @@ -122,7 +122,7 @@ type PayerSettledParams = {amount: number}; type WaitingOnBankAccountParams = {submitterDisplayName: string}; -type CanceledRequestParams = {amount: string, submitterDisplayName: string} +type CanceledRequestParams = {amount: string; submitterDisplayName: string}; type SettledAfterAddedBankAccountParams = {submitterDisplayName: string; amount: string}; diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 36c8476df976..5c52670fd37e 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -70,7 +70,7 @@ import themeColors from '../../../styles/themes/default'; import ReportActionItemBasicMessage from './ReportActionItemBasicMessage'; import RenderHTML from '../../../components/RenderHTML'; import ReportAttachmentsContext from './ReportAttachmentsContext'; -import * as CurrencyUtils from "../../../libs/CurrencyUtils"; +import * as CurrencyUtils from '../../../libs/CurrencyUtils'; const propTypes = { ...windowDimensionsPropTypes, @@ -362,7 +362,7 @@ function ReportActionItem(props) { ) : null} ); - } else if(props.action.actionName === CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENTDEQUEUED) { + } else if (props.action.actionName === CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENTDEQUEUED) { const submitterDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(props.personalDetailsList, [props.iouReport.ownerAccountID, 'displayName'], props.iouReport.ownerEmail); const amount = CurrencyUtils.convertToDisplayString(props.iouReport.total, props.iouReport.currency); From 0aac629c63aede00570c19eef26e3275812c0b35 Mon Sep 17 00:00:00 2001 From: Getabalew Tesfaye Date: Wed, 11 Oct 2023 23:44:42 +0300 Subject: [PATCH 011/844] prevent crash by checking key existence --- src/components/ReportActionItem/MoneyRequestPreview.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestPreview.js b/src/components/ReportActionItem/MoneyRequestPreview.js index a408041dbf5c..fc294c3ffeda 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview.js +++ b/src/components/ReportActionItem/MoneyRequestPreview.js @@ -206,7 +206,7 @@ function MoneyRequestPreview(props) { message += ` • ${props.translate('iou.pending')}`; } else if (ReportUtils.isSettled(props.iouReport.reportID)) { message += ` • ${props.translate('iou.settledExpensify')}`; - } else if (props.iouReport.originalMessage.reason === CONST.IOU.CANCEL_REASON.PAYMENT_EXPIRED) { + } else if (props.iouReport.originalMessage && props.iouReport.originalMessage.reason === CONST.IOU.CANCEL_REASON.PAYMENT_EXPIRED) { message += ` • ${props.translate('iou.canceled')}`; } return message; From b7986d4975e834a3642655634bf9884b66285fcb Mon Sep 17 00:00:00 2001 From: Getabalew Tesfaye Date: Wed, 11 Oct 2023 23:57:24 +0300 Subject: [PATCH 012/844] Add es translations --- src/languages/es.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/languages/es.ts b/src/languages/es.ts index 500cc529304c..d0e5efa00c12 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -519,7 +519,7 @@ export default { pay: 'Pagar', viewDetails: 'Ver detalles', pending: 'Pendiente', - canceled: 'Canceled', + canceled: 'Canceló', posted: 'Contabilizado', deleteReceipt: 'Eliminar recibo', receiptScanning: 'Escaneo de recibo en curso…', @@ -545,7 +545,7 @@ export default { payerSettled: ({amount}: PayerSettledParams) => `pagó ${amount}`, waitingOnBankAccount: ({submitterDisplayName}: WaitingOnBankAccountParams) => `inicio el pago, pero no se procesará hasta que ${submitterDisplayName} añada una cuenta bancaria`, canceledRequest: ({amount, submitterDisplayName}: CanceledRequestParams) => - `Canceled the ${amount} payment, because ${submitterDisplayName} did not enable their Expensify Wallet within 30 days`, + `Canceló el pago ${amount}, porque ${submitterDisplayName} no habilitó su billetera Expensify en un plazo de 30 días.`, settledAfterAddedBankAccount: ({submitterDisplayName, amount}: SettledAfterAddedBankAccountParams) => `${submitterDisplayName} añadió una cuenta bancaria. El pago de ${amount} se ha realizado.`, paidElsewhereWithAmount: ({payer, amount}: PaidElsewhereWithAmountParams) => `${payer} pagó ${amount} de otra forma`, From 444b732a645942f6fbc91d94ea2ec58f83d49f5a Mon Sep 17 00:00:00 2001 From: Getabalew Tesfaye Date: Thu, 12 Oct 2023 00:17:24 +0300 Subject: [PATCH 013/844] use report action value --- src/components/ReportActionItem/MoneyRequestPreview.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestPreview.js b/src/components/ReportActionItem/MoneyRequestPreview.js index aa19c8536446..b557325e8505 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview.js +++ b/src/components/ReportActionItem/MoneyRequestPreview.js @@ -216,7 +216,7 @@ function MoneyRequestPreview(props) { message += ` • ${props.translate('iou.approved')}`; } else if (props.iouReport.isWaitingOnBankAccount) { message += ` • ${props.translate('iou.pending')}`; - } else if (props.iouReport.originalMessage && props.iouReport.originalMessage.reason === CONST.IOU.CANCEL_REASON.PAYMENT_EXPIRED) { + } else if (props.action.originalMessage && props.action.originalMessage.reason === CONST.IOU.CANCEL_REASON.PAYMENT_EXPIRED) { message += ` • ${props.translate('iou.canceled')}`; } return message; From 0a8b4ad6177ff5a2fe0f1b7cd16d176b35ec8fcf Mon Sep 17 00:00:00 2001 From: Viktoryia Kliushun Date: Thu, 12 Oct 2023 10:25:36 +0200 Subject: [PATCH 014/844] Minor code improvements --- src/libs/Navigation/AppNavigator/AuthScreens.tsx | 12 +++++------- .../Navigation/AppNavigator/Navigators/Overlay.tsx | 2 +- src/libs/Navigation/AppNavigator/RHPScreenOptions.ts | 4 ++-- .../Navigation/AppNavigator/ReportScreenIDSetter.ts | 2 +- .../Navigation/AppNavigator/defaultScreenOptions.ts | 4 ++-- .../AppNavigator/getRootNavigatorScreenOptions.ts | 4 ++-- src/libs/Navigation/Navigation.js | 2 +- 7 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.tsx b/src/libs/Navigation/AppNavigator/AuthScreens.tsx index 5e8f471d4a79..c6269163a7f8 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.tsx +++ b/src/libs/Navigation/AppNavigator/AuthScreens.tsx @@ -35,6 +35,7 @@ import DemoSetupPage from '../../../pages/DemoSetupPage'; import * as OnyxTypes from '../../../types/onyx'; import type {WindowDimensions} from '../../../styles/getModalStyles'; import type {AuthScreensStackParamList} from './types'; +import type {Timezone} from '../../../types/onyx/PersonalDetails'; type AuthScreensOnyxProps = { /** Session of currently logged in user */ @@ -52,11 +53,6 @@ type AuthScreensOnyxProps = { type AuthScreensProps = WindowDimensions & AuthScreensOnyxProps; -type Timezone = { - automatic?: boolean; - selected?: string; -}; - type UnsubscribeChatShortcut = () => void; type UnsubscribeSearchShortcut = () => void; @@ -76,7 +72,7 @@ Onyx.connect({ currentAccountID = val.accountID; if (Navigation.isActiveRoute(ROUTES.SIGN_IN_MODAL)) { // This means sign in in RHP was successful, so we can dismiss the modal and subscribe to user events - Navigation.dismissModal(undefined); + Navigation.dismissModal(); User.subscribeToUserEvents(); } }, @@ -94,7 +90,7 @@ Onyx.connect({ // If the current timezone is different than the user's timezone, and their timezone is set to automatic // then update their timezone. - if (timezone !== null && timezone.automatic && timezone.selected !== currentTimezone) { + if (timezone?.automatic && timezone?.selected !== currentTimezone) { timezone.selected = currentTimezone; PersonalDetails.updateAutomaticTimezone({ automatic: true, @@ -200,6 +196,7 @@ class AuthScreens extends React.Component { searchShortcutConfig.descriptionKey, [...searchShortcutConfig.modifiers], true, + // TODO: remove type assertion when KeyboardShortcut is migrated ) as UnsubscribeSearchShortcut; this.unsubscribeChatShortcut = KeyboardShortcut.subscribe( chatShortcutConfig.shortcutKey, @@ -214,6 +211,7 @@ class AuthScreens extends React.Component { chatShortcutConfig.descriptionKey, [...chatShortcutConfig.modifiers], true, + // TODO: remove type assertion when KeyboardShortcut is migrated ) as UnsubscribeChatShortcut; } diff --git a/src/libs/Navigation/AppNavigator/Navigators/Overlay.tsx b/src/libs/Navigation/AppNavigator/Navigators/Overlay.tsx index 66888ac6201c..19b8a0b4453a 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/Overlay.tsx +++ b/src/libs/Navigation/AppNavigator/Navigators/Overlay.tsx @@ -9,7 +9,7 @@ import useLocalize from '../../../../hooks/useLocalize'; import CONST from '../../../../CONST'; type OverlayProps = { - onPress: () => unknown; + onPress: () => void; }; function Overlay(props: OverlayProps) { diff --git a/src/libs/Navigation/AppNavigator/RHPScreenOptions.ts b/src/libs/Navigation/AppNavigator/RHPScreenOptions.ts index b21f29fc466e..ae2244cb510b 100644 --- a/src/libs/Navigation/AppNavigator/RHPScreenOptions.ts +++ b/src/libs/Navigation/AppNavigator/RHPScreenOptions.ts @@ -1,12 +1,12 @@ import {CardStyleInterpolators, StackNavigationOptions} from '@react-navigation/stack'; import styles from '../../../styles/styles'; -const RHPScreenOptions = { +const RHPScreenOptions: StackNavigationOptions = { headerShown: false, animationEnabled: true, gestureDirection: 'horizontal', cardStyle: styles.navigationScreenCardStyle, cardStyleInterpolator: CardStyleInterpolators.forHorizontalIOS, -} as StackNavigationOptions; +}; export default RHPScreenOptions; diff --git a/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts b/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts index de725a59f42c..2a5f7e3aac31 100644 --- a/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts +++ b/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts @@ -25,7 +25,7 @@ type ReportScreenIDSetterProps = ReportScreenIDSetterComponentProps & ReportScre * Get the most recently accessed report for the user */ const getLastAccessedReportID = ( - reports: OnyxCollection | Report[], + reports: OnyxCollection, ignoreDefaultRooms: boolean, policies: OnyxCollection, isFirstTimeNewExpensifyUser: OnyxEntry, diff --git a/src/libs/Navigation/AppNavigator/defaultScreenOptions.ts b/src/libs/Navigation/AppNavigator/defaultScreenOptions.ts index 745e66c52e94..65a6bd052742 100644 --- a/src/libs/Navigation/AppNavigator/defaultScreenOptions.ts +++ b/src/libs/Navigation/AppNavigator/defaultScreenOptions.ts @@ -1,12 +1,12 @@ import {StackNavigationOptions} from '@react-navigation/stack'; -const defaultScreenOptions = { +const defaultScreenOptions: StackNavigationOptions = { cardStyle: { overflow: 'visible', flex: 1, }, headerShown: false, animationTypeForReplace: 'push', -} as StackNavigationOptions; +}; export default defaultScreenOptions; diff --git a/src/libs/Navigation/AppNavigator/getRootNavigatorScreenOptions.ts b/src/libs/Navigation/AppNavigator/getRootNavigatorScreenOptions.ts index 48dac2d13051..253006373a8e 100644 --- a/src/libs/Navigation/AppNavigator/getRootNavigatorScreenOptions.ts +++ b/src/libs/Navigation/AppNavigator/getRootNavigatorScreenOptions.ts @@ -5,13 +5,13 @@ import variables from '../../../styles/variables'; import getNavigationModalCardStyle from '../../../styles/getNavigationModalCardStyles'; import CONFIG from '../../../CONFIG'; -const commonScreenOptions = { +const commonScreenOptions: StackNavigationOptions = { headerShown: false, gestureDirection: 'horizontal', animationEnabled: true, cardOverlayEnabled: true, animationTypeForReplace: 'push', -} as StackNavigationOptions; +}; export default (isSmallScreenWidth: boolean) => ({ rightModalNavigator: { diff --git a/src/libs/Navigation/Navigation.js b/src/libs/Navigation/Navigation.js index 09171503706c..f24270cfcd5c 100644 --- a/src/libs/Navigation/Navigation.js +++ b/src/libs/Navigation/Navigation.js @@ -159,7 +159,7 @@ function setParams(params, routeKey) { /** * Dismisses the last modal stack if there is any * - * @param {String | undefined} targetReportID - The reportID to navigate to after dismissing the modal + * @param {String | undefined} [targetReportID] - The reportID to navigate to after dismissing the modal */ function dismissModal(targetReportID) { if (!canNavigate('dismissModal')) { From 400156a0a29e04433fe830bab235a6c08f0e3fe7 Mon Sep 17 00:00:00 2001 From: Nikki Wines Date: Fri, 13 Oct 2023 11:57:50 +0900 Subject: [PATCH 015/844] add native view for SAML SignInPage --- .../signin/SAMLSignInPage/index.native.js | 44 +++++++++++++++++++ src/pages/signin/SignInPage.js | 12 ++--- 2 files changed, 47 insertions(+), 9 deletions(-) create mode 100644 src/pages/signin/SAMLSignInPage/index.native.js diff --git a/src/pages/signin/SAMLSignInPage/index.native.js b/src/pages/signin/SAMLSignInPage/index.native.js new file mode 100644 index 000000000000..c4b0ae78bc30 --- /dev/null +++ b/src/pages/signin/SAMLSignInPage/index.native.js @@ -0,0 +1,44 @@ +import React, {useEffect, useRef} from 'react'; +import {withOnyx} from 'react-native-onyx'; +import PropTypes from 'prop-types'; +import ONYXKEYS from '../../../ONYXKEYS'; +import CONFIG from '../../../CONFIG'; +import WebView from 'react-native-webview'; +import FullScreenLoadingIndicator from '../../../components/FullscreenLoadingIndicator'; + +const propTypes = { + /** The credentials of the logged in person */ + credentials: PropTypes.shape({ + /** The email/phone the user logged in with */ + login: PropTypes.string, + }), +}; + +const defaultProps = { + credentials: {}, +}; + +const renderLoading = () => ; + +function SAMLSignInPage({credentials}) { + const webViewRef = useRef(); + const samlLoginURL = `${CONFIG.EXPENSIFY.SAML_URL}?email=${credentials.login}&referer=${CONFIG.EXPENSIFY.EXPENSIFY_CASH_REFERER}`; + return ( + {console.log("meep meep navigation changed: " + JSON.stringify(ref))}} + /> + ); +} + +SAMLSignInPage.propTypes = propTypes; +SAMLSignInPage.defaultProps = defaultProps; + +export default withOnyx({ + credentials: {key: ONYXKEYS.CREDENTIALS}, +})(SAMLSignInPage); diff --git a/src/pages/signin/SignInPage.js b/src/pages/signin/SignInPage.js index 7da41e2ab474..81abbdb9d1ae 100644 --- a/src/pages/signin/SignInPage.js +++ b/src/pages/signin/SignInPage.js @@ -96,15 +96,9 @@ function getRenderOptions({hasLogin, hasValidateCode, account, isPrimaryLogin, i const isSAMLRequired = Boolean(account.isSAMLRequired); const hasEmailDeliveryFailure = Boolean(account.hasEmailDeliveryFailure); - // SAML is temporarily restricted to users on the beta or to users signing in on web and mweb - let shouldShowChooseSSOOrMagicCode = false; - let shouldInitiateSAMLLogin = false; - const platform = getPlatform(); - if (Permissions.canUseSAML() || platform === CONST.PLATFORM.WEB || platform === CONST.PLATFORM.DESKTOP) { - // True if the user has SAML required and we're not already loading their account - shouldInitiateSAMLLogin = hasAccount && hasLogin && isSAMLRequired && !hasInitiatedSAMLLogin && account.isLoading; - shouldShowChooseSSOOrMagicCode = hasAccount && hasLogin && isSAMLEnabled && !isSAMLRequired && !isUsingMagicCode; - } + // True if the user has SAML required and we're not already loading their account + const shouldInitiateSAMLLogin = hasAccount && hasLogin && isSAMLRequired && !hasInitiatedSAMLLogin && account.isLoading; + const shouldShowChooseSSOOrMagicCode = hasAccount && hasLogin && isSAMLEnabled && !isSAMLRequired && !isUsingMagicCode; // SAML required users may reload the login page after having already entered their login details, in which // case we want to clear their sign in data so they don't end up in an infinite loop redirecting back to their From 8991e7ef264595e150ed631c8e733291ec213be1 Mon Sep 17 00:00:00 2001 From: Nikki Wines Date: Fri, 13 Oct 2023 20:18:43 +0900 Subject: [PATCH 016/844] log in with shortlivedtoken when one is returned --- .../signin/SAMLSignInPage/index.native.js | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/pages/signin/SAMLSignInPage/index.native.js b/src/pages/signin/SAMLSignInPage/index.native.js index c4b0ae78bc30..3825cca0fa97 100644 --- a/src/pages/signin/SAMLSignInPage/index.native.js +++ b/src/pages/signin/SAMLSignInPage/index.native.js @@ -1,10 +1,11 @@ -import React, {useEffect, useRef} from 'react'; +import React, {useCallback, useRef} from 'react'; import {withOnyx} from 'react-native-onyx'; import PropTypes from 'prop-types'; import ONYXKEYS from '../../../ONYXKEYS'; import CONFIG from '../../../CONFIG'; import WebView from 'react-native-webview'; import FullScreenLoadingIndicator from '../../../components/FullscreenLoadingIndicator'; +import * as Session from '../../../libs/actions/Session'; const propTypes = { /** The credentials of the logged in person */ @@ -23,6 +24,24 @@ const renderLoading = () => ; function SAMLSignInPage({credentials}) { const webViewRef = useRef(); const samlLoginURL = `${CONFIG.EXPENSIFY.SAML_URL}?email=${credentials.login}&referer=${CONFIG.EXPENSIFY.EXPENSIFY_CASH_REFERER}`; + + /** + * Handles in-app navigation once we get a response back from Expensify + * + * @param {String} params.type + * @param {String} params.url + */ + const handleNavigationStateChange = useCallback( + ({type, url}) => { + const searchParams = new URLSearchParams(new URL(url).search); + if (searchParams.has('shortLivedAuthToken')) { + const shortLivedAuthToken = searchParams.get('shortLivedAuthToken'); + Session.signInWithShortLivedAuthToken(credentials.login, shortLivedAuthToken); + return; + } + }, + [webViewRef], + ); return ( {console.log("meep meep navigation changed: " + JSON.stringify(ref))}} + onNavigationStateChange={handleNavigationStateChange} /> ); } From 99d4202ea5fd271146a47f77b4459b1498a9a885 Mon Sep 17 00:00:00 2001 From: Getabalew Tesfaye Date: Tue, 17 Oct 2023 17:44:47 +0300 Subject: [PATCH 017/844] use props.report value for amount and name --- src/pages/home/report/ReportActionItem.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index f51b0645cd7f..45621297ed98 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -363,8 +363,8 @@ function ReportActionItem(props) { ); } else if (props.action.actionName === CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENTDEQUEUED) { - const submitterDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(props.personalDetailsList, [props.iouReport.ownerAccountID, 'displayName'], props.iouReport.ownerEmail); - const amount = CurrencyUtils.convertToDisplayString(props.iouReport.total, props.iouReport.currency); + const submitterDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(props.personalDetailsList, [props.report.ownerAccountID, 'displayName'], props.report.ownerEmail); + const amount = CurrencyUtils.convertToDisplayString(props.report.total, props.report.currency); children = ; } else if (props.action.actionName === CONST.REPORT.ACTIONS.TYPE.MODIFIEDEXPENSE) { From 8386979f78ee4b0e3e3e6ebb6c315f1b3e871106 Mon Sep 17 00:00:00 2001 From: David Cardoza Date: Fri, 20 Oct 2023 13:42:47 +0800 Subject: [PATCH 018/844] Create Partner-Billing-Guide https://github.com/Expensify/Expensify/issues/318124 --- .../Partner-Billing-Guide | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 docs/articles/expensify-classic/expensify-partner-program/Partner-Billing-Guide diff --git a/docs/articles/expensify-classic/expensify-partner-program/Partner-Billing-Guide b/docs/articles/expensify-classic/expensify-partner-program/Partner-Billing-Guide new file mode 100644 index 000000000000..f87cdc974cc7 --- /dev/null +++ b/docs/articles/expensify-classic/expensify-partner-program/Partner-Billing-Guide @@ -0,0 +1,89 @@ +--- +title: Partner Billing Guide +description: Understand how Expensify bills partners and their clients +--- + +# Overview + +The ExpensifyApproved! Partner Program offers exclusive billing rates and features tailored for accountants to ensure seamless client management. If you are an accountant or consultant who recommends spend management solutions to your clients, becoming an ExpensifyApproved! Accountant may be a great certification for you. This guide will walk you through the unique billing perks available to ExpensifyApproved! partners, emphasizing the importance of understanding and managing client billing effectively. To learn what perks partners receive, check out the ExpensifyApproved! program details here. + +# Get exclusive partner pricing + +All ExpensifyApproved! Partners are automatically eligible for a special rate of $9/seat monthly, without an annual commitment when they adopt the Expensify Card. This provides flexibility as active users can vary. Here are the specifics on pricing for our Approved! Partners’ clients: +- **Bundled pricing:** US Clients using the Expensify Card get up to a 50% discount, bringing their monthly bill to $9/seat. Reach out to your Partner Manager to discuss exclusive international pricing discount.s +- **Unbundled pricing (or pay-per-use pricing):** Clients not using the Expensify Card are billed at $18/seat monthly. +- **No annual commitment:** Partners pay only for what they use, with no annual subscriptions required. +- **Expensify Card revenue share:** All partners receive a 0.5% revenue share on Expensify Card transactions made by clients. This revenue share can be passed back to the client for an additional discount to offset their Expensify bill + +# Understanding the billing process + +Expensify bills the owner of the expense policy for the activity on that policy. If accountants retain ownership of client policies, they receive the bill and can then re-bill their clients based on individual agreements. + +Each month, Expensify will send a consolidated bill detailing: +- **Pay-per-use seats:** This is the number of active clients and their users for the month. +- **Expensify Card discount**: This amount reflects how much spend is put on your card, which then determines the discount for that month. +- **Total monthly price:** This amount is the overall price of Expensify when using the Expensify Card discount to offset the cost of the pay-per-use seats +- **Policy list:** This is an overview of all client policies with their respective active seats. + +## Consolidated Domain Billing + +If your firm wishes to consolidate all Expensify billing to a single account, the Consolidated Domain Billing feature is your go-to tool. It centralizes payment for all group policies owned by any domain member of your firm. + +### Activating Consolidated Domain Billing: + 1. Claim and verify your firm’s domain. + 2. Navigate to **Settings > Domains > [Domain Name] > Domain Admins** and set a **"Primary Domain Admin"** by using the drop down toggle to select an email address. + 3. Enable **Consolidated Domain Billing** in the same section. + +The Consolidated Domain Billing tool ensures that billing takes place under a single Expensify account associated with your firm. This eliminates the need to add multiple payment cards across various accounts to cover payments for multiple clients. + +## Maintaining a Console of all clients: + +If your firm wants to have a console view of all client policies and domains, you will want to create a single, centralized login to manage all client policies and domains, such as accounting@myfirm.com. + + 1. Create a dedicated email address that will act as the universal policy owner, for example, accounting@myfirm.com. + 2. Register this email with Expensify or your chosen platform and ensure it is verified and secured. + 3. Within each client policy settings, add your centralized email (e.g. accounting@myfirm.com) as a policy admin. + 4. Do the same with each client domain. + + +## Applying Client IDs to a bill + +Using client IDs for Optimized Billing in Expensify: A unique identifier feature for ExpensifyApproved! accountants. Streamline client Workspace recognition and make your billing process more efficient. + +# How to assign a client ID to a workspace + 1. Log in to your account: Ensure you’re using an Approved! accountant account. + 2. Navigate to the desired Workspace: Go to **Settings > Workspaces > [Workspace Name] > Overview**. + 3. Input the identifier: Here, you can input an alphanumeric unique identifier for each client workspace. +**Note:** If a client has multiple workspaces, ensure each workspace has a consistent client ID. + +# How to access and download billing receipts +Accessing Billing **Settings: Go to Settings > Your Account > Payments > Billing History.** +Download the Receipt: Click on **"Download Receipt CSV".** + + +# Deep Dive +Using client IDs for all Workspaces: It's beneficial to use client IDs for all Workspaces to ensure each one is easily recognizable. +Benefits of itemized billing receipts: Employing client IDs offers itemized billing by client, with each itemization detailing unique active users. + +# FAQ + +**Do I automatically get the special billing rate as an ExpensifyApproved! Partner?** +- Yes, when you join the ExpensifyApproved! program, you will automatically get the special billing rate. To join the ExpensifyApproved! Program, you need to enroll in ExpensifyApproved! University. + +**How can I check my billing details?** +- To check your billing details, be on the lookout for your detailed billing statement sent out at the end of each month. + +**Can I pass the bill to my clients?** +- Yes, you can pass the bill on to your clients. If you retain ownership of client policies in Expensify, you can re-bill your clients. If you’d like the client to own the billing of the Expensify, they can take over billing. + +**What if I don't want to use the Expensify Card?** +- If you prefer not to use the Expensify Card, your clients will be billed at $18/seat monthly. + +**Why use client IDs?** +- Client IDs provide a streamlined method to identify and manage policies, especially beneficial when a client has multiple policies. + +**Do I need a client ID for each Workspace?** +- Yes, if you want to ensure seamless identification and billing processes. + +** Where can I see the Billing Receipts?** +- All billing owners receive an emailed PDF of their monthly billing receipt, but a CSV version can also be downloaded from the platform. From b0c9b955e8df53f51281b405aa235fb804c2a92b Mon Sep 17 00:00:00 2001 From: Sibtain Ali Date: Sun, 22 Oct 2023 00:40:27 +0500 Subject: [PATCH 019/844] fix: unread logic --- src/libs/ReportUtils.js | 10 ++++++++++ src/libs/UnreadIndicatorUpdater/index.js | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 75ee6257caab..7b55903bedf4 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -2992,6 +2992,15 @@ function buildTransactionThread(reportAction, moneyRequestReportID) { ); } +/** + * @param {Object} report + * @returns {Boolean} + */ +function isEmptyReport(report) { + const lastVisibleMessage = ReportActionsUtils.getLastVisibleMessage(report.reportID); + return !report.lastMessageText && !report.lastMessageTranslationKey && !lastVisibleMessage.lastMessageText && !lastVisibleMessage.lastMessageTranslationKey; +} + /** * @param {Object} report * @returns {Boolean} @@ -4000,6 +4009,7 @@ export { navigateToDetailsPage, generateReportID, hasReportNameError, + isEmptyReport, isUnread, isUnreadWithMention, buildOptimisticWorkspaceChats, diff --git a/src/libs/UnreadIndicatorUpdater/index.js b/src/libs/UnreadIndicatorUpdater/index.js index 09fa82612314..983fc57c511c 100644 --- a/src/libs/UnreadIndicatorUpdater/index.js +++ b/src/libs/UnreadIndicatorUpdater/index.js @@ -8,7 +8,7 @@ Onyx.connect({ key: ONYXKEYS.COLLECTION.REPORT, waitForCollectionCallback: true, callback: (reportsFromOnyx) => { - const unreadReports = _.filter(reportsFromOnyx, ReportUtils.isUnread); + const unreadReports = _.filter(reportsFromOnyx, (report) => ReportUtils.unread(report) && ReportUtils.isEmptyReport(report)); updateUnread(_.size(unreadReports)); }, }); From 90c915b71daf148b34c3b8ea2282c7fbcff3de3c Mon Sep 17 00:00:00 2001 From: Sibtain Ali Date: Sun, 22 Oct 2023 02:08:23 +0500 Subject: [PATCH 020/844] fix: empty condition --- src/libs/UnreadIndicatorUpdater/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/UnreadIndicatorUpdater/index.js b/src/libs/UnreadIndicatorUpdater/index.js index 983fc57c511c..be11b3cc3a62 100644 --- a/src/libs/UnreadIndicatorUpdater/index.js +++ b/src/libs/UnreadIndicatorUpdater/index.js @@ -8,7 +8,7 @@ Onyx.connect({ key: ONYXKEYS.COLLECTION.REPORT, waitForCollectionCallback: true, callback: (reportsFromOnyx) => { - const unreadReports = _.filter(reportsFromOnyx, (report) => ReportUtils.unread(report) && ReportUtils.isEmptyReport(report)); + const unreadReports = _.filter(reportsFromOnyx, (report) => ReportUtils.isUnread(report) && !ReportUtils.isEmptyReport(report)); updateUnread(_.size(unreadReports)); }, }); From 15294bed03d9df1331630eaeb4cc02a6bbef2aaa Mon Sep 17 00:00:00 2001 From: Sibtain Ali Date: Mon, 23 Oct 2023 01:47:50 +0500 Subject: [PATCH 021/844] feat: mimic lhn --- src/libs/UnreadIndicatorUpdater/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libs/UnreadIndicatorUpdater/index.js b/src/libs/UnreadIndicatorUpdater/index.js index be11b3cc3a62..a2c523d082f3 100644 --- a/src/libs/UnreadIndicatorUpdater/index.js +++ b/src/libs/UnreadIndicatorUpdater/index.js @@ -1,5 +1,6 @@ import _ from 'underscore'; import Onyx from 'react-native-onyx'; +import Navigation from '../Navigation/Navigation'; import ONYXKEYS from '../../ONYXKEYS'; import updateUnread from './updateUnread/index'; import * as ReportUtils from '../ReportUtils'; @@ -8,7 +9,7 @@ Onyx.connect({ key: ONYXKEYS.COLLECTION.REPORT, waitForCollectionCallback: true, callback: (reportsFromOnyx) => { - const unreadReports = _.filter(reportsFromOnyx, (report) => ReportUtils.isUnread(report) && !ReportUtils.isEmptyReport(report)); + const unreadReports = _.filter(reportsFromOnyx, (report) => ReportUtils.isUnread(report) && ReportUtils.shouldReportBeInOptionList(report, Navigation.getTopmostReportId())); updateUnread(_.size(unreadReports)); }, }); From 024ab748dbc7d4371866ee1070a99193199551ec Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Mon, 23 Oct 2023 09:45:06 +0700 Subject: [PATCH 022/844] fix: 30062 --- src/pages/home/ReportScreen.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index 81000c2dab92..9b1a72f90b9f 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -317,7 +317,7 @@ function ReportScreen({ prevOnyxReportID === routeReportID && !onyxReportID && prevReport.statusNum === CONST.REPORT.STATUS.OPEN && - (report.statusNum === CONST.REPORT.STATUS.CLOSED || (!report.statusNum && !prevReport.parentReportID))) + (report.statusNum === CONST.REPORT.STATUS.CLOSED || (!report.statusNum && !prevReport.parentReportID && prevReport.chatType === CONST.REPORT.CHAT_TYPE.POLICY_ROOM))) ) { Navigation.dismissModal(); if (Navigation.getTopmostReportId() === prevOnyxReportID) { From 0168e72eb1427af2bcc9af8c3f36f208e9e70c4a Mon Sep 17 00:00:00 2001 From: Nikki Wines Date: Mon, 23 Oct 2023 11:58:50 -0700 Subject: [PATCH 023/844] include const --- src/pages/signin/SignInPage.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/signin/SignInPage.js b/src/pages/signin/SignInPage.js index dd43970d5412..3ccd2061d794 100644 --- a/src/pages/signin/SignInPage.js +++ b/src/pages/signin/SignInPage.js @@ -100,8 +100,8 @@ function getRenderOptions({hasLogin, hasValidateCode, account, isPrimaryLogin, i const hasEmailDeliveryFailure = Boolean(account.hasEmailDeliveryFailure); // True if the user has SAML required and we haven't already initiated SAML for their account - shouldInitiateSAMLLogin = hasAccount && hasLogin && isSAMLRequired && !hasInitiatedSAMLLogin && account.isLoading; - shouldShowChooseSSOOrMagicCode = hasAccount && hasLogin && isSAMLEnabled && !isSAMLRequired && !isUsingMagicCode; + const shouldInitiateSAMLLogin = hasAccount && hasLogin && isSAMLRequired && !hasInitiatedSAMLLogin && account.isLoading; + const shouldShowChooseSSOOrMagicCode = hasAccount && hasLogin && isSAMLEnabled && !isSAMLRequired && !isUsingMagicCode; // SAML required users may reload the login page after having already entered their login details, in which // case we want to clear their sign in data so they don't end up in an infinite loop redirecting back to their From 9e4b7ae1ba30fe91d62878d4168579b83061b4ea Mon Sep 17 00:00:00 2001 From: Nikki Wines Date: Mon, 23 Oct 2023 18:33:04 -0700 Subject: [PATCH 024/844] style and clean up --- src/pages/signin/SAMLSignInPage/index.native.js | 11 +++++------ src/pages/signin/SignInPage.js | 1 - 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/pages/signin/SAMLSignInPage/index.native.js b/src/pages/signin/SAMLSignInPage/index.native.js index 3825cca0fa97..4b4624b56cde 100644 --- a/src/pages/signin/SAMLSignInPage/index.native.js +++ b/src/pages/signin/SAMLSignInPage/index.native.js @@ -1,11 +1,12 @@ import React, {useCallback, useRef} from 'react'; import {withOnyx} from 'react-native-onyx'; import PropTypes from 'prop-types'; +import WebView from 'react-native-webview'; import ONYXKEYS from '../../../ONYXKEYS'; import CONFIG from '../../../CONFIG'; -import WebView from 'react-native-webview'; -import FullScreenLoadingIndicator from '../../../components/FullscreenLoadingIndicator'; import * as Session from '../../../libs/actions/Session'; +import SAMLLoadingIndicator from '../../../components/SAMLLoadingIndicator'; +import FullScreenLoadingIndicator from '../../../components/FullscreenLoadingIndicator'; const propTypes = { /** The credentials of the logged in person */ @@ -28,11 +29,10 @@ function SAMLSignInPage({credentials}) { /** * Handles in-app navigation once we get a response back from Expensify * - * @param {String} params.type * @param {String} params.url */ const handleNavigationStateChange = useCallback( - ({type, url}) => { + ({url}) => { const searchParams = new URLSearchParams(new URL(url).search); if (searchParams.has('shortLivedAuthToken')) { const shortLivedAuthToken = searchParams.get('shortLivedAuthToken'); @@ -40,11 +40,10 @@ function SAMLSignInPage({credentials}) { return; } }, - [webViewRef], + [credentials.login], ); return ( Date: Mon, 23 Oct 2023 18:33:21 -0700 Subject: [PATCH 025/844] add SAMLLOadingINdicator --- src/components/SAMLLoadingIndicator.js | 43 +++++++++++++++++++ src/pages/signin/SAMLSignInPage/index.js | 35 +-------------- .../signin/SAMLSignInPage/index.native.js | 4 +- 3 files changed, 46 insertions(+), 36 deletions(-) create mode 100644 src/components/SAMLLoadingIndicator.js diff --git a/src/components/SAMLLoadingIndicator.js b/src/components/SAMLLoadingIndicator.js new file mode 100644 index 000000000000..32438c5359c2 --- /dev/null +++ b/src/components/SAMLLoadingIndicator.js @@ -0,0 +1,43 @@ +import _ from 'underscore'; +import React from 'react'; +import {ActivityIndicator, View, StyleSheet} from 'react-native'; +import styles from '../styles/styles'; +import themeColors from '../styles/themes/default'; +import Icon from './Icon'; +import Text from './Text'; +import * as Expensicons from './Icon/Expensicons'; +import * as Illustrations from './Icon/Illustrations'; +import useLocalize from '../hooks/useLocalize'; + +function SAMLLoadingIndicator() { + const {translate} = useLocalize(); + return ( + + + + + + {translate('samlSignIn.launching')} + + {translate('samlSignIn.oneMoment')} + + + + + + + ); +} + +SAMLLoadingIndicator.displayName = 'SAMLLoadingIndicator'; + +export default SAMLLoadingIndicator; diff --git a/src/pages/signin/SAMLSignInPage/index.js b/src/pages/signin/SAMLSignInPage/index.js index 23ce9b93b8cc..f27a3a310597 100644 --- a/src/pages/signin/SAMLSignInPage/index.js +++ b/src/pages/signin/SAMLSignInPage/index.js @@ -1,16 +1,9 @@ import React, {useEffect} from 'react'; import {withOnyx} from 'react-native-onyx'; -import {View} from 'react-native'; import PropTypes from 'prop-types'; import ONYXKEYS from '../../../ONYXKEYS'; import CONFIG from '../../../CONFIG'; -import Icon from '../../../components/Icon'; -import Text from '../../../components/Text'; -import * as Expensicons from '../../../components/Icon/Expensicons'; -import * as Illustrations from '../../../components/Icon/Illustrations'; -import styles from '../../../styles/styles'; -import themeColors from '../../../styles/themes/default'; -import useLocalize from '../../../hooks/useLocalize'; +import SAMLLoadingIndicator from '../../../components/SAMLLoadingIndicator'; const propTypes = { /** The credentials of the logged in person */ @@ -25,36 +18,12 @@ const defaultProps = { }; function SAMLSignInPage({credentials}) { - const {translate} = useLocalize(); - useEffect(() => { window.open(`${CONFIG.EXPENSIFY.SAML_URL}?email=${credentials.login}&referer=${CONFIG.EXPENSIFY.EXPENSIFY_CASH_REFERER}`, '_self'); }, [credentials.login]); return ( - - - - - - {translate('samlSignIn.launching')} - - {translate('samlSignIn.oneMoment')} - - - - - - + ); } diff --git a/src/pages/signin/SAMLSignInPage/index.native.js b/src/pages/signin/SAMLSignInPage/index.native.js index 4b4624b56cde..25affdbf26fd 100644 --- a/src/pages/signin/SAMLSignInPage/index.native.js +++ b/src/pages/signin/SAMLSignInPage/index.native.js @@ -20,10 +20,8 @@ const defaultProps = { credentials: {}, }; -const renderLoading = () => ; function SAMLSignInPage({credentials}) { - const webViewRef = useRef(); const samlLoginURL = `${CONFIG.EXPENSIFY.SAML_URL}?email=${credentials.login}&referer=${CONFIG.EXPENSIFY.EXPENSIFY_CASH_REFERER}`; /** @@ -48,7 +46,7 @@ function SAMLSignInPage({credentials}) { source={{uri: samlLoginURL}} incognito // 'incognito' prop required for Android, issue here https://github.com/react-native-webview/react-native-webview/issues/1352 startInLoadingState - renderLoading={renderLoading} + renderLoading={() => } onNavigationStateChange={handleNavigationStateChange} /> ); From 89dc792ec697c1d12be8ab566c464318e9685eda Mon Sep 17 00:00:00 2001 From: Nikki Wines Date: Mon, 23 Oct 2023 18:51:18 -0700 Subject: [PATCH 026/844] style --- src/components/SAMLLoadingIndicator.js | 3 +-- src/pages/signin/SAMLSignInPage/index.native.js | 4 +--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/components/SAMLLoadingIndicator.js b/src/components/SAMLLoadingIndicator.js index 32438c5359c2..1169436e79e5 100644 --- a/src/components/SAMLLoadingIndicator.js +++ b/src/components/SAMLLoadingIndicator.js @@ -1,6 +1,5 @@ -import _ from 'underscore'; import React from 'react'; -import {ActivityIndicator, View, StyleSheet} from 'react-native'; +import {View, StyleSheet} from 'react-native'; import styles from '../styles/styles'; import themeColors from '../styles/themes/default'; import Icon from './Icon'; diff --git a/src/pages/signin/SAMLSignInPage/index.native.js b/src/pages/signin/SAMLSignInPage/index.native.js index 25affdbf26fd..ea5621a22a65 100644 --- a/src/pages/signin/SAMLSignInPage/index.native.js +++ b/src/pages/signin/SAMLSignInPage/index.native.js @@ -1,4 +1,4 @@ -import React, {useCallback, useRef} from 'react'; +import React, {useCallback} from 'react'; import {withOnyx} from 'react-native-onyx'; import PropTypes from 'prop-types'; import WebView from 'react-native-webview'; @@ -6,7 +6,6 @@ import ONYXKEYS from '../../../ONYXKEYS'; import CONFIG from '../../../CONFIG'; import * as Session from '../../../libs/actions/Session'; import SAMLLoadingIndicator from '../../../components/SAMLLoadingIndicator'; -import FullScreenLoadingIndicator from '../../../components/FullscreenLoadingIndicator'; const propTypes = { /** The credentials of the logged in person */ @@ -35,7 +34,6 @@ function SAMLSignInPage({credentials}) { if (searchParams.has('shortLivedAuthToken')) { const shortLivedAuthToken = searchParams.get('shortLivedAuthToken'); Session.signInWithShortLivedAuthToken(credentials.login, shortLivedAuthToken); - return; } }, [credentials.login], From 526cf8f5f124d62d758491fc8e929a5b92d02b0c Mon Sep 17 00:00:00 2001 From: Nikki Wines Date: Mon, 23 Oct 2023 19:07:06 -0700 Subject: [PATCH 027/844] prettier --- src/pages/signin/SAMLSignInPage/index.js | 4 +--- src/pages/signin/SAMLSignInPage/index.native.js | 9 ++++----- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/pages/signin/SAMLSignInPage/index.js b/src/pages/signin/SAMLSignInPage/index.js index f27a3a310597..283a3113b61b 100644 --- a/src/pages/signin/SAMLSignInPage/index.js +++ b/src/pages/signin/SAMLSignInPage/index.js @@ -22,9 +22,7 @@ function SAMLSignInPage({credentials}) { window.open(`${CONFIG.EXPENSIFY.SAML_URL}?email=${credentials.login}&referer=${CONFIG.EXPENSIFY.EXPENSIFY_CASH_REFERER}`, '_self'); }, [credentials.login]); - return ( - - ); + return ; } SAMLSignInPage.propTypes = propTypes; diff --git a/src/pages/signin/SAMLSignInPage/index.native.js b/src/pages/signin/SAMLSignInPage/index.native.js index ea5621a22a65..afb1dd7d8b4b 100644 --- a/src/pages/signin/SAMLSignInPage/index.native.js +++ b/src/pages/signin/SAMLSignInPage/index.native.js @@ -19,15 +19,14 @@ const defaultProps = { credentials: {}, }; - function SAMLSignInPage({credentials}) { const samlLoginURL = `${CONFIG.EXPENSIFY.SAML_URL}?email=${credentials.login}&referer=${CONFIG.EXPENSIFY.EXPENSIFY_CASH_REFERER}`; /** - * Handles in-app navigation once we get a response back from Expensify - * - * @param {String} params.url - */ + * Handles in-app navigation once we get a response back from Expensify + * + * @param {String} params.url + */ const handleNavigationStateChange = useCallback( ({url}) => { const searchParams = new URLSearchParams(new URL(url).search); From c386ce4a67cdd50a36b3c7b2d8ba5d3730ddd06e Mon Sep 17 00:00:00 2001 From: Nikki Wines Date: Mon, 23 Oct 2023 19:24:27 -0700 Subject: [PATCH 028/844] include platform --- src/pages/signin/SAMLSignInPage/index.native.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/signin/SAMLSignInPage/index.native.js b/src/pages/signin/SAMLSignInPage/index.native.js index afb1dd7d8b4b..ad478c3c0dfd 100644 --- a/src/pages/signin/SAMLSignInPage/index.native.js +++ b/src/pages/signin/SAMLSignInPage/index.native.js @@ -6,6 +6,7 @@ import ONYXKEYS from '../../../ONYXKEYS'; import CONFIG from '../../../CONFIG'; import * as Session from '../../../libs/actions/Session'; import SAMLLoadingIndicator from '../../../components/SAMLLoadingIndicator'; +import getPlatform from '../../../libs/getPlatform'; const propTypes = { /** The credentials of the logged in person */ @@ -20,7 +21,7 @@ const defaultProps = { }; function SAMLSignInPage({credentials}) { - const samlLoginURL = `${CONFIG.EXPENSIFY.SAML_URL}?email=${credentials.login}&referer=${CONFIG.EXPENSIFY.EXPENSIFY_CASH_REFERER}`; + const samlLoginURL = `${CONFIG.EXPENSIFY.SAML_URL}?email=${credentials.login}&referer=${CONFIG.EXPENSIFY.EXPENSIFY_CASH_REFERER}&platform=${getPlatform()}`; /** * Handles in-app navigation once we get a response back from Expensify From 20dea7c2c9567ff9a645880098faf9ae8571d596 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Tue, 24 Oct 2023 05:26:32 -0300 Subject: [PATCH 029/844] Rename file to TSX --- src/components/{BigNumberPad.js => BigNumberPad.tsx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/components/{BigNumberPad.js => BigNumberPad.tsx} (100%) diff --git a/src/components/BigNumberPad.js b/src/components/BigNumberPad.tsx similarity index 100% rename from src/components/BigNumberPad.js rename to src/components/BigNumberPad.tsx From 63559ae0a6ff3d98ef179f32b91e8745f3842eca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Tue, 24 Oct 2023 06:40:05 -0300 Subject: [PATCH 030/844] Starts migration of BigNumberPad --- src/components/BigNumberPad.tsx | 57 +++++++++++++++------------------ 1 file changed, 26 insertions(+), 31 deletions(-) diff --git a/src/components/BigNumberPad.tsx b/src/components/BigNumberPad.tsx index 5587808a09fd..aeadce2c3025 100644 --- a/src/components/BigNumberPad.tsx +++ b/src/components/BigNumberPad.tsx @@ -1,29 +1,22 @@ import React, {useState} from 'react'; import {View} from 'react-native'; -import _ from 'underscore'; -import PropTypes from 'prop-types'; +import useWindowDimensions from '../hooks/useWindowDimensions'; +import ControlSelection from '../libs/ControlSelection'; import styles from '../styles/styles'; import Button from './Button'; -import ControlSelection from '../libs/ControlSelection'; -import withLocalize, {withLocalizePropTypes} from './withLocalize'; -import useWindowDimensions from '../hooks/useWindowDimensions'; +import withLocalize from './withLocalize'; -const propTypes = { +type BigNumberPadProps = { /** Callback to inform parent modal with key pressed */ - numberPressed: PropTypes.func.isRequired, + numberPressed: (key: string) => void; /** Callback to inform parent modal whether user is long pressing the "<" (backspace) button */ - longPressHandlerStateChanged: PropTypes.func, + longPressHandlerStateChanged?: (isUserLongPressingBackspace: boolean) => void; /** Used to locate this view from native classes. */ - nativeID: PropTypes.string, + nativeID?: string; - ...withLocalizePropTypes, -}; - -const defaultProps = { - longPressHandlerStateChanged: () => {}, - nativeID: 'numPadView', + // TODO: Add withLocalize props (withLocalizePropTypes) }; const padNumbers = [ @@ -33,57 +26,61 @@ const padNumbers = [ ['.', '0', '<'], ]; -function BigNumberPad(props) { - const [timer, setTimer] = useState(null); +function BigNumberPad({numberPressed, longPressHandlerStateChanged = () => {}, nativeID = 'numPadView'}: BigNumberPadProps) { + const [timer, setTimer] = useState(null); const {isExtraSmallScreenHeight} = useWindowDimensions(); /** * Handle long press key on number pad. * Only handles the '<' key and starts the continuous input timer. - * - * @param {String} key */ - const handleLongPress = (key) => { + const handleLongPress = (key: string) => { if (key !== '<') { return; } - props.longPressHandlerStateChanged(true); + longPressHandlerStateChanged(true); const newTimer = setInterval(() => { - props.numberPressed(key); + numberPressed(key); }, 100); + setTimer(newTimer); }; return ( - {_.map(padNumbers, (row, rowIndex) => ( + {padNumbers.map((row, rowIndex) => ( - {_.map(row, (column, columnIndex) => { + {row.map((column, columnIndex) => { // Adding margin between buttons except first column to // avoid unccessary space before the first column. const marginLeft = columnIndex > 0 ? styles.ml3 : {}; + return (