From 9117c61bae77f3cd18e3d7668bbd4e800bc60b42 Mon Sep 17 00:00:00 2001 From: Viktoryia Kliushun Date: Tue, 10 Oct 2023 09:22:16 +0200 Subject: [PATCH 01/92] [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 02/92] 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 0a8b4ad6177ff5a2fe0f1b7cd16d176b35ec8fcf Mon Sep 17 00:00:00 2001 From: Viktoryia Kliushun Date: Thu, 12 Oct 2023 10:25:36 +0200 Subject: [PATCH 03/92] 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 e57cfbc2118ed4045044a539653506374586c42e Mon Sep 17 00:00:00 2001 From: Nam Le Date: Tue, 7 Nov 2023 01:41:22 +0700 Subject: [PATCH 04/92] translate invite member to room --- src/languages/en.ts | 4 +++ src/languages/es.ts | 4 +++ src/libs/ReportActionsUtils.ts | 11 +++++++ src/libs/SidebarUtils.ts | 10 +++--- src/libs/actions/ReportActions.ts | 31 ++++++++++++++++++- .../home/report/ReportActionItemMessage.js | 10 +++++- 6 files changed, 63 insertions(+), 7 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index c186a1fffedf..16adcc08e41a 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -266,6 +266,10 @@ export default { tbd: 'TBD', selectCurrency: 'Select a currency', card: 'Card', + user: 'user', + users: 'users', + invited: 'invited', + removed: 'removed', }, location: { useCurrent: 'Use current location', diff --git a/src/languages/es.ts b/src/languages/es.ts index a0a30bcf4141..e3d0482d5cfa 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -256,6 +256,10 @@ export default { tbd: 'Por determinar', selectCurrency: 'Selecciona una moneda', card: 'Tarjeta', + user: 'usuario', + users: 'usuarias', + invited: 'invitada', + removed: 'eliminado', }, location: { useCurrent: 'Usar ubicación actual', diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 11e11f549682..cef6265ad6fe 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -616,6 +616,15 @@ function isNotifiableReportAction(reportAction: OnyxEntry): boolea return actions.includes(reportAction.actionName); } +function isMemberRoomChangeLog(reportAction: OnyxEntry): boolean { + if (!reportAction) { + return false; + } + + const actions: ActionName[] = [CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.INVITE_TO_ROOM, CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.REMOVE_FROM_ROOM]; + return actions.includes(reportAction.actionName); +} + export { extractLinksFromMessageHtml, getAllReportActions, @@ -657,4 +666,6 @@ export { shouldReportActionBeVisible, shouldReportActionBeVisibleAsLastAction, getFirstVisibleReportActionID, + isMemberRoomChangeLog, + isMemberPolicyChangeLog, }; diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 80ed96d25d65..871b0ef6c9a8 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -437,17 +437,17 @@ function getOptionData( const targetAccountIDs = lastAction?.originalMessage?.targetAccountIDs ?? []; const verb = lastAction.actionName === CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.INVITE_TO_ROOM || lastAction.actionName === CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.INVITE_TO_ROOM - ? 'invited' - : 'removed'; - const users = targetAccountIDs.length > 1 ? 'users' : 'user'; + ? Localize.translate(preferredLocale, 'common.invited') + : Localize.translate(preferredLocale, 'common.removed'); + const users = Localize.translate(preferredLocale, targetAccountIDs.length > 1 ? 'common.users' : 'common.user'); result.alternateText = `${verb} ${targetAccountIDs.length} ${users}`; const roomName = lastAction?.originalMessage?.roomName ?? ''; if (roomName) { const preposition = lastAction.actionName === CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.INVITE_TO_ROOM || lastAction.actionName === CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.INVITE_TO_ROOM - ? ' to' - : ' from'; + ? ` ${Localize.translate(preferredLocale, 'common.to')}` + : ` ${Localize.translate(preferredLocale, 'common.from')}`; result.alternateText += `${preposition} ${roomName}`; } } else if (lastAction?.actionName !== CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW && lastActorDisplayName && lastMessageTextFromReport) { diff --git a/src/libs/actions/ReportActions.ts b/src/libs/actions/ReportActions.ts index b9cea498a3fa..c95a6f80057d 100644 --- a/src/libs/actions/ReportActions.ts +++ b/src/libs/actions/ReportActions.ts @@ -1,9 +1,10 @@ import Onyx from 'react-native-onyx'; +import * as Localize from '@libs/Localize'; import * as ReportActionUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import ReportAction from '@src/types/onyx/ReportAction'; +import ReportAction, {Message} from '@src/types/onyx/ReportAction'; function clearReportActionErrors(reportID: string, reportAction: ReportAction) { const originalReportID = ReportUtils.getOriginalReportID(reportID, reportAction); @@ -34,7 +35,35 @@ function clearReportActionErrors(reportID: string, reportAction: ReportAction) { }); } +/** + * @param originalMessage + * @param targetAccountIDs + * @returns + */ +function getReportActionMessageRoomChange(originalMessage: Message, targetAccountIDs: number[]) { + if (targetAccountIDs.length === 0) { + return originalMessage; + } + + const mentionTags = targetAccountIDs.map((accountID, index) => { + if (index === targetAccountIDs.length - 1 && index !== 0) { + return `${Localize.translateLocal('common.and')} `; + } + return `${targetAccountIDs.length > 1 ? ', ' : ''}`; + }); + + const html = `${Localize.translateLocal('common.invited')} ${mentionTags.join('')} `; + + const message: Message = { + ...originalMessage, + html, + }; + + return message; +} + export { // eslint-disable-next-line import/prefer-default-export clearReportActionErrors, + getReportActionMessageRoomChange, }; diff --git a/src/pages/home/report/ReportActionItemMessage.js b/src/pages/home/report/ReportActionItemMessage.js index 4c6603c052a3..f3222adc0829 100644 --- a/src/pages/home/report/ReportActionItemMessage.js +++ b/src/pages/home/report/ReportActionItemMessage.js @@ -7,6 +7,7 @@ import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import styles from '@styles/styles'; +import * as ReportActions from '@userActions/ReportActions'; import CONST from '@src/CONST'; import ReportActionItemFragment from './ReportActionItemFragment'; import reportActionPropTypes from './reportActionPropTypes'; @@ -37,9 +38,16 @@ const defaultProps = { }; function ReportActionItemMessage(props) { - const messages = _.compact(props.action.previousMessage || props.action.message); + let messages = _.compact(props.action.previousMessage || props.action.message); const isAttachment = ReportUtils.isReportMessageAttachment(_.last(messages)); const isIOUReport = ReportActionsUtils.isMoneyRequestAction(props.action); + + const isMemberChangeLog = ReportActionsUtils.isMemberRoomChangeLog(props.action); + if (isMemberChangeLog) { + const targetAccountIDs = props.action.originalMessage.targetAccountIDs; + messages = [ReportActions.getReportActionMessageRoomChange(messages[0], targetAccountIDs)]; + } + let iouMessage; if (isIOUReport) { const iouReportID = lodashGet(props.action, 'originalMessage.IOUReportID'); From 2cf8aefb931d69c9ae0ad47d97ff910a6d92a348 Mon Sep 17 00:00:00 2001 From: Nam Le Date: Tue, 7 Nov 2023 09:01:42 +0700 Subject: [PATCH 05/92] fix test --- src/libs/ReportActionsUtils.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index cef6265ad6fe..3cd7abfdb682 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -667,5 +667,4 @@ export { shouldReportActionBeVisibleAsLastAction, getFirstVisibleReportActionID, isMemberRoomChangeLog, - isMemberPolicyChangeLog, }; From a31bdd1af55ee0a617699e5bdf788a93940b96b7 Mon Sep 17 00:00:00 2001 From: Nam Le Date: Tue, 7 Nov 2023 17:39:25 +0700 Subject: [PATCH 06/92] move translate --- src/languages/en.ts | 10 ++++++---- src/languages/es.ts | 10 ++++++---- src/libs/SidebarUtils.ts | 12 ++++++------ src/libs/actions/ReportActions.ts | 2 +- 4 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 16adcc08e41a..a1f054f0100c 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -266,10 +266,6 @@ export default { tbd: 'TBD', selectCurrency: 'Select a currency', card: 'Card', - user: 'user', - users: 'users', - invited: 'invited', - removed: 'removed', }, location: { useCurrent: 'Use current location', @@ -1511,6 +1507,12 @@ export default { invitePeople: 'Invite new members', genericFailureMessage: 'An error occurred inviting the user to the workspace, please try again.', pleaseEnterValidLogin: `Please ensure the email or phone number is valid (e.g. ${CONST.EXAMPLE_PHONE_NUMBER}).`, + user: 'user', + users: 'users', + invited: 'invited', + removed: 'removed', + to: 'to', + from: 'from', }, inviteMessage: { inviteMessageTitle: 'Add message', diff --git a/src/languages/es.ts b/src/languages/es.ts index e3d0482d5cfa..cd5d6a7d435d 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -256,10 +256,6 @@ export default { tbd: 'Por determinar', selectCurrency: 'Selecciona una moneda', card: 'Tarjeta', - user: 'usuario', - users: 'usuarias', - invited: 'invitada', - removed: 'eliminado', }, location: { useCurrent: 'Usar ubicación actual', @@ -1532,6 +1528,12 @@ export default { invitePeople: 'Invitar nuevos miembros', genericFailureMessage: 'Se produjo un error al invitar al usuario al espacio de trabajo. Vuelva a intentarlo..', pleaseEnterValidLogin: `Asegúrese de que el correo electrónico o el número de teléfono sean válidos (p. ej. ${CONST.EXAMPLE_PHONE_NUMBER}).`, + user: 'usuario', + users: 'usuarias', + invited: 'invitada', + removed: 'eliminado', + to: 'a', + from: 'de' }, inviteMessage: { inviteMessageTitle: 'Añadir un mensaje', diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 871b0ef6c9a8..ee5916b66b4c 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -437,17 +437,17 @@ function getOptionData( const targetAccountIDs = lastAction?.originalMessage?.targetAccountIDs ?? []; const verb = lastAction.actionName === CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.INVITE_TO_ROOM || lastAction.actionName === CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.INVITE_TO_ROOM - ? Localize.translate(preferredLocale, 'common.invited') - : Localize.translate(preferredLocale, 'common.removed'); - const users = Localize.translate(preferredLocale, targetAccountIDs.length > 1 ? 'common.users' : 'common.user'); + ? Localize.translate(preferredLocale, 'workspace.invite.invited') + : Localize.translate(preferredLocale, 'workspace.invite.removed'); + const users = Localize.translate(preferredLocale, targetAccountIDs.length > 1 ? 'workspace.invite.users' : 'workspace.invite.user'); result.alternateText = `${verb} ${targetAccountIDs.length} ${users}`; const roomName = lastAction?.originalMessage?.roomName ?? ''; if (roomName) { const preposition = lastAction.actionName === CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.INVITE_TO_ROOM || lastAction.actionName === CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.INVITE_TO_ROOM - ? ` ${Localize.translate(preferredLocale, 'common.to')}` - : ` ${Localize.translate(preferredLocale, 'common.from')}`; + ? ` ${Localize.translate(preferredLocale, 'workspace.invite.to')}` + : ` ${Localize.translate(preferredLocale, 'workspace.invite.from')}`; result.alternateText += `${preposition} ${roomName}`; } } else if (lastAction?.actionName !== CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW && lastActorDisplayName && lastMessageTextFromReport) { @@ -469,7 +469,7 @@ function getOptionData( return `${formattedText}.`; } if (index === displayNamesWithTooltips.length - 2) { - return `${formattedText} ${Localize.translate(preferredLocale, 'common.and')}`; + return `${formattedText} ${Localize.translate(preferredLocale, 'workspace.common.and')}`; } if (index < displayNamesWithTooltips.length - 2) { return `${formattedText},`; diff --git a/src/libs/actions/ReportActions.ts b/src/libs/actions/ReportActions.ts index c95a6f80057d..d0a276032ebe 100644 --- a/src/libs/actions/ReportActions.ts +++ b/src/libs/actions/ReportActions.ts @@ -52,7 +52,7 @@ function getReportActionMessageRoomChange(originalMessage: Message, targetAccoun return `${targetAccountIDs.length > 1 ? ', ' : ''}`; }); - const html = `${Localize.translateLocal('common.invited')} ${mentionTags.join('')} `; + const html = `${Localize.translateLocal('workspace.invite.invited')} ${mentionTags.join('')} `; const message: Message = { ...originalMessage, From 8c28a446b8274ce24395f3aeb424ffa56508820d Mon Sep 17 00:00:00 2001 From: Nam Le Date: Tue, 7 Nov 2023 17:46:21 +0700 Subject: [PATCH 07/92] fix translate key --- src/libs/SidebarUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index e7fca8ef9971..ecea8776ac7a 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -480,7 +480,7 @@ function getOptionData( return `${formattedText}.`; } if (index === displayNamesWithTooltips.length - 2) { - return `${formattedText} ${Localize.translate(preferredLocale, 'workspace.common.and')}`; + return `${formattedText} ${Localize.translate(preferredLocale, 'common.and')}`; } if (index < displayNamesWithTooltips.length - 2) { return `${formattedText},`; From 5296fa5c0864fc6c2108164c5990fd8ab4fe7f76 Mon Sep 17 00:00:00 2001 From: Nam Le Date: Tue, 7 Nov 2023 17:52:44 +0700 Subject: [PATCH 08/92] fix lint --- src/languages/es.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/languages/es.ts b/src/languages/es.ts index bde5195cd4c7..3606adfeafeb 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1532,7 +1532,7 @@ export default { invited: 'invitada', removed: 'eliminado', to: 'a', - from: 'de' + from: 'de', }, inviteMessage: { inviteMessageTitle: 'Añadir un mensaje', From ea9322136396092a53d6804858786c59924c40a3 Mon Sep 17 00:00:00 2001 From: Viktoryia Kliushun Date: Mon, 13 Nov 2023 09:26:33 +0100 Subject: [PATCH 09/92] Resolve conflicts after merging main; partly fix TS issues --- src/ONYXKEYS.ts | 1 + src/SCREENS.ts | 19 +- .../Navigation/AppNavigator/AuthScreens.tsx | 106 ++++---- .../AppNavigator/ModalStackNavigators.js | 251 ------------------ .../AppNavigator/ModalStackNavigators.tsx | 55 ++-- ...igator.js => BaseCentralPaneNavigator.tsx} | 3 +- .../{index.native.js => index.native.tsx} | 0 .../{index.js => index.tsx} | 0 .../AppNavigator/Navigators/Overlay.tsx | 11 +- .../Navigators/RightModalNavigator.tsx | 9 +- .../Navigation/AppNavigator/PublicScreens.tsx | 2 +- .../AppNavigator/ReportScreenIDSetter.ts | 19 +- .../AppNavigator/ReportScreenWrapper.js | 45 ---- .../AppNavigator/ReportScreenWrapper.tsx | 13 +- .../CustomRouter.ts | 34 ++- .../createCustomStackNavigator/index.js | 91 ------- .../{index.native.js => index.native.tsx} | 40 +-- .../createCustomStackNavigator/index.tsx | 47 +++- src/libs/Navigation/AppNavigator/index.tsx | 4 +- src/libs/Navigation/AppNavigator/types.ts | 11 +- src/libs/actions/App.js | 2 +- .../getNavigationModalCardStyles/types.ts | 4 +- src/types/onyx/DemoInfo.ts | 7 + src/types/onyx/index.ts | 2 + 24 files changed, 219 insertions(+), 557 deletions(-) delete mode 100644 src/libs/Navigation/AppNavigator/ModalStackNavigators.js rename src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator/{BaseCentralPaneNavigator.js => BaseCentralPaneNavigator.tsx} (88%) rename src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator/{index.native.js => index.native.tsx} (100%) rename src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator/{index.js => index.tsx} (100%) delete mode 100644 src/libs/Navigation/AppNavigator/ReportScreenWrapper.js delete mode 100644 src/libs/Navigation/AppNavigator/createCustomStackNavigator/index.js rename src/libs/Navigation/AppNavigator/createCustomStackNavigator/{index.native.js => index.native.tsx} (51%) create mode 100644 src/types/onyx/DemoInfo.ts diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index a69bc985b550..8593b1b8cbaf 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -410,6 +410,7 @@ type OnyxValues = { [ONYXKEYS.MAPBOX_ACCESS_TOKEN]: OnyxTypes.MapboxAccessToken; [ONYXKEYS.ONYX_UPDATES_FROM_SERVER]: OnyxTypes.OnyxUpdatesFromServer; [ONYXKEYS.ONYX_UPDATES_LAST_UPDATE_ID_APPLIED_TO_CLIENT]: number; + [ONYXKEYS.DEMO_INFO]: OnyxTypes.DemoInfo; [ONYXKEYS.MAX_CANVAS_AREA]: number; [ONYXKEYS.MAX_CANVAS_HEIGHT]: number; [ONYXKEYS.MAX_CANVAS_WIDTH]: number; diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 6193020173b7..e3930aa81eda 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -40,6 +40,9 @@ export default { WALLET_TRANSFER_BALANCE: 'Settings_Wallet_Transfer_Balance', WALLET_CHOOSE_TRANSFER_ACCOUNT: 'Settings_Wallet_Choose_Transfer_Account', WALLET_ENABLE_PAYMENTS: 'Settings_Wallet_EnablePayments', + WALLET_CARD_ACTIVATE: 'Settings_Wallet_Card_Activate', + WALLET_REPORT_VIRTUAL_CARD_FRAUD: 'Settings_Wallet_ReportVirtualCardFraud', + WALLET_CARDS_DIGITAL_DETAILS_UPDATE_ADDRESS: 'Settings_Wallet_Cards_Digital_Details_Update_Address', ADD_DEBIT_CARD: 'Settings_Add_Debit_Card', ADD_BANK_ACCOUNT: 'Settings_Add_Bank_Account', PREFERENCES_PRIORITY_MODE: 'Settings_Preferences_PriorityMode', @@ -47,6 +50,7 @@ export default { CLOSE: 'Settings_Close', STATUS_SET: 'Settings_Status_Set', TWO_FACTOR_AUTH: 'Settings_TwoFactorAuth', + REPORT_CARD_LOST_OR_DAMAGED: 'Settings_ReportCardLostOrDamaged', }, SAVE_THE_WORLD: { ROOT: 'SaveTheWorld_Root', @@ -73,6 +77,8 @@ export default { EDIT_REQUEST: 'EditRequest', SIGN_IN: 'SignIn', PRIVATE_NOTES: 'Private_Notes', + ROOM_MEMBERS: 'RoomMembers', + ROOM_INVITE: 'RoomInvite', }, SIGN_IN_WITH_APPLE_DESKTOP: 'AppleSignInDesktop', SIGN_IN_WITH_GOOGLE_DESKTOP: 'GoogleSignInDesktop', @@ -147,6 +153,7 @@ export default { MEMBERS: 'Workspace_Members', INVITE: 'Workspace_Invite', INVITE_MESSAGE: 'Workspace_Invite_Message', + CURRENCY: 'Workspace_Settings_Currency', }, EDIT_REQUEST: { @@ -163,17 +170,27 @@ export default { REIMBURSEMENT_ACCOUNT_ROOT: 'Reimbursement_Account_Root', WALLET_STATEMENT_ROOT: 'WalletStatement_Root', SIGN_IN_ROOT: 'SignIn_Root', - SPLIT_DETAILS_ROOT: 'SplitDetails_Root', + + SPLIT_DETAILS: { + ROOT: 'SplitDetails_Root', + EDIT_REQUEST: 'SplitDetails_Edit_Request', + EDIT_CURRENCY: 'SplitDetails_Edit_Currency', + }, + DETAILS_ROOT: 'Details_Root', PROFILE_ROOT: 'Profile_Root', REPORT_WELCOME_MESSAGE_ROOT: 'Report_WelcomeMessage_Root', REPORT_PARTICIPANTS_ROOT: 'ReportParticipants_Root', + ROOM_MEMBERS_ROOT: 'RoomMembers_Root', + ROOM_INVITE_ROOT: 'RoomInvite_Root', SEARCH_ROOT: 'Search_Root', NEW_CHAT_ROOT: 'NewChat_Root', FLAG_COMMENT_ROOT: 'FlagComment_Root', REIMBURSEMENT_ACCOUNT: 'ReimbursementAccount', GET_ASSISTANCE: 'GetAssistance', + KEYBOARD_SHORTCUTS: 'KeyboardShortcuts', + // Iframe screens from olddot HOME_OLDDOT: 'Home_OLDDOT', diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.tsx b/src/libs/Navigation/AppNavigator/AuthScreens.tsx index 33ddd77ed8c8..3017c2e8e884 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.tsx +++ b/src/libs/Navigation/AppNavigator/AuthScreens.tsx @@ -1,9 +1,9 @@ import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; import React, {memo, useEffect, useRef} from 'react'; import {View} from 'react-native'; -import Onyx, {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; +import KeyCommand from 'react-native-key-command'; +import Onyx, {OnyxEntry, withOnyx} from 'react-native-onyx'; +import {ValueOf} from 'type-fest'; import useWindowDimensions from '@hooks/useWindowDimensions'; import KeyboardShortcut from '@libs/KeyboardShortcut'; import getCurrentUrl from '@libs/Navigation/currentUrl'; @@ -12,6 +12,7 @@ import NetworkConnection from '@libs/NetworkConnection'; import * as Pusher from '@libs/Pusher/pusher'; import PusherConnectionManager from '@libs/PusherConnectionManager'; import * as SessionUtils from '@libs/SessionUtils'; +import {AuthScreensStackParamList} from '@navigation/AppNavigator/types'; import DemoSetupPage from '@pages/DemoSetupPage'; import NotFoundPage from '@pages/ErrorPage/NotFoundPage'; import DesktopSignInRedirectPage from '@pages/signin/DesktopSignInRedirectPage'; @@ -30,27 +31,46 @@ import NAVIGATORS from '@src/NAVIGATORS'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import SCREENS from '@src/SCREENS'; +import * as OnyxTypes from '@src/types/onyx'; +import type {Timezone} from '@src/types/onyx/PersonalDetails'; import createCustomStackNavigator from './createCustomStackNavigator'; import defaultScreenOptions from './defaultScreenOptions'; import getRootNavigatorScreenOptions from './getRootNavigatorScreenOptions'; import CentralPaneNavigator from './Navigators/CentralPaneNavigator'; import RightModalNavigator from './Navigators/RightModalNavigator'; -const loadReportAttachments = () => require('../../../pages/home/report/ReportAttachments').default; -const loadSidebarScreen = () => require('../../../pages/home/sidebar/SidebarScreen').default; -const loadValidateLoginPage = () => require('../../../pages/ValidateLoginPage').default; -const loadLogOutPreviousUserPage = () => require('../../../pages/LogOutPreviousUserPage').default; -const loadConciergePage = () => require('../../../pages/ConciergePage').default; +type AuthScreensProps = { + /** Session of currently logged in user */ + session: OnyxEntry; + + /** The report ID of the last opened public room as anonymous user */ + lastOpenedPublicRoomID: OnyxEntry; -let timezone; -let currentAccountID; -let isLoadingApp; + /** 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; + + /** Information about any currently running demos */ + demoInfo: OnyxEntry; +}; + +const loadReportAttachments = () => require('../../../pages/home/report/ReportAttachments').default as React.ComponentType; +const loadSidebarScreen = () => require('../../../pages/home/sidebar/SidebarScreen').default as React.ComponentType; +const loadValidateLoginPage = () => require('../../../pages/ValidateLoginPage').default as React.ComponentType; +const loadLogOutPreviousUserPage = () => require('../../../pages/LogOutPreviousUserPage').default as React.ComponentType; +const loadConciergePage = () => require('../../../pages/ConciergePage').default as React.ComponentType; + +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; } @@ -71,12 +91,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?.automatic && timezone?.selected !== currentTimezone) { timezone.selected = currentTimezone; PersonalDetails.updateAutomaticTimezone({ automatic: true, @@ -89,11 +109,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 // When modal screen is focused, update modal visibility in Onyx @@ -108,40 +128,7 @@ 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, - - /** Opt-in experimental mode that prevents certain Onyx keys from persisting to disk */ - isUsingMemoryOnlyKeys: PropTypes.bool, - - /** The last Onyx update ID was applied to the client */ - lastUpdateIDAppliedToClient: PropTypes.number, - - /** Information about any currently running demos */ - demoInfo: PropTypes.shape({ - money2020: PropTypes.shape({ - isBeginningDemo: PropTypes.bool, - }), - }), -}; - -const defaultProps = { - isUsingMemoryOnlyKeys: false, - session: { - email: null, - }, - lastOpenedPublicRoomID: null, - lastUpdateIDAppliedToClient: null, - demoInfo: {}, -}; - -function AuthScreens({isUsingMemoryOnlyKeys, lastUpdateIDAppliedToClient, session, lastOpenedPublicRoomID, demoInfo}) { +function AuthScreens({isUsingMemoryOnlyKeys = null, lastUpdateIDAppliedToClient = null, session = {email: undefined}, lastOpenedPublicRoomID = null, demoInfo = null}: AuthScreensProps) { const {isSmallScreenWidth} = useWindowDimensions(); const screenOptions = getRootNavigatorScreenOptions(isSmallScreenWidth); const isInitialRender = useRef(true); @@ -156,7 +143,7 @@ function AuthScreens({isUsingMemoryOnlyKeys, lastUpdateIDAppliedToClient, sessio const searchShortcutConfig = CONST.KEYBOARD_SHORTCUTS.SEARCH; const chatShortcutConfig = CONST.KEYBOARD_SHORTCUTS.NEW_CHAT; const currentUrl = getCurrentUrl(); - const isLoggingInAsNewUser = SessionUtils.isLoggingInAsNewUser(currentUrl, session.email); + const isLoggingInAsNewUser = SessionUtils.isLoggingInAsNewUser(currentUrl, session?.email ?? ''); const shouldGetAllData = isUsingMemoryOnlyKeys || SessionUtils.didUserLogInDuringSession() || isLoggingInAsNewUser; // Sign out the current user if we're transitioning with a different user const isTransitioning = currentUrl.includes(ROUTES.TRANSITION_BETWEEN_APPS); @@ -193,6 +180,9 @@ function AuthScreens({isUsingMemoryOnlyKeys, lastUpdateIDAppliedToClient, sessio App.reconnectApp(lastUpdateIDAppliedToClient); } + // TODO: remove when App is merged + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore App.setUpPoliciesAndNavigate(session); App.redirectThirdPartyDesktopSignIn(); @@ -221,7 +211,7 @@ function AuthScreens({isUsingMemoryOnlyKeys, lastUpdateIDAppliedToClient, sessio }); }, shortcutsOverviewShortcutConfig.descriptionKey, - shortcutsOverviewShortcutConfig.modifiers, + shortcutsOverviewShortcutConfig.modifiers as unknown as string[], true, ); @@ -234,7 +224,7 @@ function AuthScreens({isUsingMemoryOnlyKeys, lastUpdateIDAppliedToClient, sessio Modal.close(Session.checkIfActionIsAllowed(() => Navigation.navigate(ROUTES.SEARCH))); }, shortcutsOverviewShortcutConfig.descriptionKey, - shortcutsOverviewShortcutConfig.modifiers, + shortcutsOverviewShortcutConfig.modifiers as unknown as string[], true, ); @@ -244,7 +234,7 @@ function AuthScreens({isUsingMemoryOnlyKeys, lastUpdateIDAppliedToClient, sessio Modal.close(Session.checkIfActionIsAllowed(() => Navigation.navigate(ROUTES.NEW))); }, chatShortcutConfig.descriptionKey, - chatShortcutConfig.modifiers, + chatShortcutConfig.modifiers as unknown as string[], true, ); @@ -263,12 +253,10 @@ function AuthScreens({isUsingMemoryOnlyKeys, lastUpdateIDAppliedToClient, sessio true); -export default withOnyx({ +export default 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 2f0a75a02cc3..000000000000 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js +++ /dev/null @@ -1,251 +0,0 @@ -import {CardStyleInterpolators, createStackNavigator} from '@react-navigation/stack'; -import React from 'react'; -import _ from 'underscore'; -import styles from '@styles/styles'; -import SCREENS from '@src/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(); - - function ModalStack() { - return ( - - {_.map(screens, (getComponent, name) => ( - - ))} - - ); - } - - ModalStack.displayName = 'ModalStack'; - - return ModalStack; -} - -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, - SplitDetails_Edit_Request: () => require('../../../pages/EditSplitBillPage').default, - SplitDetails_Edit_Currency: () => require('../../../pages/iou/IOUCurrencySelection').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 RoomMembersModalStackNavigator = createModalStackNavigator({ - RoomMembers_Root: () => require('../../../pages/RoomMembersPage').default, -}); - -const RoomInviteModalStackNavigator = createModalStackNavigator({ - RoomInvite_Root: () => require('../../../pages/RoomInvitePage').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_Cards_Digital_Details_Update_Address: () => require('../../../pages/settings/Profile/PersonalDetails/AddressPage').default, - Settings_Wallet_DomainCards: () => require('../../../pages/settings/Wallet/ExpensifyCardPage').default, - Settings_Wallet_ReportVirtualCardFraud: () => require('../../../pages/settings/Wallet/ReportVirtualCardFraudPage').default, - Settings_Wallet_Card_Activate: () => require('../../../pages/settings/Wallet/ActivatePhysicalCardPage').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_Settings_Currency: () => require('../../../pages/workspace/WorkspaceSettingsCurrencyPage').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, - Settings_ReportCardLostOrDamaged: () => require('../../../pages/settings/Wallet/ReportCardLostPage').default, - KeyboardShortcuts: () => require('../../../pages/KeyboardShortcutsPage').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, - RoomMembersModalStackNavigator, - RoomInviteModalStackNavigator, -}; diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx index 9e8cf9680e73..15fd1dbf1516 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx @@ -1,7 +1,7 @@ +import {CardStyleInterpolators, createStackNavigator} from '@react-navigation/stack'; import React from 'react'; -import {createStackNavigator, CardStyleInterpolators} from '@react-navigation/stack'; -import styles from '../../../styles/styles'; -import SCREENS from '../../../SCREENS'; +import styles from '@styles/styles'; +import SCREENS from '@src/SCREENS'; const defaultSubRouteOptions = { cardStyle: styles.navigationScreenCardStyle, @@ -18,17 +18,24 @@ type Screens = Record React.ComponentType>; */ function createModalStackNavigator(screens: Screens): () => React.JSX.Element { const ModalStackNavigator = createStackNavigator(); - return () => ( - - {Object.keys(screens).map((name) => ( - - ))} - - ); + + function ModalStack() { + return ( + + {Object.keys(screens).map((name) => ( + + ))} + + ); + } + + ModalStack.displayName = 'ModalStack'; + + return ModalStack; } const MoneyRequestModalStackNavigator = createModalStackNavigator({ @@ -52,7 +59,9 @@ const MoneyRequestModalStackNavigator = createModalStackNavigator({ }); const SplitDetailsModalStackNavigator = createModalStackNavigator({ - [SCREENS.SPLIT_DETAILS_ROOT]: () => require('../../../pages/iou/SplitBillDetailsPage').default as React.ComponentType, + [SCREENS.SPLIT_DETAILS.ROOT]: () => require('../../../pages/iou/SplitBillDetailsPage').default as React.ComponentType, + [SCREENS.SPLIT_DETAILS.EDIT_REQUEST]: () => require('../../../pages/EditSplitBillPage').default as React.ComponentType, + [SCREENS.SPLIT_DETAILS.EDIT_CURRENCY]: () => require('../../../pages/iou/IOUCurrencySelection').default as React.ComponentType, }); const DetailsModalStackNavigator = createModalStackNavigator({ @@ -89,6 +98,14 @@ const ReportParticipantsModalStackNavigator = createModalStackNavigator({ [SCREENS.REPORT_PARTICIPANTS_ROOT]: () => require('../../../pages/ReportParticipantsPage').default as React.ComponentType, }); +const RoomMembersModalStackNavigator = createModalStackNavigator({ + [SCREENS.ROOM_MEMBERS_ROOT]: () => require('../../../pages/RoomMembersPage').default as React.ComponentType, +}); + +const RoomInviteModalStackNavigator = createModalStackNavigator({ + [SCREENS.ROOM_INVITE_ROOT]: () => require('../../../pages/RoomInvitePage').default as React.ComponentType, +}); + const SearchModalStackNavigator = createModalStackNavigator({ [SCREENS.SEARCH_ROOT]: () => require('../../../pages/SearchPage').default as React.ComponentType, }); @@ -141,7 +158,10 @@ const SettingsModalStackNavigator = createModalStackNavigator({ [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_CARDS_DIGITAL_DETAILS_UPDATE_ADDRESS]: () => require('../../../pages/settings/Profile/PersonalDetails/AddressPage').default as React.ComponentType, [SCREENS.SETTINGS.WALLET_DOMAIN_CARDS]: () => require('../../../pages/settings/Wallet/ExpensifyCardPage').default as React.ComponentType, + [SCREENS.SETTINGS.WALLET_REPORT_VIRTUAL_CARD_FRAUD]: () => require('../../../pages/settings/Wallet/ReportVirtualCardFraudPage').default as React.ComponentType, + [SCREENS.SETTINGS.WALLET_CARD_ACTIVATE]: () => require('../../../pages/settings/Wallet/ActivatePhysicalCardPage').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, @@ -151,6 +171,7 @@ const SettingsModalStackNavigator = createModalStackNavigator({ [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.CURRENCY]: () => require('../../../pages/workspace/WorkspaceSettingsCurrencyPage').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, @@ -163,6 +184,8 @@ const SettingsModalStackNavigator = createModalStackNavigator({ [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, + [SCREENS.SETTINGS.REPORT_CARD_LOST_OR_DAMAGED]: () => require('../../../pages/settings/Wallet/ReportCardLostPage').default as React.ComponentType, + [SCREENS.KEYBOARD_SHORTCUTS]: () => require('../../../pages/KeyboardShortcutsPage').default as React.ComponentType, }); const EnablePaymentsStackNavigator = createModalStackNavigator({ @@ -223,4 +246,6 @@ export { PrivateNotesModalStackNavigator, NewTeachersUniteNavigator, SignInModalStackNavigator, + RoomMembersModalStackNavigator, + RoomInviteModalStackNavigator, }; diff --git a/src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator/BaseCentralPaneNavigator.js b/src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator/BaseCentralPaneNavigator.tsx similarity index 88% rename from src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator/BaseCentralPaneNavigator.js rename to src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator/BaseCentralPaneNavigator.tsx index a1646011e560..73785e643036 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator/BaseCentralPaneNavigator.js +++ b/src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator/BaseCentralPaneNavigator.tsx @@ -1,11 +1,12 @@ import {createStackNavigator} from '@react-navigation/stack'; import React from 'react'; import ReportScreenWrapper from '@libs/Navigation/AppNavigator/ReportScreenWrapper'; +import type {CentralPaneStackParamList} from '@libs/Navigation/AppNavigator/types'; import getCurrentUrl from '@libs/Navigation/currentUrl'; import styles from '@styles/styles'; import SCREENS from '@src/SCREENS'; -const Stack = createStackNavigator(); +const Stack = createStackNavigator(); const url = getCurrentUrl(); const openOnAdminRoom = url ? new URL(url).searchParams.get('openOnAdminRoom') : undefined; diff --git a/src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator/index.native.js b/src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator/index.native.tsx similarity index 100% rename from src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator/index.native.js rename to src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator/index.native.tsx diff --git a/src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator/index.js b/src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator/index.tsx similarity index 100% rename from src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator/index.js rename to src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator/index.tsx diff --git a/src/libs/Navigation/AppNavigator/Navigators/Overlay.tsx b/src/libs/Navigation/AppNavigator/Navigators/Overlay.tsx index 266e86d210d7..e65c20ce8141 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/Overlay.tsx +++ b/src/libs/Navigation/AppNavigator/Navigators/Overlay.tsx @@ -10,7 +10,7 @@ type OverlayProps = { onPress: () => void; }; -function Overlay(props: OverlayProps) { +function Overlay({onPress}: OverlayProps) { const {current} = useCardAnimation(); // TODO: remove type assertion when useLocalize is migrated const {translate} = useLocalize() as unknown as {translate: (phrase: string) => string}; @@ -23,19 +23,14 @@ function Overlay(props: OverlayProps) { 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 */} (); function RightModalNavigator(props: StackScreenProps) { - // TODO: remove type assertion when useWindowDimensions is migrated to TS - const {isSmallScreenWidth} = useWindowDimensions() as WindowDimensions; + const {isSmallScreenWidth} = useWindowDimensions(); return ( diff --git a/src/libs/Navigation/AppNavigator/PublicScreens.tsx b/src/libs/Navigation/AppNavigator/PublicScreens.tsx index 2a22d7d5dec5..d4e4726e7c42 100644 --- a/src/libs/Navigation/AppNavigator/PublicScreens.tsx +++ b/src/libs/Navigation/AppNavigator/PublicScreens.tsx @@ -47,7 +47,7 @@ function PublicScreens() { component={GoogleSignInDesktopPage} /> diff --git a/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts b/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts index a67d8110040c..40bb2707ea01 100644 --- a/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts +++ b/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts @@ -3,11 +3,10 @@ import {OnyxCollection, OnyxEntry, withOnyx} from 'react-native-onyx'; import usePermissions from '@hooks/usePermissions'; import Navigation from '@libs/Navigation/Navigation'; import * as ReportUtils from '@libs/ReportUtils'; -import reportPropTypes from '@pages/reportPropTypes'; import * as App from '@userActions/App'; import ONYXKEYS from '@src/ONYXKEYS'; -import {Policy, Report} from '../../../types/onyx'; -import {ReportScreenWrapperProps} from './types'; +import type {Policy, Report} from '@src/types/onyx'; +import type {ReportScreenWrapperProps} from './types'; type ReportScreenIDSetterComponentProps = { /** Available reports that would be displayed in this navigator */ @@ -16,6 +15,7 @@ type ReportScreenIDSetterComponentProps = { /** The policies which the user has access to */ policies: OnyxCollection; + /** Whether user is a new user */ isFirstTimeNewExpensifyUser: OnyxEntry; }; @@ -33,7 +33,8 @@ const getLastAccessedReportID = ( ): number | string => { // If deeplink url contains reportID params, we should show the report that has this reportID. const currentRoute = Navigation.getActiveRoute(); - const {reportID} = ReportUtils.parseReportRouteParams(currentRoute); + // TODO: get rid of assertion when ReportUtils is migrated to TS + const {reportID} = ReportUtils.parseReportRouteParams(currentRoute) as {reportID: string}; if (reportID) { return reportID; } @@ -47,8 +48,7 @@ const getLastAccessedReportID = ( // 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}: ReportScreenIDSetterProps): null { - // TODO: remove type assertion when usePermissions is migrated - const {canUseDefaultRooms} = usePermissions() as {canUseDefaultRooms: boolean}; + const {canUseDefaultRooms} = usePermissions(); useEffect(() => { // Don't update if there is a reportID in the params already @@ -79,21 +79,14 @@ ReportScreenIDSetter.displayName = 'ReportScreenIDSetter'; 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 87a8a4abc687..000000000000 --- a/src/libs/Navigation/AppNavigator/ReportScreenWrapper.js +++ /dev/null @@ -1,45 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -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, - - /* Navigation functions provided by React Navigation */ - navigation: PropTypes.shape({ - setParams: PropTypes.func.isRequired, - }).isRequired, -}; - -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 index 2c17bf4b2ed0..fda939a0e15d 100644 --- a/src/libs/Navigation/AppNavigator/ReportScreenWrapper.tsx +++ b/src/libs/Navigation/AppNavigator/ReportScreenWrapper.tsx @@ -1,20 +1,17 @@ import React from 'react'; -import ReportScreen from '../../../pages/home/ReportScreen'; +import ReportScreen from '@pages/home/ReportScreen'; import ReportScreenIDSetter from './ReportScreenIDSetter'; import {ReportScreenWrapperProps} from './types'; -function ReportScreenWrapper(props: ReportScreenWrapperProps) { +function ReportScreenWrapper({route, navigation}: 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 */} - + ); diff --git a/src/libs/Navigation/AppNavigator/createCustomStackNavigator/CustomRouter.ts b/src/libs/Navigation/AppNavigator/createCustomStackNavigator/CustomRouter.ts index dc80948a2dfa..ce120e0409e1 100644 --- a/src/libs/Navigation/AppNavigator/createCustomStackNavigator/CustomRouter.ts +++ b/src/libs/Navigation/AppNavigator/createCustomStackNavigator/CustomRouter.ts @@ -1,6 +1,7 @@ -import {StackRouter, RouterConfigOptions, ParamListBase, StackNavigationState, PartialState} from '@react-navigation/native'; -import NAVIGATORS from '../../../../NAVIGATORS'; -import {ResponsiveStackNavigatorRouterOptions} from '../types'; +import {ParamListBase, PartialState, RouterConfigOptions, StackNavigationState, StackRouter} from '@react-navigation/native'; +import {ResponsiveStackNavigatorRouterOptions} from '@libs/Navigation/AppNavigator/types'; +import NAVIGATORS from '@src/NAVIGATORS'; +import SCREENS from '@src/SCREENS'; type MutableState = { index: number; @@ -12,26 +13,39 @@ type State = Omit>, 'stale'> & const isAtLeastOneCentralPaneNavigatorInState = (state: State): boolean => !!state.routes.find((r) => r.name === NAVIGATORS.CENTRAL_PANE_NAVIGATOR); /** - * @param {Object} state - react-navigation state - * @returns {String} + * @param state - react-navigation state + * @returns */ -const getTopMostReportIDFromRHP = (state) => { +const getTopMostReportIDFromRHP = (state: State): string => { if (!state) { return ''; } - const topmostRightPane = lodashFindLast(state.routes, (route) => route.name === NAVIGATORS.RIGHT_MODAL_NAVIGATOR); + const topmostRightPane = [...state.routes].reverse().find((route) => route.name === NAVIGATORS.RIGHT_MODAL_NAVIGATOR); if (topmostRightPane) { + // TODO: fix TS issue + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore return getTopMostReportIDFromRHP(topmostRightPane.state); } - const topmostRoute = lodashFindLast(state.routes); + const topmostRoute = state.routes[state.routes.length - 1]; if (topmostRoute.state) { + // TODO: fix TS issue + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore return getTopMostReportIDFromRHP(topmostRoute.state); } - if (topmostRoute.params && topmostRoute.params.reportID) { + // TODO: fix TS issue + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + if (topmostRoute.params?.reportID) { + // TODO: fix TS issue + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + // eslint-disable-next-line @typescript-eslint/no-unsafe-return return topmostRoute.params.reportID; } @@ -41,7 +55,7 @@ const getTopMostReportIDFromRHP = (state) => { * 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) */ -const addCentralPaneNavigatorRoute = (state) => { +const addCentralPaneNavigatorRoute = (state: State) => { const reportID = getTopMostReportIDFromRHP(state); const centralPaneNavigatorRoute = { name: NAVIGATORS.CENTRAL_PANE_NAVIGATOR, diff --git a/src/libs/Navigation/AppNavigator/createCustomStackNavigator/index.js b/src/libs/Navigation/AppNavigator/createCustomStackNavigator/index.js deleted file mode 100644 index 8924b01e2acb..000000000000 --- a/src/libs/Navigation/AppNavigator/createCustomStackNavigator/index.js +++ /dev/null @@ -1,91 +0,0 @@ -import {createNavigatorFactory, useNavigationBuilder} from '@react-navigation/native'; -import {StackView} from '@react-navigation/stack'; -import PropTypes from 'prop-types'; -import React, {useMemo, useRef} from 'react'; -import useWindowDimensions from '@hooks/useWindowDimensions'; -import NAVIGATORS from '@src/NAVIGATORS'; -import CustomRouter from './CustomRouter'; - -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 reduceReportRoutes(routes) { - const result = []; - let count = 0; - const reverseRoutes = [...routes].reverse(); - - reverseRoutes.forEach((route) => { - if (route.name === NAVIGATORS.CENTRAL_PANE_NAVIGATOR) { - // Remove all report routes except the last 3. This will improve performance. - if (count < 3) { - result.push(route); - count++; - } - } else { - result.push(route); - } - }); - - return result.reverse(); -} - -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, - }); - - const stateToRender = useMemo(() => { - const result = reduceReportRoutes(state.routes); - - return { - ...state, - index: result.length - 1, - routes: [...result], - }; - }, [state]); - - return ( - - - - ); -} - -ResponsiveStackNavigator.defaultProps = defaultProps; -ResponsiveStackNavigator.propTypes = propTypes; -ResponsiveStackNavigator.displayName = 'ResponsiveStackNavigator'; - -export default createNavigatorFactory(ResponsiveStackNavigator); diff --git a/src/libs/Navigation/AppNavigator/createCustomStackNavigator/index.native.js b/src/libs/Navigation/AppNavigator/createCustomStackNavigator/index.native.tsx similarity index 51% rename from src/libs/Navigation/AppNavigator/createCustomStackNavigator/index.native.js rename to src/libs/Navigation/AppNavigator/createCustomStackNavigator/index.native.tsx index ae36f4aff9ad..25675effabca 100644 --- a/src/libs/Navigation/AppNavigator/createCustomStackNavigator/index.native.js +++ b/src/libs/Navigation/AppNavigator/createCustomStackNavigator/index.native.tsx @@ -1,38 +1,24 @@ -import {createNavigatorFactory, useNavigationBuilder} from '@react-navigation/native'; -import {StackView} from '@react-navigation/stack'; -import PropTypes from 'prop-types'; +import {createNavigatorFactory, ParamListBase, StackActionHelpers, StackNavigationState, useNavigationBuilder} from '@react-navigation/native'; +import {StackNavigationEventMap, StackNavigationOptions, StackView} from '@react-navigation/stack'; import React, {useRef} from 'react'; import useWindowDimensions from '@hooks/useWindowDimensions'; +import {ResponsiveStackNavigatorProps, ResponsiveStackNavigatorRouterOptions} from '@navigation/AppNavigator/types'; import CustomRouter from './CustomRouter'; -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) { +function ResponsiveStackNavigator(props: ResponsiveStackNavigatorProps) { const {isSmallScreenWidth} = useWindowDimensions(); const isSmallScreenWidthRef = useRef(isSmallScreenWidth); isSmallScreenWidthRef.current = isSmallScreenWidth; - const {navigation, state, descriptors, NavigationContent} = useNavigationBuilder(CustomRouter, { + const {navigation, state, descriptors, NavigationContent} = useNavigationBuilder< + StackNavigationState, + ResponsiveStackNavigatorRouterOptions, + StackActionHelpers, + StackNavigationOptions, + StackNavigationEventMap + >(CustomRouter, { children: props.children, screenOptions: props.screenOptions, initialRouteName: props.initialRouteName, @@ -53,8 +39,6 @@ function ResponsiveStackNavigator(props) { ); } -ResponsiveStackNavigator.defaultProps = defaultProps; -ResponsiveStackNavigator.propTypes = propTypes; ResponsiveStackNavigator.displayName = 'ResponsiveStackNavigator'; -export default createNavigatorFactory(ResponsiveStackNavigator); +export default createNavigatorFactory, StackNavigationOptions, StackNavigationEventMap, typeof ResponsiveStackNavigator>(ResponsiveStackNavigator); diff --git a/src/libs/Navigation/AppNavigator/createCustomStackNavigator/index.tsx b/src/libs/Navigation/AppNavigator/createCustomStackNavigator/index.tsx index a97bb3de9611..3d3bd6d8d447 100644 --- a/src/libs/Navigation/AppNavigator/createCustomStackNavigator/index.tsx +++ b/src/libs/Navigation/AppNavigator/createCustomStackNavigator/index.tsx @@ -1,14 +1,35 @@ -import React, {useRef} from 'react'; -import {useNavigationBuilder, createNavigatorFactory, ParamListBase, StackActionHelpers, StackNavigationState} from '@react-navigation/native'; +import {createNavigatorFactory, ParamListBase, StackActionHelpers, StackNavigationState, useNavigationBuilder} from '@react-navigation/native'; import {StackNavigationEventMap, StackNavigationOptions, StackView} from '@react-navigation/stack'; +import React, {useMemo, useRef} from 'react'; +import useWindowDimensions from '@hooks/useWindowDimensions'; +import type {ResponsiveStackNavigatorProps, ResponsiveStackNavigatorRouterOptions} from '@libs/Navigation/AppNavigator/types'; +import NAVIGATORS from '@src/NAVIGATORS'; import CustomRouter from './CustomRouter'; -import useWindowDimensions from '../../../../hooks/useWindowDimensions'; -import {ResponsiveStackNavigatorProps, ResponsiveStackNavigatorRouterOptions} from '../types'; -import type {WindowDimensions} from '../../../../styles/getModalStyles'; + +type Routes = StackNavigationState['routes']; + +function reduceReportRoutes(routes: Routes): Routes { + const result: Routes = []; + let count = 0; + const reverseRoutes = [...routes].reverse(); + + reverseRoutes.forEach((route) => { + if (route.name === NAVIGATORS.CENTRAL_PANE_NAVIGATOR) { + // Remove all report routes except the last 3. This will improve performance. + if (count < 3) { + result.push(route); + count++; + } + } else { + result.push(route); + } + }); + + return result.reverse(); +} function ResponsiveStackNavigator(props: ResponsiveStackNavigatorProps) { - // TODO: remove type assertion when useWindowDimensions is migrated to TS - const {isSmallScreenWidth} = useWindowDimensions() as WindowDimensions; + const {isSmallScreenWidth} = useWindowDimensions(); const isSmallScreenWidthRef = useRef(isSmallScreenWidth); @@ -28,12 +49,22 @@ function ResponsiveStackNavigator(props: ResponsiveStackNavigatorProps) { getIsSmallScreenWidth: () => isSmallScreenWidthRef.current, }); + const stateToRender = useMemo(() => { + const result = reduceReportRoutes(state.routes); + + return { + ...state, + index: result.length - 1, + routes: [...result], + }; + }, [state]); + return ( diff --git a/src/libs/Navigation/AppNavigator/index.tsx b/src/libs/Navigation/AppNavigator/index.tsx index d2cf587b8fa8..8d65f5166060 100644 --- a/src/libs/Navigation/AppNavigator/index.tsx +++ b/src/libs/Navigation/AppNavigator/index.tsx @@ -5,8 +5,8 @@ type AppNavigatorProps = { authenticated: boolean; }; -function AppNavigator(props: AppNavigatorProps) { - if (props.authenticated) { +function AppNavigator({authenticated}: AppNavigatorProps) { + if (authenticated) { const AuthScreens = require('./AuthScreens').default; // These are the protected screens and only accessible when an authToken is present diff --git a/src/libs/Navigation/AppNavigator/types.ts b/src/libs/Navigation/AppNavigator/types.ts index 67286a6cbd85..fa5ac3702597 100644 --- a/src/libs/Navigation/AppNavigator/types.ts +++ b/src/libs/Navigation/AppNavigator/types.ts @@ -1,10 +1,9 @@ -import {StackScreenProps, StackNavigationOptions, StackNavigationEventMap} from '@react-navigation/stack'; -import {DefaultNavigatorOptions, ParamListBase, StackNavigationState, DefaultRouterOptions, NavigatorScreenParams} from '@react-navigation/native'; +import {DefaultNavigatorOptions, DefaultRouterOptions, NavigatorScreenParams, ParamListBase, StackNavigationState} from '@react-navigation/native'; +import {StackNavigationEventMap, StackNavigationOptions, StackScreenProps} from '@react-navigation/stack'; import {ValueOf} from 'type-fest'; - -import SCREENS from '../../../SCREENS'; -import NAVIGATORS from '../../../NAVIGATORS'; import CONST from '../../../CONST'; +import NAVIGATORS from '../../../NAVIGATORS'; +import SCREENS from '../../../SCREENS'; type AccountValidationParams = { /** AccountID associated with the validation link */ @@ -41,6 +40,7 @@ type PublicScreensStackParamList = { [SCREENS.UNLINK_LOGIN]: AccountValidationParams; [SCREENS.SIGN_IN_WITH_APPLE_DESKTOP]: undefined; [SCREENS.SIGN_IN_WITH_GOOGLE_DESKTOP]: undefined; + [SCREENS.SAML_SIGN_IN]: undefined; }; type AuthScreensStackParamList = { @@ -59,6 +59,7 @@ type AuthScreensStackParamList = { source: string; }; [SCREENS.NOT_FOUND]: undefined; + [CONST.DEMO_PAGES.MONEY2020]: undefined; [NAVIGATORS.RIGHT_MODAL_NAVIGATOR]: NavigatorScreenParams; [SCREENS.DESKTOP_SIGN_IN_REDIRECT]: undefined; }; diff --git a/src/libs/actions/App.js b/src/libs/actions/App.js index 884dddf0eb0a..92913b290aa6 100644 --- a/src/libs/actions/App.js +++ b/src/libs/actions/App.js @@ -227,7 +227,7 @@ function openApp() { /** * Fetches data when the app reconnects to the network - * @param {Number} [updateIDFrom] the ID of the Onyx update that we want to start fetching from + * @param {Number | null} [updateIDFrom] the ID of the Onyx update that we want to start fetching from */ function reconnectApp(updateIDFrom = 0) { console.debug(`[OnyxUpdates] App reconnecting with updateIDFrom: ${updateIDFrom}`); diff --git a/src/styles/getNavigationModalCardStyles/types.ts b/src/styles/getNavigationModalCardStyles/types.ts index 877981dd4dd2..e0dba07dc908 100644 --- a/src/styles/getNavigationModalCardStyles/types.ts +++ b/src/styles/getNavigationModalCardStyles/types.ts @@ -1,7 +1,5 @@ import {ViewStyle} from 'react-native'; -type GetNavigationModalCardStylesParams = {isSmallScreenWidth: number}; - -type GetNavigationModalCardStyles = (params: GetNavigationModalCardStylesParams) => ViewStyle; +type GetNavigationModalCardStyles = () => ViewStyle; export default GetNavigationModalCardStyles; diff --git a/src/types/onyx/DemoInfo.ts b/src/types/onyx/DemoInfo.ts new file mode 100644 index 000000000000..0e7a09f93983 --- /dev/null +++ b/src/types/onyx/DemoInfo.ts @@ -0,0 +1,7 @@ +type DemoInfo = { + money2020: { + isBeginningDemo?: boolean; + }; +}; + +export default DemoInfo; diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index f02d3d2f548f..9486e4e9624c 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -7,6 +7,7 @@ import Card from './Card'; import Credentials from './Credentials'; import Currency from './Currency'; import CustomStatusDraft from './CustomStatusDraft'; +import DemoInfo from './DemoInfo'; import Download from './Download'; import Form, {AddDebitCardForm, DateOfBirthForm} from './Form'; import FrequentlyUsedEmoji from './FrequentlyUsedEmoji'; @@ -61,6 +62,7 @@ export type { Currency, CustomStatusDraft, DateOfBirthForm, + DemoInfo, Download, Form, FrequentlyUsedEmoji, From 22e369f69823c38d02a7bfcd2d428d110b0bfcdb Mon Sep 17 00:00:00 2001 From: Viktoryia Kliushun Date: Mon, 13 Nov 2023 12:37:30 +0100 Subject: [PATCH 10/92] TS fixes after merging main --- .../Navigation/AppNavigator/AuthScreens.tsx | 22 +++++++------------ src/libs/actions/App.ts | 4 ++-- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.tsx b/src/libs/Navigation/AppNavigator/AuthScreens.tsx index 3017c2e8e884..533fc68e57d4 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.tsx +++ b/src/libs/Navigation/AppNavigator/AuthScreens.tsx @@ -1,9 +1,6 @@ -import lodashGet from 'lodash/get'; import React, {memo, useEffect, useRef} from 'react'; import {View} from 'react-native'; -import KeyCommand from 'react-native-key-command'; import Onyx, {OnyxEntry, withOnyx} from 'react-native-onyx'; -import {ValueOf} from 'type-fest'; import useWindowDimensions from '@hooks/useWindowDimensions'; import KeyboardShortcut from '@libs/KeyboardShortcut'; import getCurrentUrl from '@libs/Navigation/currentUrl'; @@ -12,7 +9,6 @@ import NetworkConnection from '@libs/NetworkConnection'; import * as Pusher from '@libs/Pusher/pusher'; import PusherConnectionManager from '@libs/PusherConnectionManager'; import * as SessionUtils from '@libs/SessionUtils'; -import {AuthScreensStackParamList} from '@navigation/AppNavigator/types'; import DemoSetupPage from '@pages/DemoSetupPage'; import NotFoundPage from '@pages/ErrorPage/NotFoundPage'; import DesktopSignInRedirectPage from '@pages/signin/DesktopSignInRedirectPage'; @@ -38,6 +34,7 @@ import defaultScreenOptions from './defaultScreenOptions'; import getRootNavigatorScreenOptions from './getRootNavigatorScreenOptions'; import CentralPaneNavigator from './Navigators/CentralPaneNavigator'; import RightModalNavigator from './Navigators/RightModalNavigator'; +import {AuthScreensStackParamList} from './types'; type AuthScreensProps = { /** Session of currently logged in user */ @@ -144,7 +141,7 @@ function AuthScreens({isUsingMemoryOnlyKeys = null, lastUpdateIDAppliedToClient const chatShortcutConfig = CONST.KEYBOARD_SHORTCUTS.NEW_CHAT; const currentUrl = getCurrentUrl(); const isLoggingInAsNewUser = SessionUtils.isLoggingInAsNewUser(currentUrl, session?.email ?? ''); - const shouldGetAllData = isUsingMemoryOnlyKeys || SessionUtils.didUserLogInDuringSession() || isLoggingInAsNewUser; + const shouldGetAllData = isUsingMemoryOnlyKeys ?? SessionUtils.didUserLogInDuringSession() ?? isLoggingInAsNewUser; // Sign out the current user if we're transitioning with a different user const isTransitioning = currentUrl.includes(ROUTES.TRANSITION_BETWEEN_APPS); if (isLoggingInAsNewUser && isTransitioning) { @@ -157,7 +154,7 @@ function AuthScreens({isUsingMemoryOnlyKeys = null, lastUpdateIDAppliedToClient if (isLoadingApp) { App.openApp(); } else { - App.reconnectApp(lastUpdateIDAppliedToClient); + App.reconnectApp(lastUpdateIDAppliedToClient ?? 0); } }); PusherConnectionManager.init(); @@ -177,18 +174,15 @@ function AuthScreens({isUsingMemoryOnlyKeys = null, lastUpdateIDAppliedToClient if (shouldGetAllData) { App.openApp(); } else { - App.reconnectApp(lastUpdateIDAppliedToClient); + App.reconnectApp(lastUpdateIDAppliedToClient ?? 0); } - // TODO: remove when App is merged - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore App.setUpPoliciesAndNavigate(session); App.redirectThirdPartyDesktopSignIn(); // Check if we should be running any demos immediately after signing in. - if (lodashGet(demoInfo, 'money2020.isBeginningDemo', false)) { + if (demoInfo?.money2020?.isBeginningDemo) { Navigation.navigate(ROUTES.MONEY2020, CONST.NAVIGATION.TYPE.FORCED_UP); } if (lastOpenedPublicRoomID) { @@ -211,7 +205,7 @@ function AuthScreens({isUsingMemoryOnlyKeys = null, lastUpdateIDAppliedToClient }); }, shortcutsOverviewShortcutConfig.descriptionKey, - shortcutsOverviewShortcutConfig.modifiers as unknown as string[], + shortcutsOverviewShortcutConfig.modifiers, true, ); @@ -224,7 +218,7 @@ function AuthScreens({isUsingMemoryOnlyKeys = null, lastUpdateIDAppliedToClient Modal.close(Session.checkIfActionIsAllowed(() => Navigation.navigate(ROUTES.SEARCH))); }, shortcutsOverviewShortcutConfig.descriptionKey, - shortcutsOverviewShortcutConfig.modifiers as unknown as string[], + shortcutsOverviewShortcutConfig.modifiers, true, ); @@ -234,7 +228,7 @@ function AuthScreens({isUsingMemoryOnlyKeys = null, lastUpdateIDAppliedToClient Modal.close(Session.checkIfActionIsAllowed(() => Navigation.navigate(ROUTES.NEW))); }, chatShortcutConfig.descriptionKey, - chatShortcutConfig.modifiers as unknown as string[], + chatShortcutConfig.modifiers, true, ); diff --git a/src/libs/actions/App.ts b/src/libs/actions/App.ts index ff673d173613..956c10521363 100644 --- a/src/libs/actions/App.ts +++ b/src/libs/actions/App.ts @@ -3,7 +3,7 @@ import Str from 'expensify-common/lib/str'; import 'moment/locale/es'; import {AppState, AppStateStatus} from 'react-native'; -import Onyx, {OnyxCollection, OnyxUpdate} from 'react-native-onyx'; +import Onyx, {OnyxCollection, OnyxEntry, OnyxUpdate} from 'react-native-onyx'; import {ValueOf} from 'type-fest'; import * as API from '@libs/API'; import * as Browser from '@libs/Browser'; @@ -385,7 +385,7 @@ function savePolicyDraftByNewWorkspace(policyID?: string, policyName?: string, p * pass it in as a parameter. withOnyx guarantees that the value has been read * from Onyx because it will not render the AuthScreens until that point. */ -function setUpPoliciesAndNavigate(session: OnyxTypes.Session) { +function setUpPoliciesAndNavigate(session: OnyxEntry) { const currentUrl = getCurrentUrl(); if (!session || !currentUrl || !currentUrl.includes('exitTo')) { return; From da25fc57dfc1391a2fa043184bdcfb476ef9746c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Tue, 14 Nov 2023 06:27:31 -0300 Subject: [PATCH 11/92] Rename files to TS --- .../{index.android.js => index.android.ts} | 0 .../ForegroundNotifications/{index.ios.js => index.ios.ts} | 0 .../ForegroundNotifications/{index.js => index.ts} | 0 .../PushNotification/{NotificationType.js => NotificationType.ts} | 0 .../backgroundRefresh/{index.android.js => index.android.ts} | 0 .../PushNotification/backgroundRefresh/{index.js => index.ts} | 0 .../PushNotification/{index.native.js => index.native.ts} | 0 src/libs/Notification/PushNotification/{index.js => index.ts} | 0 ...houldShowPushNotification.js => shouldShowPushNotification.ts} | 0 .../subscribePushNotification/{index.js => index.ts} | 0 ...ifications.js => subscribeToReportCommentPushNotifications.ts} | 0 11 files changed, 0 insertions(+), 0 deletions(-) rename src/libs/Notification/PushNotification/ForegroundNotifications/{index.android.js => index.android.ts} (100%) rename src/libs/Notification/PushNotification/ForegroundNotifications/{index.ios.js => index.ios.ts} (100%) rename src/libs/Notification/PushNotification/ForegroundNotifications/{index.js => index.ts} (100%) rename src/libs/Notification/PushNotification/{NotificationType.js => NotificationType.ts} (100%) rename src/libs/Notification/PushNotification/backgroundRefresh/{index.android.js => index.android.ts} (100%) rename src/libs/Notification/PushNotification/backgroundRefresh/{index.js => index.ts} (100%) rename src/libs/Notification/PushNotification/{index.native.js => index.native.ts} (100%) rename src/libs/Notification/PushNotification/{index.js => index.ts} (100%) rename src/libs/Notification/PushNotification/{shouldShowPushNotification.js => shouldShowPushNotification.ts} (100%) rename src/libs/Notification/PushNotification/subscribePushNotification/{index.js => index.ts} (100%) rename src/libs/Notification/PushNotification/{subscribeToReportCommentPushNotifications.js => subscribeToReportCommentPushNotifications.ts} (100%) diff --git a/src/libs/Notification/PushNotification/ForegroundNotifications/index.android.js b/src/libs/Notification/PushNotification/ForegroundNotifications/index.android.ts similarity index 100% rename from src/libs/Notification/PushNotification/ForegroundNotifications/index.android.js rename to src/libs/Notification/PushNotification/ForegroundNotifications/index.android.ts diff --git a/src/libs/Notification/PushNotification/ForegroundNotifications/index.ios.js b/src/libs/Notification/PushNotification/ForegroundNotifications/index.ios.ts similarity index 100% rename from src/libs/Notification/PushNotification/ForegroundNotifications/index.ios.js rename to src/libs/Notification/PushNotification/ForegroundNotifications/index.ios.ts diff --git a/src/libs/Notification/PushNotification/ForegroundNotifications/index.js b/src/libs/Notification/PushNotification/ForegroundNotifications/index.ts similarity index 100% rename from src/libs/Notification/PushNotification/ForegroundNotifications/index.js rename to src/libs/Notification/PushNotification/ForegroundNotifications/index.ts diff --git a/src/libs/Notification/PushNotification/NotificationType.js b/src/libs/Notification/PushNotification/NotificationType.ts similarity index 100% rename from src/libs/Notification/PushNotification/NotificationType.js rename to src/libs/Notification/PushNotification/NotificationType.ts diff --git a/src/libs/Notification/PushNotification/backgroundRefresh/index.android.js b/src/libs/Notification/PushNotification/backgroundRefresh/index.android.ts similarity index 100% rename from src/libs/Notification/PushNotification/backgroundRefresh/index.android.js rename to src/libs/Notification/PushNotification/backgroundRefresh/index.android.ts diff --git a/src/libs/Notification/PushNotification/backgroundRefresh/index.js b/src/libs/Notification/PushNotification/backgroundRefresh/index.ts similarity index 100% rename from src/libs/Notification/PushNotification/backgroundRefresh/index.js rename to src/libs/Notification/PushNotification/backgroundRefresh/index.ts diff --git a/src/libs/Notification/PushNotification/index.native.js b/src/libs/Notification/PushNotification/index.native.ts similarity index 100% rename from src/libs/Notification/PushNotification/index.native.js rename to src/libs/Notification/PushNotification/index.native.ts diff --git a/src/libs/Notification/PushNotification/index.js b/src/libs/Notification/PushNotification/index.ts similarity index 100% rename from src/libs/Notification/PushNotification/index.js rename to src/libs/Notification/PushNotification/index.ts diff --git a/src/libs/Notification/PushNotification/shouldShowPushNotification.js b/src/libs/Notification/PushNotification/shouldShowPushNotification.ts similarity index 100% rename from src/libs/Notification/PushNotification/shouldShowPushNotification.js rename to src/libs/Notification/PushNotification/shouldShowPushNotification.ts diff --git a/src/libs/Notification/PushNotification/subscribePushNotification/index.js b/src/libs/Notification/PushNotification/subscribePushNotification/index.ts similarity index 100% rename from src/libs/Notification/PushNotification/subscribePushNotification/index.js rename to src/libs/Notification/PushNotification/subscribePushNotification/index.ts diff --git a/src/libs/Notification/PushNotification/subscribeToReportCommentPushNotifications.js b/src/libs/Notification/PushNotification/subscribeToReportCommentPushNotifications.ts similarity index 100% rename from src/libs/Notification/PushNotification/subscribeToReportCommentPushNotifications.js rename to src/libs/Notification/PushNotification/subscribeToReportCommentPushNotifications.ts From fe710694a9e5701678886e0da54832e2266a7d18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Wed, 15 Nov 2023 10:14:45 -0300 Subject: [PATCH 12/92] Migrate ForegroundNotifications to TS --- .../ForegroundNotifications/index.android.ts | 5 ++++- .../PushNotification/ForegroundNotifications/index.ios.ts | 5 ++++- .../PushNotification/ForegroundNotifications/index.ts | 6 +++++- .../PushNotification/ForegroundNotifications/types.ts | 6 ++++++ 4 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 src/libs/Notification/PushNotification/ForegroundNotifications/types.ts diff --git a/src/libs/Notification/PushNotification/ForegroundNotifications/index.android.ts b/src/libs/Notification/PushNotification/ForegroundNotifications/index.android.ts index b36c0d0c7d18..b19e1210e747 100644 --- a/src/libs/Notification/PushNotification/ForegroundNotifications/index.android.ts +++ b/src/libs/Notification/PushNotification/ForegroundNotifications/index.android.ts @@ -1,5 +1,6 @@ import Airship from '@ua/react-native-airship'; import shouldShowPushNotification from '@libs/Notification/PushNotification/shouldShowPushNotification'; +import ForegroundNotifications from './types'; function configureForegroundNotifications() { Airship.push.android.setForegroundDisplayPredicate((pushPayload) => Promise.resolve(shouldShowPushNotification(pushPayload))); @@ -9,7 +10,9 @@ function disableForegroundNotifications() { Airship.push.android.setForegroundDisplayPredicate(() => Promise.resolve(false)); } -export default { +const foregroundNotifications: ForegroundNotifications = { configureForegroundNotifications, disableForegroundNotifications, }; + +export default foregroundNotifications; diff --git a/src/libs/Notification/PushNotification/ForegroundNotifications/index.ios.ts b/src/libs/Notification/PushNotification/ForegroundNotifications/index.ios.ts index 0f0929951a90..2f66d08e63e0 100644 --- a/src/libs/Notification/PushNotification/ForegroundNotifications/index.ios.ts +++ b/src/libs/Notification/PushNotification/ForegroundNotifications/index.ios.ts @@ -1,5 +1,6 @@ import Airship, {iOS} from '@ua/react-native-airship'; import shouldShowPushNotification from '@libs/Notification/PushNotification/shouldShowPushNotification'; +import ForegroundNotifications from './types'; function configureForegroundNotifications() { // Set our default iOS foreground presentation to be loud with a banner @@ -20,7 +21,9 @@ function disableForegroundNotifications() { Airship.push.iOS.setForegroundPresentationOptionsCallback(() => Promise.resolve([])); } -export default { +const foregroundNotifications: ForegroundNotifications = { configureForegroundNotifications, disableForegroundNotifications, }; + +export default foregroundNotifications; diff --git a/src/libs/Notification/PushNotification/ForegroundNotifications/index.ts b/src/libs/Notification/PushNotification/ForegroundNotifications/index.ts index acb116f7bc43..ca390f524daf 100644 --- a/src/libs/Notification/PushNotification/ForegroundNotifications/index.ts +++ b/src/libs/Notification/PushNotification/ForegroundNotifications/index.ts @@ -1,7 +1,11 @@ +import ForegroundNotifications from './types'; + /** * Configures notification handling while in the foreground on iOS and Android. This is a no-op on other platforms. */ -export default { +const foregroundNotifications: ForegroundNotifications = { configureForegroundNotifications: () => {}, disableForegroundNotifications: () => {}, }; + +export default foregroundNotifications; diff --git a/src/libs/Notification/PushNotification/ForegroundNotifications/types.ts b/src/libs/Notification/PushNotification/ForegroundNotifications/types.ts new file mode 100644 index 000000000000..ebceed1e4543 --- /dev/null +++ b/src/libs/Notification/PushNotification/ForegroundNotifications/types.ts @@ -0,0 +1,6 @@ +type ForegroundNotifications = { + configureForegroundNotifications: () => void; + disableForegroundNotifications: () => void; +}; + +export default ForegroundNotifications; From 0b0f19c2c5baebd4c81202009047a532e2f05114 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Wed, 15 Nov 2023 10:31:44 -0300 Subject: [PATCH 13/92] Migrate NotificationType and shouldShowPushNotification to TS --- .../PushNotification/NotificationType.ts | 2 +- .../shouldShowPushNotification.ts | 20 ++++++++++++------- src/libs/actions/Report.js | 2 +- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/libs/Notification/PushNotification/NotificationType.ts b/src/libs/Notification/PushNotification/NotificationType.ts index 092a48fe7815..8e53af950881 100644 --- a/src/libs/Notification/PushNotification/NotificationType.ts +++ b/src/libs/Notification/PushNotification/NotificationType.ts @@ -4,4 +4,4 @@ */ export default { REPORT_COMMENT: 'reportComment', -}; +} as const; diff --git a/src/libs/Notification/PushNotification/shouldShowPushNotification.ts b/src/libs/Notification/PushNotification/shouldShowPushNotification.ts index f25d452a77d5..ce1f013be90f 100644 --- a/src/libs/Notification/PushNotification/shouldShowPushNotification.ts +++ b/src/libs/Notification/PushNotification/shouldShowPushNotification.ts @@ -1,23 +1,29 @@ -import _ from 'underscore'; +import {PushPayload} from '@ua/react-native-airship'; +import {OnyxUpdate} from 'react-native-onyx'; import Log from '@libs/Log'; import * as ReportActionUtils from '@libs/ReportActionsUtils'; import * as Report from '@userActions/Report'; +type PushData = { + onyxData: OnyxUpdate[]; + reportID?: number; +}; + /** * Returns whether the given Airship notification should be shown depending on the current state of the app - * @param {PushPayload} pushPayload - * @returns {Boolean} */ -export default function shouldShowPushNotification(pushPayload) { +export default function shouldShowPushNotification(pushPayload: PushPayload): boolean { Log.info('[PushNotification] push notification received', false, {pushPayload}); - let pushData = pushPayload.extras.payload; + let payload = pushPayload.extras.payload; // The payload is string encoded on Android - if (_.isString(pushData)) { - pushData = JSON.parse(pushData); + if (typeof payload === 'string') { + payload = JSON.parse(payload); } + const pushData = payload as PushData; + if (!pushData.reportID) { Log.info('[PushNotification] Not a report action notification. Showing notification'); return true; diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 22a1bc5441e6..f34f31a21511 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -1753,7 +1753,7 @@ function setIsComposerFullSize(reportID, isComposerFullSize) { /** * @param {String} reportID - * @param {Object} action the associated report action (optional) + * @param {Object|null} action the associated report action (optional) * @param {Boolean} isRemote whether or not this notification is a remote push notification * @returns {Boolean} */ From 201e78dacda49941780a9094fe84390bff9c5cd3 Mon Sep 17 00:00:00 2001 From: Nam Le Date: Mon, 20 Nov 2023 21:37:35 +0700 Subject: [PATCH 14/92] update new change translate --- src/libs/ReportActionsUtils.ts | 10 ------ src/libs/ReportUtils.js | 12 +++---- src/libs/actions/ReportActions.ts | 33 ++++++++++++++----- .../home/report/ReportActionItemMessage.js | 6 ++-- 4 files changed, 32 insertions(+), 29 deletions(-) diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 41af659e4f1a..4615cac245ea 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -628,15 +628,6 @@ function isNotifiableReportAction(reportAction: OnyxEntry): boolea return actions.includes(reportAction.actionName); } -function isMemberRoomChangeLog(reportAction: OnyxEntry): boolean { - if (!reportAction) { - return false; - } - - const actions: ActionName[] = [CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.INVITE_TO_ROOM, CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.REMOVE_FROM_ROOM]; - return actions.includes(reportAction.actionName); -} - export { extractLinksFromMessageHtml, getAllReportActions, @@ -678,6 +669,5 @@ export { shouldReportActionBeVisible, shouldReportActionBeVisibleAsLastAction, getFirstVisibleReportActionID, - isMemberRoomChangeLog, isChannelLogMemberAction, }; diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 673cb09232de..808f6088d9c7 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -4179,8 +4179,8 @@ function getIOUReportActionDisplayMessage(reportAction) { function getChannelLogMemberMessage(reportAction) { const verb = reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.INVITE_TO_ROOM || reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.INVITE_TO_ROOM - ? 'invited' - : 'removed'; + ? Localize.translateLocal('workspace.invite.invited') + : Localize.translateLocal('workspace.invite.removed'); const mentions = _.map(reportAction.originalMessage.targetAccountIDs, (accountID) => { const personalDetail = lodashGet(allPersonalDetails, accountID); @@ -4195,17 +4195,17 @@ function getChannelLogMemberMessage(reportAction) { if (mentions.length === 0) { message = `${verb} ${lastMention}`; } else if (mentions.length === 1) { - message = `${verb} ${mentions[0]} and ${lastMention}`; + message = `${verb} ${mentions[0]} ${Localize.translateLocal('common.and')} ${lastMention}`; } else { - message = `${verb} ${mentions.join(', ')}, and ${lastMention}`; + message = `${verb} ${mentions.join(', ')}, ${Localize.translateLocal('common.and')} ${lastMention}`; } const roomName = lodashGet(reportAction, 'originalMessage.roomName', ''); if (roomName) { const preposition = reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.INVITE_TO_ROOM || reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.INVITE_TO_ROOM - ? ' to' - : ' from'; + ? ` ${Localize.translateLocal('workspace.invite.to')}` + : ` ${Localize.translateLocal('workspace.invite.from')}` message += `${preposition} ${roomName}`; } diff --git a/src/libs/actions/ReportActions.ts b/src/libs/actions/ReportActions.ts index 768e596c0f4e..7ee1076e6fcd 100644 --- a/src/libs/actions/ReportActions.ts +++ b/src/libs/actions/ReportActions.ts @@ -4,9 +4,11 @@ import * as ReportActionUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import ReportAction, {Message} from '@src/types/onyx/ReportAction'; +import ReportAction, { Message } from '@src/types/onyx/ReportAction'; +import OriginalMessage from '@src/types/onyx/OriginalMessage'; import * as Report from './Report'; + function clearReportActionErrors(reportID: string, reportAction: ReportAction) { const originalReportID = ReportUtils.getOriginalReportID(reportID, reportAction); @@ -46,9 +48,10 @@ function clearReportActionErrors(reportID: string, reportAction: ReportAction) { * @param targetAccountIDs * @returns */ -function getReportActionMessageRoomChange(originalMessage: Message, targetAccountIDs: number[]) { +function getReportActionMessageRoomChange(message: Message, originalMessage: OriginalMessage) { + const targetAccountIDs = 'targetAccountIDs' in originalMessage ? (originalMessage.targetAccountIDs as number[]) : []; if (targetAccountIDs.length === 0) { - return originalMessage; + return message; } const mentionTags = targetAccountIDs.map((accountID, index) => { @@ -58,18 +61,30 @@ function getReportActionMessageRoomChange(originalMessage: Message, targetAccoun return `${targetAccountIDs.length > 1 ? ', ' : ''}`; }); - const html = `${Localize.translateLocal('workspace.invite.invited')} ${mentionTags.join('')} `; + let html = `${Localize.translateLocal('workspace.invite.invited')} ${mentionTags.join('')}`; + + const preposition = + originalMessage.actionName === CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.INVITE_TO_ROOM || originalMessage.actionName === CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.INVITE_TO_ROOM + ? ` ${Localize.translateLocal('workspace.invite.to')}` + : ` ${Localize.translateLocal('workspace.invite.from')}`; + + const regex = /]*>(.*?)<\/a>/; + const match = message.html ? message.html.match(regex) : ''; + if (match) { + const extractedATag = match[0]; + html += `${preposition} ${extractedATag}`; + } + + html += ``; - const message: Message = { - ...originalMessage, + return { + ...message, html, }; - - return message; } export { // eslint-disable-next-line import/prefer-default-export clearReportActionErrors, getReportActionMessageRoomChange, -}; +}; \ No newline at end of file diff --git a/src/pages/home/report/ReportActionItemMessage.js b/src/pages/home/report/ReportActionItemMessage.js index eee39bdb7b09..9626c2477274 100644 --- a/src/pages/home/report/ReportActionItemMessage.js +++ b/src/pages/home/report/ReportActionItemMessage.js @@ -42,10 +42,8 @@ function ReportActionItemMessage(props) { let fragments = _.compact(props.action.previousMessage || props.action.message); const isIOUReport = ReportActionsUtils.isMoneyRequestAction(props.action); - const isMemberChangeLog = ReportActionsUtils.isMemberRoomChangeLog(props.action); - if (isMemberChangeLog) { - const targetAccountIDs = props.action.originalMessage.targetAccountIDs; - fragments = [ReportActions.getReportActionMessageRoomChange(fragments[0], targetAccountIDs)]; + if (ReportActionsUtils.isChannelLogMemberAction(props.action)) { + fragments = [ReportActions.getReportActionMessageRoomChange(fragments[0], props.action.originalMessage)]; } let iouMessage; From f7c36f8eef7fa02c1727565059f8e6e200ce9b68 Mon Sep 17 00:00:00 2001 From: Nam Le Date: Mon, 20 Nov 2023 21:38:20 +0700 Subject: [PATCH 15/92] fix prettier --- src/libs/ReportUtils.js | 2 +- src/libs/actions/ReportActions.ts | 5 ++--- src/pages/home/report/ReportActionItemMessage.js | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 808f6088d9c7..2848f69ad2a5 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -4205,7 +4205,7 @@ function getChannelLogMemberMessage(reportAction) { const preposition = reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.INVITE_TO_ROOM || reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.INVITE_TO_ROOM ? ` ${Localize.translateLocal('workspace.invite.to')}` - : ` ${Localize.translateLocal('workspace.invite.from')}` + : ` ${Localize.translateLocal('workspace.invite.from')}`; message += `${preposition} ${roomName}`; } diff --git a/src/libs/actions/ReportActions.ts b/src/libs/actions/ReportActions.ts index 7ee1076e6fcd..17aa442a4a7a 100644 --- a/src/libs/actions/ReportActions.ts +++ b/src/libs/actions/ReportActions.ts @@ -4,11 +4,10 @@ import * as ReportActionUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import ReportAction, { Message } from '@src/types/onyx/ReportAction'; import OriginalMessage from '@src/types/onyx/OriginalMessage'; +import ReportAction, {Message} from '@src/types/onyx/ReportAction'; import * as Report from './Report'; - function clearReportActionErrors(reportID: string, reportAction: ReportAction) { const originalReportID = ReportUtils.getOriginalReportID(reportID, reportAction); @@ -87,4 +86,4 @@ export { // eslint-disable-next-line import/prefer-default-export clearReportActionErrors, getReportActionMessageRoomChange, -}; \ No newline at end of file +}; diff --git a/src/pages/home/report/ReportActionItemMessage.js b/src/pages/home/report/ReportActionItemMessage.js index 9626c2477274..064e70070bb9 100644 --- a/src/pages/home/report/ReportActionItemMessage.js +++ b/src/pages/home/report/ReportActionItemMessage.js @@ -6,8 +6,8 @@ import _ from 'underscore'; import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; -import * as ReportActions from '@userActions/ReportActions'; import useThemeStyles from '@styles/useThemeStyles'; +import * as ReportActions from '@userActions/ReportActions'; import CONST from '@src/CONST'; import ReportActionItemFragment from './ReportActionItemFragment'; import reportActionPropTypes from './reportActionPropTypes'; From 794dd7af543189aaae5bcb6465582b25c7196d81 Mon Sep 17 00:00:00 2001 From: Nam Le Date: Mon, 20 Nov 2023 21:40:28 +0700 Subject: [PATCH 16/92] remove space --- src/libs/ReportUtils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 2848f69ad2a5..983f23ec602f 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -4197,7 +4197,7 @@ function getChannelLogMemberMessage(reportAction) { } else if (mentions.length === 1) { message = `${verb} ${mentions[0]} ${Localize.translateLocal('common.and')} ${lastMention}`; } else { - message = `${verb} ${mentions.join(', ')}, ${Localize.translateLocal('common.and')} ${lastMention}`; + message = `${verb} ${mentions.join(', ')}, ${Localize.translateLocal('common.and')} ${lastMention}`; } const roomName = lodashGet(reportAction, 'originalMessage.roomName', ''); From 5a356071bc6f039986e35ac7e0d27df051dc0af9 Mon Sep 17 00:00:00 2001 From: Nam Le Date: Mon, 20 Nov 2023 23:12:01 +0700 Subject: [PATCH 17/92] fix reuse get message --- src/libs/ReportUtils.js | 17 +++++-- src/libs/actions/ReportActions.ts | 45 +------------------ .../home/report/ReportActionItemMessage.js | 6 +-- 3 files changed, 17 insertions(+), 51 deletions(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 983f23ec602f..ca22ee50d00d 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -4174,15 +4174,19 @@ function getIOUReportActionDisplayMessage(reportAction) { * Return room channel log display message * * @param {Object} reportAction + * @param {Boolean} isHtml * @returns {String} */ -function getChannelLogMemberMessage(reportAction) { +function getChannelLogMemberMessage(reportAction, isHtml) { const verb = reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.INVITE_TO_ROOM || reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.INVITE_TO_ROOM ? Localize.translateLocal('workspace.invite.invited') : Localize.translateLocal('workspace.invite.removed'); const mentions = _.map(reportAction.originalMessage.targetAccountIDs, (accountID) => { + if (isHtml) { + return ``; + } const personalDetail = lodashGet(allPersonalDetails, accountID); const displayNameOrLogin = LocalePhoneNumber.formatPhoneNumber(lodashGet(personalDetail, 'login', '')) || lodashGet(personalDetail, 'displayName', '') || Localize.translateLocal('common.hidden'); @@ -4190,7 +4194,7 @@ function getChannelLogMemberMessage(reportAction) { }); const lastMention = mentions.pop(); - let message = ''; + let message = isHtml ? '' : ''; if (mentions.length === 0) { message = `${verb} ${lastMention}`; @@ -4206,9 +4210,16 @@ function getChannelLogMemberMessage(reportAction) { reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.INVITE_TO_ROOM || reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.INVITE_TO_ROOM ? ` ${Localize.translateLocal('workspace.invite.to')}` : ` ${Localize.translateLocal('workspace.invite.from')}`; - message += `${preposition} ${roomName}`; + + const messageHtml = lodashGet(reportAction, ['message', 0, 'html']); + const match = messageHtml && messageHtml.match(/]*>(.*?)<\/a>/); + const extractedATag = match ? match[0] : ''; + + message += `${preposition} ${isHtml ? extractedATag : roomName}`; } + message += isHtml ? '' : ''; + return message; } diff --git a/src/libs/actions/ReportActions.ts b/src/libs/actions/ReportActions.ts index 17aa442a4a7a..d7ff96fc6c2e 100644 --- a/src/libs/actions/ReportActions.ts +++ b/src/libs/actions/ReportActions.ts @@ -1,11 +1,9 @@ import Onyx from 'react-native-onyx'; -import * as Localize from '@libs/Localize'; import * as ReportActionUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import OriginalMessage from '@src/types/onyx/OriginalMessage'; -import ReportAction, {Message} from '@src/types/onyx/ReportAction'; +import ReportAction from '@src/types/onyx/ReportAction'; import * as Report from './Report'; function clearReportActionErrors(reportID: string, reportAction: ReportAction) { @@ -42,48 +40,7 @@ function clearReportActionErrors(reportID: string, reportAction: ReportAction) { }); } -/** - * @param originalMessage - * @param targetAccountIDs - * @returns - */ -function getReportActionMessageRoomChange(message: Message, originalMessage: OriginalMessage) { - const targetAccountIDs = 'targetAccountIDs' in originalMessage ? (originalMessage.targetAccountIDs as number[]) : []; - if (targetAccountIDs.length === 0) { - return message; - } - - const mentionTags = targetAccountIDs.map((accountID, index) => { - if (index === targetAccountIDs.length - 1 && index !== 0) { - return `${Localize.translateLocal('common.and')} `; - } - return `${targetAccountIDs.length > 1 ? ', ' : ''}`; - }); - - let html = `${Localize.translateLocal('workspace.invite.invited')} ${mentionTags.join('')}`; - - const preposition = - originalMessage.actionName === CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.INVITE_TO_ROOM || originalMessage.actionName === CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.INVITE_TO_ROOM - ? ` ${Localize.translateLocal('workspace.invite.to')}` - : ` ${Localize.translateLocal('workspace.invite.from')}`; - - const regex = /]*>(.*?)<\/a>/; - const match = message.html ? message.html.match(regex) : ''; - if (match) { - const extractedATag = match[0]; - html += `${preposition} ${extractedATag}`; - } - - html += ``; - - return { - ...message, - html, - }; -} - export { // eslint-disable-next-line import/prefer-default-export clearReportActionErrors, - getReportActionMessageRoomChange, }; diff --git a/src/pages/home/report/ReportActionItemMessage.js b/src/pages/home/report/ReportActionItemMessage.js index 064e70070bb9..d5108ff5e6d7 100644 --- a/src/pages/home/report/ReportActionItemMessage.js +++ b/src/pages/home/report/ReportActionItemMessage.js @@ -7,7 +7,6 @@ import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import useThemeStyles from '@styles/useThemeStyles'; -import * as ReportActions from '@userActions/ReportActions'; import CONST from '@src/CONST'; import ReportActionItemFragment from './ReportActionItemFragment'; import reportActionPropTypes from './reportActionPropTypes'; @@ -39,11 +38,10 @@ const defaultProps = { function ReportActionItemMessage(props) { const styles = useThemeStyles(); - let fragments = _.compact(props.action.previousMessage || props.action.message); + const fragments = _.compact(props.action.previousMessage || props.action.message); const isIOUReport = ReportActionsUtils.isMoneyRequestAction(props.action); - if (ReportActionsUtils.isChannelLogMemberAction(props.action)) { - fragments = [ReportActions.getReportActionMessageRoomChange(fragments[0], props.action.originalMessage)]; + fragments[0].html = ReportUtils.getChannelLogMemberMessage(props.action, true); } let iouMessage; From 3a957938df06555f48833dd3f8fe6e8dd15f3654 Mon Sep 17 00:00:00 2001 From: Nam Le Date: Thu, 23 Nov 2023 00:08:38 +0700 Subject: [PATCH 18/92] refactor, build link room --- src/libs/ReportActionsUtils.ts | 140 +++++++++++++++++- src/libs/ReportUtils.js | 52 +------ .../report/ContextMenu/ContextMenuActions.js | 2 +- .../home/report/ReportActionItemMessage.js | 13 +- src/types/onyx/OriginalMessage.ts | 18 +-- 5 files changed, 166 insertions(+), 59 deletions(-) diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 4615cac245ea..d961aa87172f 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -5,12 +5,15 @@ import OnyxUtils from 'react-native-onyx/lib/utils'; import {ValueOf} from 'type-fest'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import {ActionName} from '@src/types/onyx/OriginalMessage'; +import {ActionName, CommonOriginalMessage} from '@src/types/onyx/OriginalMessage'; +import PersonalDetails from '@src/types/onyx/PersonalDetails'; import Report from '@src/types/onyx/Report'; import ReportAction, {ReportActions} from '@src/types/onyx/ReportAction'; import * as CollectionUtils from './CollectionUtils'; import * as Environment from './Environment/Environment'; import isReportMessageAttachment from './isReportMessageAttachment'; +import * as LocalePhoneNumber from './LocalePhoneNumber'; +import * as Localize from './Localize'; import Log from './Log'; type LastVisibleMessage = { @@ -19,6 +22,15 @@ type LastVisibleMessage = { lastMessageHtml?: string; }; +type MessageActionItemChanelLog = { + kind: string; + content?: string; + accountID?: number; + isComma?: boolean; + roomName?: string; + roomID?: number; +}; + const allReports: OnyxCollection = {}; Onyx.connect({ key: ONYXKEYS.COLLECTION.REPORT, @@ -45,6 +57,12 @@ Onyx.connect({ }, }); +let allPersonalDetails: OnyxEntry> = null; +Onyx.connect({ + key: ONYXKEYS.PERSONAL_DETAILS_LIST, + callback: (val) => (allPersonalDetails = val), +}); + let isNetworkOffline = false; Onyx.connect({ key: ONYXKEYS.NETWORK, @@ -628,6 +646,124 @@ function isNotifiableReportAction(reportAction: OnyxEntry): boolea return actions.includes(reportAction.actionName); } +function getChannelLogMemberAction(reportAction: OnyxEntry) { + const messageItems: MessageActionItemChanelLog[] = []; + const verb = + reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.INVITE_TO_ROOM || reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.INVITE_TO_ROOM + ? Localize.translateLocal('workspace.invite.invited') + : Localize.translateLocal('workspace.invite.removed'); + + messageItems.push({ + kind: 'text', + content: `${verb} `, + }); + + const originalMessage = reportAction?.originalMessage as CommonOriginalMessage; + const targetAccountIDs: number[] = originalMessage?.targetAccountIDs ?? []; + const mentions = targetAccountIDs.map((accountID) => { + const personalDetail = allPersonalDetails?.[accountID]; + + if (personalDetail) { + const displayNameOrLogin = + LocalePhoneNumber.formatPhoneNumber(personalDetail.login ?? '') || personalDetail?.displayName ? personalDetail?.displayName : Localize.translateLocal('common.hidden'); + return {content: `@${displayNameOrLogin}`, accountID}; + } + return {content: '', accountID: 0}; + }); + + const lastMention = mentions.pop(); + + if (mentions.length === 0) { + messageItems.push({ + kind: 'userMention', + ...lastMention, + }); + } else if (mentions.length === 1) { + messageItems.push( + { + kind: 'userMention', + ...mentions[0], + }, + { + kind: 'text', + content: ` ${Localize.translateLocal('common.and')} `, + }, + { + kind: 'userMention', + ...lastMention, + }, + ); + } else { + mentions.forEach((mention) => { + messageItems.push({ + kind: 'userMention', + ...mention, + content: `${mention.content}, `, + isComma: true, + }); + }); + + messageItems.push( + { + kind: 'text', + content: `${Localize.translateLocal('common.and')} `, + }, + { + kind: 'userMention', + ...lastMention, + }, + ); + } + + const roomName = originalMessage?.roomName; + if (roomName) { + const preposition = + reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.INVITE_TO_ROOM || reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.INVITE_TO_ROOM + ? ` ${Localize.translateLocal('workspace.invite.to')} ` + : ` ${Localize.translateLocal('workspace.invite.from')} `; + + messageItems.push( + { + kind: 'text', + content: preposition, + }, + { + kind: 'roomReference', + roomName, + roomID: originalMessage?.reportID, + content: roomName, + }, + ); + } + + return messageItems; +} + +function getActionItemFragmentChanelLog(reportAction: OnyxEntry) { + const messageItems: MessageActionItemChanelLog[] = getChannelLogMemberAction(reportAction); + let html = ''; + messageItems.forEach((messageItem) => { + switch (messageItem.kind) { + case 'userMention': + html += `${messageItem.isComma ? ', ' : ''}`; + break; + case 'roomReference': + html += `${messageItem.roomName}`; + break; + default: + html += messageItem.content; + } + }); + + html += ''; + + return { + html, + text: reportAction?.message ? reportAction?.message[0].text : '', + type: CONST.REPORT.MESSAGE.TYPE.COMMENT, + }; +} + export { extractLinksFromMessageHtml, getAllReportActions, @@ -670,4 +806,6 @@ export { shouldReportActionBeVisibleAsLastAction, getFirstVisibleReportActionID, isChannelLogMemberAction, + getChannelLogMemberAction, + getActionItemFragmentChanelLog, }; diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index ca22ee50d00d..1d018e1a7075 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -4171,56 +4171,14 @@ function getIOUReportActionDisplayMessage(reportAction) { } /** - * Return room channel log display message + * Return room channel log message * * @param {Object} reportAction - * @param {Boolean} isHtml * @returns {String} */ -function getChannelLogMemberMessage(reportAction, isHtml) { - const verb = - reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.INVITE_TO_ROOM || reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.INVITE_TO_ROOM - ? Localize.translateLocal('workspace.invite.invited') - : Localize.translateLocal('workspace.invite.removed'); - - const mentions = _.map(reportAction.originalMessage.targetAccountIDs, (accountID) => { - if (isHtml) { - return ``; - } - const personalDetail = lodashGet(allPersonalDetails, accountID); - const displayNameOrLogin = - LocalePhoneNumber.formatPhoneNumber(lodashGet(personalDetail, 'login', '')) || lodashGet(personalDetail, 'displayName', '') || Localize.translateLocal('common.hidden'); - return `@${displayNameOrLogin}`; - }); - - const lastMention = mentions.pop(); - let message = isHtml ? '' : ''; - - if (mentions.length === 0) { - message = `${verb} ${lastMention}`; - } else if (mentions.length === 1) { - message = `${verb} ${mentions[0]} ${Localize.translateLocal('common.and')} ${lastMention}`; - } else { - message = `${verb} ${mentions.join(', ')}, ${Localize.translateLocal('common.and')} ${lastMention}`; - } - - const roomName = lodashGet(reportAction, 'originalMessage.roomName', ''); - if (roomName) { - const preposition = - reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.INVITE_TO_ROOM || reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.INVITE_TO_ROOM - ? ` ${Localize.translateLocal('workspace.invite.to')}` - : ` ${Localize.translateLocal('workspace.invite.from')}`; - - const messageHtml = lodashGet(reportAction, ['message', 0, 'html']); - const match = messageHtml && messageHtml.match(/]*>(.*?)<\/a>/); - const extractedATag = match ? match[0] : ''; - - message += `${preposition} ${isHtml ? extractedATag : roomName}`; - } - - message += isHtml ? '' : ''; - - return message; +function getChannelLogMemberMessagePlainText(reportAction) { + const messageItems = ReportActionsUtils.getChannelLogMemberAction(reportAction); + return _.map(messageItems, (item) => item.content).join(''); } /** @@ -4286,6 +4244,7 @@ function shouldDisableWelcomeMessage(report, policy) { } export { + getChannelLogMemberMessagePlainText, getReportParticipantsTitle, isReportMessageAttachment, findLastAccessedReport, @@ -4445,7 +4404,6 @@ export { parseReportRouteParams, getReimbursementQueuedActionMessage, getPersonalDetailsForAccountID, - getChannelLogMemberMessage, getRoom, shouldDisableWelcomeMessage, }; diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.js b/src/pages/home/report/ContextMenu/ContextMenuActions.js index 4f35926c5957..d5a5ea17608d 100644 --- a/src/pages/home/report/ContextMenu/ContextMenuActions.js +++ b/src/pages/home/report/ContextMenu/ContextMenuActions.js @@ -282,7 +282,7 @@ export default [ const displayMessage = ReportUtils.getIOUReportActionDisplayMessage(reportAction); Clipboard.setString(displayMessage); } else if (ReportActionsUtils.isChannelLogMemberAction(reportAction)) { - const logMessage = ReportUtils.getChannelLogMemberMessage(reportAction); + const logMessage = ReportUtils.getChannelLogMemberMessagePlainText(reportAction); Clipboard.setString(logMessage); } else if (content) { const parser = new ExpensiMark(); diff --git a/src/pages/home/report/ReportActionItemMessage.js b/src/pages/home/report/ReportActionItemMessage.js index d5108ff5e6d7..af9b5265ffc2 100644 --- a/src/pages/home/report/ReportActionItemMessage.js +++ b/src/pages/home/report/ReportActionItemMessage.js @@ -8,6 +8,7 @@ import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; +import TextCommentFragment from './comment/TextCommentFragment'; import ReportActionItemFragment from './ReportActionItemFragment'; import reportActionPropTypes from './reportActionPropTypes'; @@ -41,7 +42,17 @@ function ReportActionItemMessage(props) { const fragments = _.compact(props.action.previousMessage || props.action.message); const isIOUReport = ReportActionsUtils.isMoneyRequestAction(props.action); if (ReportActionsUtils.isChannelLogMemberAction(props.action)) { - fragments[0].html = ReportUtils.getChannelLogMemberMessage(props.action, true); + const fragment = ReportActionsUtils.getActionItemFragmentChanelLog(props.action); + + return ( + + ); } let iouMessage; diff --git a/src/types/onyx/OriginalMessage.ts b/src/types/onyx/OriginalMessage.ts index b5e4b25a6508..33d0c7061915 100644 --- a/src/types/onyx/OriginalMessage.ts +++ b/src/types/onyx/OriginalMessage.ts @@ -144,20 +144,20 @@ type OriginalMessageReportPreview = { }; }; +type CommonOriginalMessage = { + targetAccountIDs?: number[]; + roomName?: string; + reportID?: number; +}; + type OriginalMessagePolicyChangeLog = { actionName: ValueOf; - originalMessage: { - targetAccountIDs?: number[]; - roomName?: string; - }; + originalMessage?: CommonOriginalMessage; }; type OriginalMessageRoomChangeLog = { actionName: ValueOf; - originalMessage: { - targetAccountIDs?: number[]; - roomName?: string; - }; + originalMessage?: CommonOriginalMessage; }; type OriginalMessagePolicyTask = { @@ -196,4 +196,4 @@ type OriginalMessage = | OriginalMessageReimbursementQueued; export default OriginalMessage; -export type {ChronosOOOEvent, Decision, Reaction, ActionName}; +export type {ChronosOOOEvent, Decision, Reaction, ActionName, CommonOriginalMessage}; From b8326c76f39f5440999b9f832ab162edec162d06 Mon Sep 17 00:00:00 2001 From: Nam Le Date: Thu, 23 Nov 2023 01:11:14 +0700 Subject: [PATCH 19/92] refactor --- src/libs/ReportActionsUtils.ts | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index d961aa87172f..66b5f0e05000 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -123,6 +123,10 @@ function isChannelLogMemberAction(reportAction: OnyxEntry) { ); } +function isInvitedRoom(reportAction: OnyxEntry) { + return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.INVITE_TO_ROOM || reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.INVITE_TO_ROOM; +} + /** * Returns whether the comment is a thread parent message/the first message in a thread */ @@ -648,10 +652,9 @@ function isNotifiableReportAction(reportAction: OnyxEntry): boolea function getChannelLogMemberAction(reportAction: OnyxEntry) { const messageItems: MessageActionItemChanelLog[] = []; - const verb = - reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.INVITE_TO_ROOM || reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.INVITE_TO_ROOM - ? Localize.translateLocal('workspace.invite.invited') - : Localize.translateLocal('workspace.invite.removed'); + const isInviteRoom = isInvitedRoom(reportAction); + + const verb = isInviteRoom ? Localize.translateLocal('workspace.invite.invited') : Localize.translateLocal('workspace.invite.removed'); messageItems.push({ kind: 'text', @@ -717,10 +720,7 @@ function getChannelLogMemberAction(reportAction: OnyxEntry) { const roomName = originalMessage?.roomName; if (roomName) { - const preposition = - reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.INVITE_TO_ROOM || reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.INVITE_TO_ROOM - ? ` ${Localize.translateLocal('workspace.invite.to')} ` - : ` ${Localize.translateLocal('workspace.invite.from')} `; + const preposition = isInviteRoom ? ` ${Localize.translateLocal('workspace.invite.to')} ` : ` ${Localize.translateLocal('workspace.invite.from')} `; messageItems.push( { @@ -808,4 +808,5 @@ export { isChannelLogMemberAction, getChannelLogMemberAction, getActionItemFragmentChanelLog, + isInvitedRoom, }; From dd0e9c31d71657941347c4b9cdad50dbd43baeac Mon Sep 17 00:00:00 2001 From: Nam Le Date: Thu, 23 Nov 2023 09:16:14 +0700 Subject: [PATCH 20/92] add target for link --- src/libs/ReportActionsUtils.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 8d627dd2224e..0da36c86daf7 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -745,13 +745,14 @@ function getChannelLogMemberAction(reportAction: OnyxEntry) { function getActionItemFragmentChanelLog(reportAction: OnyxEntry) { const messageItems: MessageActionItemChanelLog[] = getChannelLogMemberAction(reportAction); let html = ''; + messageItems.forEach((messageItem) => { switch (messageItem.kind) { case 'userMention': html += `${messageItem.isComma ? ', ' : ''}`; break; case 'roomReference': - html += `${messageItem.roomName}`; + html += `${messageItem.roomName}`; break; default: html += messageItem.content; From a6693d060862cff853adf5129117fd6ec059edc7 Mon Sep 17 00:00:00 2001 From: Nam Le Date: Thu, 23 Nov 2023 19:31:58 +0700 Subject: [PATCH 21/92] fix: some convetion and refactor --- src/libs/ReportActionsUtils.ts | 49 +++++++++++-------- .../home/report/ReportActionItemMessage.js | 2 +- 2 files changed, 30 insertions(+), 21 deletions(-) diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 0da36c86daf7..5c04de1e0854 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -6,15 +6,15 @@ import {ValueOf} from 'type-fest'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import {ActionName, CommonOriginalMessage} from '@src/types/onyx/OriginalMessage'; -import PersonalDetails from '@src/types/onyx/PersonalDetails'; import Report from '@src/types/onyx/Report'; -import ReportAction, {ReportActions} from '@src/types/onyx/ReportAction'; +import ReportAction, {Message, ReportActions} from '@src/types/onyx/ReportAction'; import * as CollectionUtils from './CollectionUtils'; import * as Environment from './Environment/Environment'; import isReportMessageAttachment from './isReportMessageAttachment'; import * as LocalePhoneNumber from './LocalePhoneNumber'; import * as Localize from './Localize'; import Log from './Log'; +import * as PersonalDetailsUtils from './PersonalDetailsUtils'; type LastVisibleMessage = { lastMessageTranslationKey?: string; @@ -22,14 +22,29 @@ type LastVisibleMessage = { lastMessageHtml?: string; }; -type MessageActionItemChanelLog = { - kind: string; +type MessageActionItemChanelLogBase = { + readonly kind: string; content?: string; +}; + +type MessageActionItemChanelLogText = { + readonly kind: 'text'; + content?: string; +}; + +type MessageActionItemChanelLogUserMention = { + readonly kind: 'userMention'; accountID?: number; isComma?: boolean; +} & MessageActionItemChanelLogBase; + +type MessageActionItemChanelLogRoomReference = { + readonly kind: 'roomReference'; roomName?: string; roomID?: number; -}; +} & MessageActionItemChanelLogBase; + +type MessageActionItemChanelLog = MessageActionItemChanelLogText | MessageActionItemChanelLogUserMention | MessageActionItemChanelLogRoomReference; const allReports: OnyxCollection = {}; Onyx.connect({ @@ -57,12 +72,6 @@ Onyx.connect({ }, }); -let allPersonalDetails: OnyxEntry> = null; -Onyx.connect({ - key: ONYXKEYS.PERSONAL_DETAILS_LIST, - callback: (val) => (allPersonalDetails = val), -}); - let isNetworkOffline = false; Onyx.connect({ key: ONYXKEYS.NETWORK, @@ -653,7 +662,7 @@ function isNotifiableReportAction(reportAction: OnyxEntry): boolea return actions.includes(reportAction.actionName); } -function getChannelLogMemberAction(reportAction: OnyxEntry) { +function getChannelLogMemberAction(reportAction: OnyxEntry): MessageActionItemChanelLog[] { const messageItems: MessageActionItemChanelLog[] = []; const isInviteRoom = isInvitedRoom(reportAction); @@ -666,12 +675,14 @@ function getChannelLogMemberAction(reportAction: OnyxEntry) { const originalMessage = reportAction?.originalMessage as CommonOriginalMessage; const targetAccountIDs: number[] = originalMessage?.targetAccountIDs ?? []; + const personalDetails = PersonalDetailsUtils.getPersonalDetailsByIDs(targetAccountIDs, 0); + const mentions = targetAccountIDs.map((accountID) => { - const personalDetail = allPersonalDetails?.[accountID]; + const personalDetail = personalDetails.find((personal) => personal.accountID === accountID); if (personalDetail) { const displayNameOrLogin = - LocalePhoneNumber.formatPhoneNumber(personalDetail.login ?? '') || personalDetail?.displayName ? personalDetail?.displayName : Localize.translateLocal('common.hidden'); + LocalePhoneNumber.formatPhoneNumber(personalDetail.login ?? '') || (personalDetail?.displayName ? personalDetail?.displayName : Localize.translateLocal('common.hidden')); return {content: `@${displayNameOrLogin}`, accountID}; } return {content: '', accountID: 0}; @@ -742,9 +753,9 @@ function getChannelLogMemberAction(reportAction: OnyxEntry) { return messageItems; } -function getActionItemFragmentChanelLog(reportAction: OnyxEntry) { +function getActionItemFragmentChanelLogFragment(reportAction: OnyxEntry): Message { const messageItems: MessageActionItemChanelLog[] = getChannelLogMemberAction(reportAction); - let html = ''; + let html = ''; messageItems.forEach((messageItem) => { switch (messageItem.kind) { @@ -759,10 +770,8 @@ function getActionItemFragmentChanelLog(reportAction: OnyxEntry) { } }); - html += ''; - return { - html, + html: `${html}`, text: reportAction?.message ? reportAction?.message[0].text : '', type: CONST.REPORT.MESSAGE.TYPE.COMMENT, }; @@ -832,6 +841,6 @@ export { getFirstVisibleReportActionID, isChannelLogMemberAction, getChannelLogMemberAction, - getActionItemFragmentChanelLog, + getActionItemFragmentChanelLogFragment, isInvitedRoom, }; diff --git a/src/pages/home/report/ReportActionItemMessage.js b/src/pages/home/report/ReportActionItemMessage.js index af9b5265ffc2..dcc2bcf70ae2 100644 --- a/src/pages/home/report/ReportActionItemMessage.js +++ b/src/pages/home/report/ReportActionItemMessage.js @@ -42,7 +42,7 @@ function ReportActionItemMessage(props) { const fragments = _.compact(props.action.previousMessage || props.action.message); const isIOUReport = ReportActionsUtils.isMoneyRequestAction(props.action); if (ReportActionsUtils.isChannelLogMemberAction(props.action)) { - const fragment = ReportActionsUtils.getActionItemFragmentChanelLog(props.action); + const fragment = ReportActionsUtils.getActionItemFragmentChanelLogFragment(props.action); return ( Date: Fri, 24 Nov 2023 17:16:33 +0700 Subject: [PATCH 22/92] fix: handle comma --- src/libs/ReportActionsUtils.ts | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 5c04de1e0854..ee04ca214c33 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -35,7 +35,6 @@ type MessageActionItemChanelLogText = { type MessageActionItemChanelLogUserMention = { readonly kind: 'userMention'; accountID?: number; - isComma?: boolean; } & MessageActionItemChanelLogBase; type MessageActionItemChanelLogRoomReference = { @@ -712,12 +711,17 @@ function getChannelLogMemberAction(reportAction: OnyxEntry): Messa ); } else { mentions.forEach((mention) => { - messageItems.push({ - kind: 'userMention', - ...mention, - content: `${mention.content}, `, - isComma: true, - }); + messageItems.push( + { + kind: 'userMention', + ...mention, + content: `${mention.content}`, + }, + { + kind: 'text', + content: `, `, + }, + ); }); messageItems.push( @@ -760,7 +764,7 @@ function getActionItemFragmentChanelLogFragment(reportAction: OnyxEntry { switch (messageItem.kind) { case 'userMention': - html += `${messageItem.isComma ? ', ' : ''}`; + html += ``; break; case 'roomReference': html += `${messageItem.roomName}`; From 65015263049259290d1ceeb6c0b36049139642cb Mon Sep 17 00:00:00 2001 From: Nam Le Date: Mon, 27 Nov 2023 19:11:49 +0700 Subject: [PATCH 23/92] fix: type and logic mention --- src/libs/ReportActionsUtils.ts | 64 +++++++++++-------- .../home/report/ReportActionItemMessage.js | 2 +- 2 files changed, 37 insertions(+), 29 deletions(-) diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index ee04ca214c33..e4e8eaa3d104 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -24,23 +24,23 @@ type LastVisibleMessage = { type MessageActionItemChanelLogBase = { readonly kind: string; - content?: string; + readonly content: string; }; type MessageActionItemChanelLogText = { readonly kind: 'text'; - content?: string; + readonly content: string; }; type MessageActionItemChanelLogUserMention = { readonly kind: 'userMention'; - accountID?: number; + readonly accountID: number; } & MessageActionItemChanelLogBase; type MessageActionItemChanelLogRoomReference = { readonly kind: 'roomReference'; - roomName?: string; - roomID?: number; + readonly roomName: string; + readonly roomID: number; } & MessageActionItemChanelLogBase; type MessageActionItemChanelLog = MessageActionItemChanelLogText | MessageActionItemChanelLogUserMention | MessageActionItemChanelLogRoomReference; @@ -676,29 +676,34 @@ function getChannelLogMemberAction(reportAction: OnyxEntry): Messa const targetAccountIDs: number[] = originalMessage?.targetAccountIDs ?? []; const personalDetails = PersonalDetailsUtils.getPersonalDetailsByIDs(targetAccountIDs, 0); - const mentions = targetAccountIDs.map((accountID) => { + const mentions: MessageActionItemChanelLogUserMention[] = targetAccountIDs.map((accountID) => { const personalDetail = personalDetails.find((personal) => personal.accountID === accountID); if (personalDetail) { const displayNameOrLogin = LocalePhoneNumber.formatPhoneNumber(personalDetail.login ?? '') || (personalDetail?.displayName ? personalDetail?.displayName : Localize.translateLocal('common.hidden')); - return {content: `@${displayNameOrLogin}`, accountID}; + return {content: `@${displayNameOrLogin}`, accountID} as MessageActionItemChanelLogUserMention; } - return {content: '', accountID: 0}; + return {content: `@${Localize.translateLocal('common.hidden')}`, accountID} as MessageActionItemChanelLogUserMention; }); const lastMention = mentions.pop(); + if (!lastMention) { + return []; + } if (mentions.length === 0) { messageItems.push({ kind: 'userMention', - ...lastMention, + content: lastMention.content, + accountID: lastMention.accountID, }); } else if (mentions.length === 1) { messageItems.push( { kind: 'userMention', - ...mentions[0], + content: mentions[0].content, + accountID: mentions[0].accountID, }, { kind: 'text', @@ -706,7 +711,8 @@ function getChannelLogMemberAction(reportAction: OnyxEntry): Messa }, { kind: 'userMention', - ...lastMention, + content: lastMention.content, + accountID: lastMention.accountID, }, ); } else { @@ -714,7 +720,7 @@ function getChannelLogMemberAction(reportAction: OnyxEntry): Messa messageItems.push( { kind: 'userMention', - ...mention, + accountID: mention.accountID, content: `${mention.content}`, }, { @@ -731,7 +737,8 @@ function getChannelLogMemberAction(reportAction: OnyxEntry): Messa }, { kind: 'userMention', - ...lastMention, + content: lastMention.content, + accountID: lastMention.accountID, }, ); } @@ -740,24 +747,26 @@ function getChannelLogMemberAction(reportAction: OnyxEntry): Messa if (roomName) { const preposition = isInviteRoom ? ` ${Localize.translateLocal('workspace.invite.to')} ` : ` ${Localize.translateLocal('workspace.invite.from')} `; - messageItems.push( - { - kind: 'text', - content: preposition, - }, - { - kind: 'roomReference', - roomName, - roomID: originalMessage?.reportID, - content: roomName, - }, - ); + if (originalMessage.reportID) { + messageItems.push( + { + kind: 'text', + content: preposition, + }, + { + kind: 'roomReference', + roomName, + roomID: originalMessage.reportID, + content: roomName, + }, + ); + } } return messageItems; } -function getActionItemFragmentChanelLogFragment(reportAction: OnyxEntry): Message { +function getActionItemFragmentChannelLogFragment(reportAction: OnyxEntry): Message { const messageItems: MessageActionItemChanelLog[] = getChannelLogMemberAction(reportAction); let html = ''; @@ -845,6 +854,5 @@ export { getFirstVisibleReportActionID, isChannelLogMemberAction, getChannelLogMemberAction, - getActionItemFragmentChanelLogFragment, - isInvitedRoom, + getActionItemFragmentChannelLogFragment, }; diff --git a/src/pages/home/report/ReportActionItemMessage.js b/src/pages/home/report/ReportActionItemMessage.js index dcc2bcf70ae2..977a25fa4284 100644 --- a/src/pages/home/report/ReportActionItemMessage.js +++ b/src/pages/home/report/ReportActionItemMessage.js @@ -42,7 +42,7 @@ function ReportActionItemMessage(props) { const fragments = _.compact(props.action.previousMessage || props.action.message); const isIOUReport = ReportActionsUtils.isMoneyRequestAction(props.action); if (ReportActionsUtils.isChannelLogMemberAction(props.action)) { - const fragment = ReportActionsUtils.getActionItemFragmentChanelLogFragment(props.action); + const fragment = ReportActionsUtils.getActionItemFragmentChannelLogFragment(props.action); return ( Date: Mon, 27 Nov 2023 19:20:50 +0700 Subject: [PATCH 24/92] fix: typo --- src/libs/ReportActionsUtils.ts | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index e4e8eaa3d104..29360e6ac7ea 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -22,28 +22,28 @@ type LastVisibleMessage = { lastMessageHtml?: string; }; -type MessageActionItemChanelLogBase = { +type MessageActionItemChannelLogBase = { readonly kind: string; readonly content: string; }; -type MessageActionItemChanelLogText = { +type MessageActionItemChannelLogText = { readonly kind: 'text'; readonly content: string; }; -type MessageActionItemChanelLogUserMention = { +type MessageActionItemChannelLogUserMention = { readonly kind: 'userMention'; readonly accountID: number; -} & MessageActionItemChanelLogBase; +} & MessageActionItemChannelLogBase; -type MessageActionItemChanelLogRoomReference = { +type MessageActionItemChannelLogRoomReference = { readonly kind: 'roomReference'; readonly roomName: string; readonly roomID: number; -} & MessageActionItemChanelLogBase; +} & MessageActionItemChannelLogBase; -type MessageActionItemChanelLog = MessageActionItemChanelLogText | MessageActionItemChanelLogUserMention | MessageActionItemChanelLogRoomReference; +type MessageActionItemChannelLog = MessageActionItemChannelLogText | MessageActionItemChannelLogUserMention | MessageActionItemChannelLogRoomReference; const allReports: OnyxCollection = {}; Onyx.connect({ @@ -661,8 +661,8 @@ function isNotifiableReportAction(reportAction: OnyxEntry): boolea return actions.includes(reportAction.actionName); } -function getChannelLogMemberAction(reportAction: OnyxEntry): MessageActionItemChanelLog[] { - const messageItems: MessageActionItemChanelLog[] = []; +function getChannelLogMemberAction(reportAction: OnyxEntry): MessageActionItemChannelLog[] { + const messageItems: MessageActionItemChannelLog[] = []; const isInviteRoom = isInvitedRoom(reportAction); const verb = isInviteRoom ? Localize.translateLocal('workspace.invite.invited') : Localize.translateLocal('workspace.invite.removed'); @@ -676,15 +676,15 @@ function getChannelLogMemberAction(reportAction: OnyxEntry): Messa const targetAccountIDs: number[] = originalMessage?.targetAccountIDs ?? []; const personalDetails = PersonalDetailsUtils.getPersonalDetailsByIDs(targetAccountIDs, 0); - const mentions: MessageActionItemChanelLogUserMention[] = targetAccountIDs.map((accountID) => { + const mentions: MessageActionItemChannelLogUserMention[] = targetAccountIDs.map((accountID) => { const personalDetail = personalDetails.find((personal) => personal.accountID === accountID); if (personalDetail) { const displayNameOrLogin = LocalePhoneNumber.formatPhoneNumber(personalDetail.login ?? '') || (personalDetail?.displayName ? personalDetail?.displayName : Localize.translateLocal('common.hidden')); - return {content: `@${displayNameOrLogin}`, accountID} as MessageActionItemChanelLogUserMention; + return {content: `@${displayNameOrLogin}`, accountID} as MessageActionItemChannelLogUserMention; } - return {content: `@${Localize.translateLocal('common.hidden')}`, accountID} as MessageActionItemChanelLogUserMention; + return {content: `@${Localize.translateLocal('common.hidden')}`, accountID} as MessageActionItemChannelLogUserMention; }); const lastMention = mentions.pop(); @@ -767,7 +767,7 @@ function getChannelLogMemberAction(reportAction: OnyxEntry): Messa } function getActionItemFragmentChannelLogFragment(reportAction: OnyxEntry): Message { - const messageItems: MessageActionItemChanelLog[] = getChannelLogMemberAction(reportAction); + const messageItems: MessageActionItemChannelLog[] = getChannelLogMemberAction(reportAction); let html = ''; messageItems.forEach((messageItem) => { From 1dcc21693d3b2db11f8992e954b9cfad5b822e0f Mon Sep 17 00:00:00 2001 From: Nam Le Date: Mon, 27 Nov 2023 23:36:03 +0700 Subject: [PATCH 25/92] fix: name convention --- src/libs/ReportActionsUtils.ts | 54 +++++++++++-------- src/libs/ReportUtils.js | 12 ----- .../report/ContextMenu/ContextMenuActions.js | 2 +- .../home/report/ReportActionItemMessage.js | 2 +- src/types/onyx/OriginalMessage.ts | 8 +-- 5 files changed, 39 insertions(+), 39 deletions(-) diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 29360e6ac7ea..e5411b6e997e 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -5,7 +5,7 @@ import OnyxUtils from 'react-native-onyx/lib/utils'; import {ValueOf} from 'type-fest'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import {ActionName, CommonOriginalMessage} from '@src/types/onyx/OriginalMessage'; +import {ActionName, ChangeLogOriginalMessage} from '@src/types/onyx/OriginalMessage'; import Report from '@src/types/onyx/Report'; import ReportAction, {Message, ReportActions} from '@src/types/onyx/ReportAction'; import * as CollectionUtils from './CollectionUtils'; @@ -22,28 +22,28 @@ type LastVisibleMessage = { lastMessageHtml?: string; }; -type MessageActionItemChannelLogBase = { +type MemberChangeMessageElementBase = { readonly kind: string; readonly content: string; }; -type MessageActionItemChannelLogText = { +type MemberChangeMessageTextElement = { readonly kind: 'text'; readonly content: string; }; -type MessageActionItemChannelLogUserMention = { +type MemberChangeMessageUserMentionElement = { readonly kind: 'userMention'; readonly accountID: number; -} & MessageActionItemChannelLogBase; +} & MemberChangeMessageElementBase; -type MessageActionItemChannelLogRoomReference = { +type MemberChangeMessageRoomReferenceElement = { readonly kind: 'roomReference'; readonly roomName: string; readonly roomID: number; -} & MessageActionItemChannelLogBase; +} & MemberChangeMessageElementBase; -type MessageActionItemChannelLog = MessageActionItemChannelLogText | MessageActionItemChannelLogUserMention | MessageActionItemChannelLogRoomReference; +type MessageActionItemChannelLog = MemberChangeMessageTextElement | MemberChangeMessageUserMentionElement | MemberChangeMessageRoomReferenceElement; const allReports: OnyxCollection = {}; Onyx.connect({ @@ -131,7 +131,7 @@ function isChannelLogMemberAction(reportAction: OnyxEntry) { ); } -function isInvitedRoom(reportAction: OnyxEntry) { +function isInviteMemberAction(reportAction: OnyxEntry) { return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.INVITE_TO_ROOM || reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.INVITE_TO_ROOM; } @@ -661,30 +661,30 @@ function isNotifiableReportAction(reportAction: OnyxEntry): boolea return actions.includes(reportAction.actionName); } -function getChannelLogMemberAction(reportAction: OnyxEntry): MessageActionItemChannelLog[] { +function getMemberChangeMessageElements(reportAction: OnyxEntry): MessageActionItemChannelLog[] { const messageItems: MessageActionItemChannelLog[] = []; - const isInviteRoom = isInvitedRoom(reportAction); + const isInviteAction = isInviteMemberAction(reportAction); - const verb = isInviteRoom ? Localize.translateLocal('workspace.invite.invited') : Localize.translateLocal('workspace.invite.removed'); + const verb = isInviteAction ? Localize.translateLocal('workspace.invite.invited') : Localize.translateLocal('workspace.invite.removed'); messageItems.push({ kind: 'text', content: `${verb} `, }); - const originalMessage = reportAction?.originalMessage as CommonOriginalMessage; + const originalMessage = reportAction?.originalMessage as ChangeLogOriginalMessage; const targetAccountIDs: number[] = originalMessage?.targetAccountIDs ?? []; const personalDetails = PersonalDetailsUtils.getPersonalDetailsByIDs(targetAccountIDs, 0); - const mentions: MessageActionItemChannelLogUserMention[] = targetAccountIDs.map((accountID) => { + const mentions: MemberChangeMessageUserMentionElement[] = targetAccountIDs.map((accountID) => { const personalDetail = personalDetails.find((personal) => personal.accountID === accountID); if (personalDetail) { const displayNameOrLogin = LocalePhoneNumber.formatPhoneNumber(personalDetail.login ?? '') || (personalDetail?.displayName ? personalDetail?.displayName : Localize.translateLocal('common.hidden')); - return {content: `@${displayNameOrLogin}`, accountID} as MessageActionItemChannelLogUserMention; + return {content: `@${displayNameOrLogin}`, accountID} as MemberChangeMessageUserMentionElement; } - return {content: `@${Localize.translateLocal('common.hidden')}`, accountID} as MessageActionItemChannelLogUserMention; + return {content: `@${Localize.translateLocal('common.hidden')}`, accountID} as MemberChangeMessageUserMentionElement; }); const lastMention = mentions.pop(); @@ -745,7 +745,7 @@ function getChannelLogMemberAction(reportAction: OnyxEntry): Messa const roomName = originalMessage?.roomName; if (roomName) { - const preposition = isInviteRoom ? ` ${Localize.translateLocal('workspace.invite.to')} ` : ` ${Localize.translateLocal('workspace.invite.from')} `; + const preposition = isInviteAction ? ` ${Localize.translateLocal('workspace.invite.to')} ` : ` ${Localize.translateLocal('workspace.invite.from')} `; if (originalMessage.reportID) { messageItems.push( @@ -766,8 +766,8 @@ function getChannelLogMemberAction(reportAction: OnyxEntry): Messa return messageItems; } -function getActionItemFragmentChannelLogFragment(reportAction: OnyxEntry): Message { - const messageItems: MessageActionItemChannelLog[] = getChannelLogMemberAction(reportAction); +function getMemberChangeMessageFragment(reportAction: OnyxEntry): Message { + const messageItems: MessageActionItemChannelLog[] = getMemberChangeMessageElements(reportAction); let html = ''; messageItems.forEach((messageItem) => { @@ -790,6 +790,18 @@ function getActionItemFragmentChannelLogFragment(reportAction: OnyxEntry): string { + const messageItems = getMemberChangeMessageElements(reportAction); + return messageItems.map((item) => item.content).join(''); +} + /** * Helper method to determine if the provided accountID has made a request on the specified report. * @@ -853,6 +865,6 @@ export { hasRequestFromCurrentAccount, getFirstVisibleReportActionID, isChannelLogMemberAction, - getChannelLogMemberAction, - getActionItemFragmentChannelLogFragment, + getMemberChangeMessageFragment, + getMemberChangeMessagePlainText, }; diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 8a16c4f9751b..14a9e5afbe9d 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -4263,17 +4263,6 @@ function getIOUReportActionDisplayMessage(reportAction) { }); } -/** - * Return room channel log message - * - * @param {Object} reportAction - * @returns {String} - */ -function getChannelLogMemberMessagePlainText(reportAction) { - const messageItems = ReportActionsUtils.getChannelLogMemberAction(reportAction); - return _.map(messageItems, (item) => item.content).join(''); -} - /** * Checks if a report is a group chat. * @@ -4337,7 +4326,6 @@ function shouldDisableWelcomeMessage(report, policy) { } export { - getChannelLogMemberMessagePlainText, getReportParticipantsTitle, isReportMessageAttachment, findLastAccessedReport, diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.js b/src/pages/home/report/ContextMenu/ContextMenuActions.js index bea87494b5eb..e6afcb489935 100644 --- a/src/pages/home/report/ContextMenu/ContextMenuActions.js +++ b/src/pages/home/report/ContextMenu/ContextMenuActions.js @@ -282,7 +282,7 @@ export default [ const displayMessage = ReportUtils.getIOUReportActionDisplayMessage(reportAction); Clipboard.setString(displayMessage); } else if (ReportActionsUtils.isChannelLogMemberAction(reportAction)) { - const logMessage = ReportUtils.getChannelLogMemberMessagePlainText(reportAction); + const logMessage = ReportActionsUtils.getMemberChangeMessagePlainText(reportAction); Clipboard.setString(logMessage); } else if (content) { const parser = new ExpensiMark(); diff --git a/src/pages/home/report/ReportActionItemMessage.js b/src/pages/home/report/ReportActionItemMessage.js index 977a25fa4284..b658988ac3bc 100644 --- a/src/pages/home/report/ReportActionItemMessage.js +++ b/src/pages/home/report/ReportActionItemMessage.js @@ -42,7 +42,7 @@ function ReportActionItemMessage(props) { const fragments = _.compact(props.action.previousMessage || props.action.message); const isIOUReport = ReportActionsUtils.isMoneyRequestAction(props.action); if (ReportActionsUtils.isChannelLogMemberAction(props.action)) { - const fragment = ReportActionsUtils.getActionItemFragmentChannelLogFragment(props.action); + const fragment = ReportActionsUtils.getMemberChangeMessageFragment(props.action); return ( ; - originalMessage?: CommonOriginalMessage; + originalMessage?: ChangeLogOriginalMessage; }; type OriginalMessageRoomChangeLog = { actionName: ValueOf; - originalMessage?: CommonOriginalMessage; + originalMessage?: ChangeLogOriginalMessage; }; type OriginalMessagePolicyTask = { @@ -196,4 +196,4 @@ type OriginalMessage = | OriginalMessageReimbursementQueued; export default OriginalMessage; -export type {ChronosOOOEvent, Decision, Reaction, ActionName, CommonOriginalMessage}; +export type {ChronosOOOEvent, Decision, Reaction, ActionName, ChangeLogOriginalMessage}; From a2e6fe9fe492c82e86a69c73f49ae7f1f6f1c359 Mon Sep 17 00:00:00 2001 From: Nam Le Date: Tue, 28 Nov 2023 00:02:19 +0700 Subject: [PATCH 26/92] update minor --- src/libs/ReportActionsUtils.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index cad4f8390747..bdc68abb18c0 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -44,7 +44,7 @@ type MemberChangeMessageRoomReferenceElement = { readonly roomID: number; } & MemberChangeMessageElementBase; -type MessageActionItemChannelLog = MemberChangeMessageTextElement | MemberChangeMessageUserMentionElement | MemberChangeMessageRoomReferenceElement; +type MemberChangeMessageElement = MemberChangeMessageTextElement | MemberChangeMessageUserMentionElement | MemberChangeMessageRoomReferenceElement; const allReports: OnyxCollection = {}; Onyx.connect({ @@ -665,8 +665,8 @@ function isNotifiableReportAction(reportAction: OnyxEntry): boolea return actions.includes(reportAction.actionName); } -function getMemberChangeMessageElements(reportAction: OnyxEntry): MessageActionItemChannelLog[] { - const messageItems: MessageActionItemChannelLog[] = []; +function getMemberChangeMessageElements(reportAction: OnyxEntry): MemberChangeMessageElement[] { + const messageItems: MemberChangeMessageElement[] = []; const isInviteAction = isInviteMemberAction(reportAction); const verb = isInviteAction ? Localize.translateLocal('workspace.invite.invited') : Localize.translateLocal('workspace.invite.removed'); @@ -771,7 +771,7 @@ function getMemberChangeMessageElements(reportAction: OnyxEntry): } function getMemberChangeMessageFragment(reportAction: OnyxEntry): Message { - const messageItems: MessageActionItemChannelLog[] = getMemberChangeMessageElements(reportAction); + const messageItems: MemberChangeMessageElement[] = getMemberChangeMessageElements(reportAction); let html = ''; messageItems.forEach((messageItem) => { @@ -803,7 +803,7 @@ function getMemberChangeMessageFragment(reportAction: OnyxEntry): function getMemberChangeMessagePlainText(reportAction: OnyxEntry): string { const messageItems = getMemberChangeMessageElements(reportAction); - return messageItems.map((item) => item.content).join(''); + return messageItems.map((element) => element.content).join(''); } /** From 7013b0a42ebb7af29a7dcc41c944cbabd118bf56 Mon Sep 17 00:00:00 2001 From: Nam Le Date: Tue, 28 Nov 2023 00:13:40 +0700 Subject: [PATCH 27/92] fix minor --- src/libs/ReportActionsUtils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index bdc68abb18c0..07c6cfafb5e4 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -802,8 +802,8 @@ function getMemberChangeMessageFragment(reportAction: OnyxEntry): */ function getMemberChangeMessagePlainText(reportAction: OnyxEntry): string { - const messageItems = getMemberChangeMessageElements(reportAction); - return messageItems.map((element) => element.content).join(''); + const messageElements = getMemberChangeMessageElements(reportAction); + return messageElements.map((element) => element.content).join(''); } /** From 001917264b662b818f221b49433385e843455c97 Mon Sep 17 00:00:00 2001 From: Nam Le Date: Tue, 28 Nov 2023 17:25:40 +0700 Subject: [PATCH 28/92] fix: update name fuc and remove type --- src/libs/ReportActionsUtils.ts | 10 +++++----- .../home/report/ContextMenu/ContextMenuActions.js | 2 +- src/pages/home/report/ReportActionItemMessage.js | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 07c6cfafb5e4..7942315c6419 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -126,7 +126,7 @@ function isReimbursementQueuedAction(reportAction: OnyxEntry) { return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENTQUEUED; } -function isChannelLogMemberAction(reportAction: OnyxEntry) { +function isMemberChangeAction(reportAction: OnyxEntry) { return ( reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.INVITE_TO_ROOM || reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.REMOVE_FROM_ROOM || @@ -680,15 +680,15 @@ function getMemberChangeMessageElements(reportAction: OnyxEntry): const targetAccountIDs: number[] = originalMessage?.targetAccountIDs ?? []; const personalDetails = PersonalDetailsUtils.getPersonalDetailsByIDs(targetAccountIDs, 0); - const mentions: MemberChangeMessageUserMentionElement[] = targetAccountIDs.map((accountID) => { + const mentions = targetAccountIDs.map((accountID) => { const personalDetail = personalDetails.find((personal) => personal.accountID === accountID); if (personalDetail) { const displayNameOrLogin = LocalePhoneNumber.formatPhoneNumber(personalDetail.login ?? '') || (personalDetail?.displayName ? personalDetail?.displayName : Localize.translateLocal('common.hidden')); - return {content: `@${displayNameOrLogin}`, accountID} as MemberChangeMessageUserMentionElement; + return {content: `@${displayNameOrLogin}`, accountID}; } - return {content: `@${Localize.translateLocal('common.hidden')}`, accountID} as MemberChangeMessageUserMentionElement; + return {content: `@${Localize.translateLocal('common.hidden')}`, accountID}; }); const lastMention = mentions.pop(); @@ -868,7 +868,7 @@ export { shouldReportActionBeVisibleAsLastAction, hasRequestFromCurrentAccount, getFirstVisibleReportActionID, - isChannelLogMemberAction, + isMemberChangeAction, getMemberChangeMessageFragment, getMemberChangeMessagePlainText, }; diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.js b/src/pages/home/report/ContextMenu/ContextMenuActions.js index 7e46d00e1182..6c645bc87486 100644 --- a/src/pages/home/report/ContextMenu/ContextMenuActions.js +++ b/src/pages/home/report/ContextMenu/ContextMenuActions.js @@ -281,7 +281,7 @@ export default [ } else if (ReportActionsUtils.isMoneyRequestAction(reportAction)) { const displayMessage = ReportUtils.getIOUReportActionDisplayMessage(reportAction); Clipboard.setString(displayMessage); - } else if (ReportActionsUtils.isChannelLogMemberAction(reportAction)) { + } else if (ReportActionsUtils.isMemberChangeAction(reportAction)) { const logMessage = ReportActionsUtils.getMemberChangeMessagePlainText(reportAction); Clipboard.setString(logMessage); } else if (content) { diff --git a/src/pages/home/report/ReportActionItemMessage.js b/src/pages/home/report/ReportActionItemMessage.js index b658988ac3bc..46e0438f250a 100644 --- a/src/pages/home/report/ReportActionItemMessage.js +++ b/src/pages/home/report/ReportActionItemMessage.js @@ -41,7 +41,7 @@ function ReportActionItemMessage(props) { const styles = useThemeStyles(); const fragments = _.compact(props.action.previousMessage || props.action.message); const isIOUReport = ReportActionsUtils.isMoneyRequestAction(props.action); - if (ReportActionsUtils.isChannelLogMemberAction(props.action)) { + if (ReportActionsUtils.isMemberChangeAction(props.action)) { const fragment = ReportActionsUtils.getMemberChangeMessageFragment(props.action); return ( From bce6a22f03b00714f217cbc8d1ed2a25d7d89a85 Mon Sep 17 00:00:00 2001 From: Nam Le Date: Tue, 28 Nov 2023 19:03:00 +0700 Subject: [PATCH 29/92] refactor --- src/libs/ReportActionsUtils.ts | 44 ++++++++++++++++------------------ 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 7942315c6419..76eb5b6126a0 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -666,12 +666,12 @@ function isNotifiableReportAction(reportAction: OnyxEntry): boolea } function getMemberChangeMessageElements(reportAction: OnyxEntry): MemberChangeMessageElement[] { - const messageItems: MemberChangeMessageElement[] = []; + const messageElements: MemberChangeMessageElement[] = []; const isInviteAction = isInviteMemberAction(reportAction); const verb = isInviteAction ? Localize.translateLocal('workspace.invite.invited') : Localize.translateLocal('workspace.invite.removed'); - messageItems.push({ + messageElements.push({ kind: 'text', content: `${verb} `, }); @@ -697,13 +697,13 @@ function getMemberChangeMessageElements(reportAction: OnyxEntry): } if (mentions.length === 0) { - messageItems.push({ + messageElements.push({ kind: 'userMention', content: lastMention.content, accountID: lastMention.accountID, }); } else if (mentions.length === 1) { - messageItems.push( + messageElements.push( { kind: 'userMention', content: mentions[0].content, @@ -721,7 +721,7 @@ function getMemberChangeMessageElements(reportAction: OnyxEntry): ); } else { mentions.forEach((mention) => { - messageItems.push( + messageElements.push( { kind: 'userMention', accountID: mention.accountID, @@ -734,7 +734,7 @@ function getMemberChangeMessageElements(reportAction: OnyxEntry): ); }); - messageItems.push( + messageElements.push( { kind: 'text', content: `${Localize.translateLocal('common.and')} `, @@ -752,7 +752,7 @@ function getMemberChangeMessageElements(reportAction: OnyxEntry): const preposition = isInviteAction ? ` ${Localize.translateLocal('workspace.invite.to')} ` : ` ${Localize.translateLocal('workspace.invite.from')} `; if (originalMessage.reportID) { - messageItems.push( + messageElements.push( { kind: 'text', content: preposition, @@ -767,25 +767,23 @@ function getMemberChangeMessageElements(reportAction: OnyxEntry): } } - return messageItems; + return messageElements; } function getMemberChangeMessageFragment(reportAction: OnyxEntry): Message { - const messageItems: MemberChangeMessageElement[] = getMemberChangeMessageElements(reportAction); - let html = ''; - - messageItems.forEach((messageItem) => { - switch (messageItem.kind) { - case 'userMention': - html += ``; - break; - case 'roomReference': - html += `${messageItem.roomName}`; - break; - default: - html += messageItem.content; - } - }); + const messageElements: MemberChangeMessageElement[] = getMemberChangeMessageElements(reportAction); + const html = messageElements + .map((messageElement) => { + switch (messageElement.kind) { + case 'userMention': + return ``; + case 'roomReference': + return `${messageElement.roomName}`; + default: + return messageElement.content; + } + }) + .join(''); return { html: `${html}`, From 00fb67c50e1cebf982d689e0d167efaa77c5e04c Mon Sep 17 00:00:00 2001 From: Nam Le Date: Tue, 28 Nov 2023 20:46:55 +0700 Subject: [PATCH 30/92] remove comment and keep changelog --- src/libs/ReportActionsUtils.ts | 23 ++++++++--------------- src/types/onyx/OriginalMessage.ts | 18 +++++++++--------- 2 files changed, 17 insertions(+), 24 deletions(-) diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 76eb5b6126a0..1531d0321be0 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -5,7 +5,7 @@ import OnyxUtils from 'react-native-onyx/lib/utils'; import {ValueOf} from 'type-fest'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import {ActionName, ChangeLogOriginalMessage} from '@src/types/onyx/OriginalMessage'; +import {ActionName, ChangeLog} from '@src/types/onyx/OriginalMessage'; import Report from '@src/types/onyx/Report'; import ReportAction, {Message, ReportActions} from '@src/types/onyx/ReportAction'; import {EmptyObject, isEmptyObject} from '@src/types/utils/EmptyObject'; @@ -666,17 +666,17 @@ function isNotifiableReportAction(reportAction: OnyxEntry): boolea } function getMemberChangeMessageElements(reportAction: OnyxEntry): MemberChangeMessageElement[] { - const messageElements: MemberChangeMessageElement[] = []; const isInviteAction = isInviteMemberAction(reportAction); - const verb = isInviteAction ? Localize.translateLocal('workspace.invite.invited') : Localize.translateLocal('workspace.invite.removed'); - messageElements.push({ - kind: 'text', - content: `${verb} `, - }); + const messageElements: MemberChangeMessageElement[] = [ + { + kind: 'text', + content: `${verb} `, + }, + ]; - const originalMessage = reportAction?.originalMessage as ChangeLogOriginalMessage; + const originalMessage = reportAction?.originalMessage as ChangeLog; const targetAccountIDs: number[] = originalMessage?.targetAccountIDs ?? []; const personalDetails = PersonalDetailsUtils.getPersonalDetailsByIDs(targetAccountIDs, 0); @@ -792,13 +792,6 @@ function getMemberChangeMessageFragment(reportAction: OnyxEntry): }; } -/** - * Return room channel log message - * - * @param {Object} reportAction - * @returns {String} - */ - function getMemberChangeMessagePlainText(reportAction: OnyxEntry): string { const messageElements = getMemberChangeMessageElements(reportAction); return messageElements.map((element) => element.content).join(''); diff --git a/src/types/onyx/OriginalMessage.ts b/src/types/onyx/OriginalMessage.ts index 6172afb8b414..11efcdebaf28 100644 --- a/src/types/onyx/OriginalMessage.ts +++ b/src/types/onyx/OriginalMessage.ts @@ -135,6 +135,12 @@ type ChronosOOOTimestamp = { timezone_type: number; }; +type ChangeLog = { + targetAccountIDs?: number[]; + roomName?: string; + reportID?: number; +}; + type ChronosOOOEvent = { id: string; lengthInDays: number; @@ -161,20 +167,14 @@ type OriginalMessageReportPreview = { }; }; -type ChangeLogOriginalMessage = { - targetAccountIDs?: number[]; - roomName?: string; - reportID?: number; -}; - type OriginalMessagePolicyChangeLog = { actionName: ValueOf; - originalMessage?: ChangeLogOriginalMessage; + originalMessage?: ChangeLog; }; type OriginalMessageRoomChangeLog = { actionName: ValueOf; - originalMessage?: ChangeLogOriginalMessage; + originalMessage?: ChangeLog; }; type OriginalMessagePolicyTask = { @@ -224,4 +224,4 @@ type OriginalMessage = | OriginalMessageMoved; export default OriginalMessage; -export type {ChronosOOOEvent, Decision, Reaction, ActionName, IOUMessage, Closed, OriginalMessageActionName, ChangeLogOriginalMessage}; +export type {ChronosOOOEvent, Decision, Reaction, ActionName, IOUMessage, Closed, OriginalMessageActionName, ChangeLog}; From 162670f6233521677ae14010354c8b8de45031d5 Mon Sep 17 00:00:00 2001 From: Nam Le Date: Tue, 28 Nov 2023 20:48:40 +0700 Subject: [PATCH 31/92] refactor --- src/types/onyx/OriginalMessage.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/types/onyx/OriginalMessage.ts b/src/types/onyx/OriginalMessage.ts index 11efcdebaf28..feb77ca8c317 100644 --- a/src/types/onyx/OriginalMessage.ts +++ b/src/types/onyx/OriginalMessage.ts @@ -169,12 +169,12 @@ type OriginalMessageReportPreview = { type OriginalMessagePolicyChangeLog = { actionName: ValueOf; - originalMessage?: ChangeLog; + originalMessage: ChangeLog; }; type OriginalMessageRoomChangeLog = { actionName: ValueOf; - originalMessage?: ChangeLog; + originalMessage: ChangeLog; }; type OriginalMessagePolicyTask = { From 31af9ded14f7a8f877016adaf90e3dde9710be89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Tue, 28 Nov 2023 10:52:09 -0300 Subject: [PATCH 32/92] Minor fixes in backgroundRefresh --- .../backgroundRefresh/index.android.ts | 11 +++++++---- .../PushNotification/backgroundRefresh/index.ts | 6 +++++- .../PushNotification/backgroundRefresh/types.ts | 3 +++ 3 files changed, 15 insertions(+), 5 deletions(-) create mode 100644 src/libs/Notification/PushNotification/backgroundRefresh/types.ts diff --git a/src/libs/Notification/PushNotification/backgroundRefresh/index.android.ts b/src/libs/Notification/PushNotification/backgroundRefresh/index.android.ts index 4502011b459e..2b3c6ebf21b4 100644 --- a/src/libs/Notification/PushNotification/backgroundRefresh/index.android.ts +++ b/src/libs/Notification/PushNotification/backgroundRefresh/index.android.ts @@ -4,8 +4,9 @@ import Visibility from '@libs/Visibility'; import * as App from '@userActions/App'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import BackgroundRefresh from './types'; -function getLastOnyxUpdateID() { +function getLastOnyxUpdateID(): Promise { return new Promise((resolve) => { const connectionID = Onyx.connect({ key: ONYXKEYS.ONYX_UPDATES_LAST_UPDATE_ID_APPLIED_TO_CLIENT, @@ -23,7 +24,7 @@ function getLastOnyxUpdateID() { * We use this to refresh the app in the background after receiving a push notification (native only). Since the full app * wakes on iOS and by extension runs reconnectApp already, this is a no-op on everything but Android. */ -export default function backgroundRefresh() { +const backgroundRefresh: BackgroundRefresh = () => { if (Visibility.isVisible()) { return; } @@ -38,9 +39,11 @@ export default function backgroundRefresh() { * See more here: https://reactnative.dev/docs/headless-js-android */ App.confirmReadyToOpenApp(); - App.reconnectApp(lastUpdateIDAppliedToClient); + App.reconnectApp(lastUpdateIDAppliedToClient ?? undefined); }) .catch((error) => { Log.alert(`${CONST.ERROR.ENSURE_BUGBOT} [PushNotification] backgroundRefresh failed. This should never happen.`, {error}); }); -} +}; + +export default backgroundRefresh; diff --git a/src/libs/Notification/PushNotification/backgroundRefresh/index.ts b/src/libs/Notification/PushNotification/backgroundRefresh/index.ts index 657fb15ee429..c7f47a532d89 100644 --- a/src/libs/Notification/PushNotification/backgroundRefresh/index.ts +++ b/src/libs/Notification/PushNotification/backgroundRefresh/index.ts @@ -1,7 +1,11 @@ +import BackgroundRefresh from './types'; + /** * Runs our reconnectApp action if the app is in the background. * * We use this to refresh the app in the background after receiving a push notification (native only). Since the full app * wakes on iOS and by extension runs reconnectApp already, this is a no-op on everything but Android. */ -export default function backgroundRefresh() {} +const backgroundRefresh: BackgroundRefresh = () => {}; + +export default backgroundRefresh; diff --git a/src/libs/Notification/PushNotification/backgroundRefresh/types.ts b/src/libs/Notification/PushNotification/backgroundRefresh/types.ts new file mode 100644 index 000000000000..d3d1ee44a1fd --- /dev/null +++ b/src/libs/Notification/PushNotification/backgroundRefresh/types.ts @@ -0,0 +1,3 @@ +type BackgroundRefresh = () => void; + +export default BackgroundRefresh; From 7067417cfd09eeae608181e7d9f49b3e6e72775c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Tue, 28 Nov 2023 11:08:09 -0300 Subject: [PATCH 33/92] make ForegroundNotifications capitalized --- .../ForegroundNotifications/index.android.ts | 6 +++--- .../PushNotification/ForegroundNotifications/index.ios.ts | 6 +++--- .../PushNotification/ForegroundNotifications/index.ts | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/libs/Notification/PushNotification/ForegroundNotifications/index.android.ts b/src/libs/Notification/PushNotification/ForegroundNotifications/index.android.ts index b19e1210e747..8c63d81093a6 100644 --- a/src/libs/Notification/PushNotification/ForegroundNotifications/index.android.ts +++ b/src/libs/Notification/PushNotification/ForegroundNotifications/index.android.ts @@ -1,6 +1,6 @@ import Airship from '@ua/react-native-airship'; import shouldShowPushNotification from '@libs/Notification/PushNotification/shouldShowPushNotification'; -import ForegroundNotifications from './types'; +import ForegroundNotificationsType from './types'; function configureForegroundNotifications() { Airship.push.android.setForegroundDisplayPredicate((pushPayload) => Promise.resolve(shouldShowPushNotification(pushPayload))); @@ -10,9 +10,9 @@ function disableForegroundNotifications() { Airship.push.android.setForegroundDisplayPredicate(() => Promise.resolve(false)); } -const foregroundNotifications: ForegroundNotifications = { +const ForegroundNotifications: ForegroundNotificationsType = { configureForegroundNotifications, disableForegroundNotifications, }; -export default foregroundNotifications; +export default ForegroundNotifications; diff --git a/src/libs/Notification/PushNotification/ForegroundNotifications/index.ios.ts b/src/libs/Notification/PushNotification/ForegroundNotifications/index.ios.ts index 2f66d08e63e0..588a24a27651 100644 --- a/src/libs/Notification/PushNotification/ForegroundNotifications/index.ios.ts +++ b/src/libs/Notification/PushNotification/ForegroundNotifications/index.ios.ts @@ -1,6 +1,6 @@ import Airship, {iOS} from '@ua/react-native-airship'; import shouldShowPushNotification from '@libs/Notification/PushNotification/shouldShowPushNotification'; -import ForegroundNotifications from './types'; +import ForegroundNotificationsType from './types'; function configureForegroundNotifications() { // Set our default iOS foreground presentation to be loud with a banner @@ -21,9 +21,9 @@ function disableForegroundNotifications() { Airship.push.iOS.setForegroundPresentationOptionsCallback(() => Promise.resolve([])); } -const foregroundNotifications: ForegroundNotifications = { +const ForegroundNotifications: ForegroundNotificationsType = { configureForegroundNotifications, disableForegroundNotifications, }; -export default foregroundNotifications; +export default ForegroundNotifications; diff --git a/src/libs/Notification/PushNotification/ForegroundNotifications/index.ts b/src/libs/Notification/PushNotification/ForegroundNotifications/index.ts index ca390f524daf..91a68fbc3503 100644 --- a/src/libs/Notification/PushNotification/ForegroundNotifications/index.ts +++ b/src/libs/Notification/PushNotification/ForegroundNotifications/index.ts @@ -1,11 +1,11 @@ -import ForegroundNotifications from './types'; +import ForegroundNotificationsType from './types'; /** * Configures notification handling while in the foreground on iOS and Android. This is a no-op on other platforms. */ -const foregroundNotifications: ForegroundNotifications = { +const ForegroundNotifications: ForegroundNotificationsType = { configureForegroundNotifications: () => {}, disableForegroundNotifications: () => {}, }; -export default foregroundNotifications; +export default ForegroundNotifications; From 9d6ef344ecdf94302785475dcec860ac2face1b5 Mon Sep 17 00:00:00 2001 From: Nam Le Date: Tue, 28 Nov 2023 22:31:36 +0700 Subject: [PATCH 34/92] update translate --- src/languages/es.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/languages/es.ts b/src/languages/es.ts index a0c5e621e600..94aa21ff3240 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1560,7 +1560,7 @@ export default { genericFailureMessage: 'Se produjo un error al invitar al usuario al espacio de trabajo. Vuelva a intentarlo..', pleaseEnterValidLogin: `Asegúrese de que el correo electrónico o el número de teléfono sean válidos (p. ej. ${CONST.EXAMPLE_PHONE_NUMBER}).`, user: 'usuario', - users: 'usuarias', + users: 'usuarios', invited: 'invitada', removed: 'eliminado', to: 'a', From 333ce90a2e8238bc60b87b9713703e9291425982 Mon Sep 17 00:00:00 2001 From: Nam Le Date: Tue, 28 Nov 2023 23:18:26 +0700 Subject: [PATCH 35/92] fix translate --- 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 94aa21ff3240..ba53233bfc1e 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1561,8 +1561,8 @@ export default { pleaseEnterValidLogin: `Asegúrese de que el correo electrónico o el número de teléfono sean válidos (p. ej. ${CONST.EXAMPLE_PHONE_NUMBER}).`, user: 'usuario', users: 'usuarios', - invited: 'invitada', - removed: 'eliminado', + invited: 'invitó', + removed: 'eliminó', to: 'a', from: 'de', }, From c624fe2cab8138f3d45130a5e57ec10d534f18b9 Mon Sep 17 00:00:00 2001 From: Nam Le Date: Wed, 29 Nov 2023 09:47:28 +0700 Subject: [PATCH 36/92] fix translate with comma --- src/languages/en.ts | 1 + src/languages/es.ts | 1 + src/libs/ReportActionsUtils.ts | 4 +++- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index fe0032794a66..66ff823ef572 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -270,6 +270,7 @@ export default { selectCurrency: 'Select a currency', card: 'Card', required: 'Required', + serialComma: ', ', }, location: { useCurrent: 'Use current location', diff --git a/src/languages/es.ts b/src/languages/es.ts index ba53233bfc1e..b3df05a31828 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -260,6 +260,7 @@ export default { selectCurrency: 'Selecciona una moneda', card: 'Tarjeta', required: 'Obligatorio', + serialComma: ' ', }, location: { useCurrent: 'Usar ubicación actual', diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 1531d0321be0..246abd52bea8 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -667,6 +667,8 @@ function isNotifiableReportAction(reportAction: OnyxEntry): boolea function getMemberChangeMessageElements(reportAction: OnyxEntry): MemberChangeMessageElement[] { const isInviteAction = isInviteMemberAction(reportAction); + + // currently, in the app we only have the logic show the message when invite the members const verb = isInviteAction ? Localize.translateLocal('workspace.invite.invited') : Localize.translateLocal('workspace.invite.removed'); const messageElements: MemberChangeMessageElement[] = [ @@ -729,7 +731,7 @@ function getMemberChangeMessageElements(reportAction: OnyxEntry): }, { kind: 'text', - content: `, `, + content: `${Localize.translateLocal('common.serialComma')}`, }, ); }); From c615c01109a5b8e86dcefd96fec2c5b837cecbd4 Mon Sep 17 00:00:00 2001 From: Nam Le Date: Wed, 29 Nov 2023 15:14:18 +0700 Subject: [PATCH 37/92] refactor --- src/libs/PersonalDetailsUtils.js | 13 +++++++++++++ src/libs/ReportActionsUtils.ts | 9 +-------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/libs/PersonalDetailsUtils.js b/src/libs/PersonalDetailsUtils.js index 560480dcec9d..8a4151391453 100644 --- a/src/libs/PersonalDetailsUtils.js +++ b/src/libs/PersonalDetailsUtils.js @@ -197,6 +197,18 @@ function getFormattedAddress(privatePersonalDetails) { return formattedAddress.trim().replace(/,$/, ''); } +/** + * @param {Object} personalDetail - details object + * @returns {String | undefined} - The effective display name + */ +function getEffectiveDisplayName(personalDetail) { + if (personalDetail) { + return LocalePhoneNumber.formatPhoneNumber(personalDetail.login) || personalDetail.displayName; + } + + return undefined; +} + export { getDisplayNameOrDefault, getPersonalDetailsByIDs, @@ -206,4 +218,5 @@ export { getFormattedAddress, getFormattedStreet, getStreetLines, + getEffectiveDisplayName, }; diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 246abd52bea8..7bfa9a13321d 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -12,7 +12,6 @@ import {EmptyObject, isEmptyObject} from '@src/types/utils/EmptyObject'; import * as CollectionUtils from './CollectionUtils'; import * as Environment from './Environment/Environment'; import isReportMessageAttachment from './isReportMessageAttachment'; -import * as LocalePhoneNumber from './LocalePhoneNumber'; import * as Localize from './Localize'; import Log from './Log'; import * as PersonalDetailsUtils from './PersonalDetailsUtils'; @@ -684,13 +683,7 @@ function getMemberChangeMessageElements(reportAction: OnyxEntry): const mentions = targetAccountIDs.map((accountID) => { const personalDetail = personalDetails.find((personal) => personal.accountID === accountID); - - if (personalDetail) { - const displayNameOrLogin = - LocalePhoneNumber.formatPhoneNumber(personalDetail.login ?? '') || (personalDetail?.displayName ? personalDetail?.displayName : Localize.translateLocal('common.hidden')); - return {content: `@${displayNameOrLogin}`, accountID}; - } - return {content: `@${Localize.translateLocal('common.hidden')}`, accountID}; + return {content: `@${PersonalDetailsUtils.getEffectiveDisplayName(personalDetail) ?? Localize.translateLocal('common.hidden')}`, accountID}; }); const lastMention = mentions.pop(); From c8f48adac5389c719bdd95b364e1a93e92877268 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Wed, 29 Nov 2023 09:56:34 -0300 Subject: [PATCH 38/92] Several improvements and PushNotification migration --- .../PushNotification/NotificationType.ts | 27 +++++- .../PushNotification/index.native.ts | 95 +++++++++---------- .../Notification/PushNotification/index.ts | 5 +- .../shouldShowPushNotification.ts | 15 +-- ...bscribeToReportCommentPushNotifications.ts | 11 ++- .../Notification/PushNotification/types.ts | 22 +++++ src/types/onyx/OnyxUpdatesFromServer.ts | 6 +- 7 files changed, 113 insertions(+), 68 deletions(-) create mode 100644 src/libs/Notification/PushNotification/types.ts diff --git a/src/libs/Notification/PushNotification/NotificationType.ts b/src/libs/Notification/PushNotification/NotificationType.ts index 8e53af950881..a69525c96581 100644 --- a/src/libs/Notification/PushNotification/NotificationType.ts +++ b/src/libs/Notification/PushNotification/NotificationType.ts @@ -1,7 +1,28 @@ +import {OnyxServerUpdate} from '@src/types/onyx/OnyxUpdatesFromServer'; + +const NotificationType = { + REPORT_COMMENT: 'reportComment', +} as const; + +type NotificationDataMap = { + [NotificationType.REPORT_COMMENT]: ReportCommentNotificationData; +}; + +type NotificationData = ReportCommentNotificationData; + +type ReportCommentNotificationData = { + title?: string; + type?: typeof NotificationType.REPORT_COMMENT; + reportID?: number; + reportActionID?: string; + shouldScrollToLastUnread?: boolean; + roomName?: string; + onyxData?: OnyxServerUpdate[]; +}; + /** * See https://github.com/Expensify/Web-Expensify/blob/main/lib/MobilePushNotifications.php for the various * types of push notifications sent by our API. */ -export default { - REPORT_COMMENT: 'reportComment', -} as const; +export default NotificationType; +export type {NotificationDataMap, NotificationData, ReportCommentNotificationData}; diff --git a/src/libs/Notification/PushNotification/index.native.ts b/src/libs/Notification/PushNotification/index.native.ts index 8513a88e46d3..537989116021 100644 --- a/src/libs/Notification/PushNotification/index.native.ts +++ b/src/libs/Notification/PushNotification/index.native.ts @@ -1,57 +1,57 @@ -import Airship, {EventType} from '@ua/react-native-airship'; -import lodashGet from 'lodash/get'; +import Airship, {EventType, PushPayload} from '@ua/react-native-airship'; import Onyx from 'react-native-onyx'; -import _ from 'underscore'; import Log from '@libs/Log'; -import * as PushNotification from '@userActions/PushNotification'; +import * as PushNotificationActions from '@userActions/PushNotification'; import ONYXKEYS from '@src/ONYXKEYS'; import ForegroundNotifications from './ForegroundNotifications'; -import NotificationType from './NotificationType'; +import NotificationType, {NotificationData} from './NotificationType'; +import PushNotificationType, {ClearNotifications, Deregister, Init, OnReceived, OnSelected, Register} from './types'; + +type NotificationEventActionCallback = (data: NotificationData) => void; let isUserOptedInToPushNotifications = false; Onyx.connect({ key: ONYXKEYS.PUSH_NOTIFICATIONS_ENABLED, - callback: (val) => (isUserOptedInToPushNotifications = val), + callback: (val) => (isUserOptedInToPushNotifications = val ?? false), }); -const notificationEventActionMap = {}; +const notificationEventActionMap: Partial>> = {}; /** * Handle a push notification event, and trigger and bound actions. - * - * @param {String} eventType - * @param {Object} notification */ -function pushNotificationEventCallback(eventType, notification) { - const actionMap = notificationEventActionMap[eventType] || {}; - let payload = lodashGet(notification, 'extras.payload'); +function pushNotificationEventCallback(eventType: EventType, notification: PushPayload) { + const actionMap = notificationEventActionMap[eventType] ?? {}; + let payload = notification.extras.payload; // On Android, some notification payloads are sent as a JSON string rather than an object - if (_.isString(payload)) { + if (typeof payload === 'string') { payload = JSON.parse(payload); } + const data = payload as NotificationData; + Log.info(`[PushNotification] Callback triggered for ${eventType}`); - if (!payload) { + if (!data) { Log.warn('[PushNotification] Notification has null or undefined payload, not executing any callback.'); return; } - if (!payload.type) { + if (!data.type) { Log.warn('[PushNotification] No type value provided in payload, not executing any callback.'); return; } - const action = actionMap[payload.type]; + const action = actionMap[data.type]; if (!action) { Log.warn('[PushNotification] No callback set up: ', { event: eventType, - notificationType: payload.type, + notificationType: data.type, }); return; } - action(payload); + action(data); } /** @@ -65,7 +65,7 @@ function refreshNotificationOptInStatus() { } Log.info('[PushNotification] Push notification opt-in status changed.', false, {isOptedIn}); - PushNotification.setPushNotificationOptInStatus(isOptedIn); + PushNotificationActions.setPushNotificationOptInStatus(isOptedIn); }); } @@ -76,12 +76,12 @@ function refreshNotificationOptInStatus() { * WARNING: Moving or changing this code could break Push Notification processing in non-obvious ways. * DO NOT ALTER UNLESS YOU KNOW WHAT YOU'RE DOING. See this PR for details: https://github.com/Expensify/App/pull/3877 */ -function init() { +const init: Init = () => { // Setup event listeners Airship.addListener(EventType.PushReceived, (notification) => { // By default, refresh notification opt-in status to true if we receive a notification if (!isUserOptedInToPushNotifications) { - PushNotification.setPushNotificationOptInStatus(true); + PushNotificationActions.setPushNotificationOptInStatus(true); } pushNotificationEventCallback(EventType.PushReceived, notification.pushPayload); @@ -97,14 +97,13 @@ function init() { Airship.addListener(EventType.NotificationOptInStatus, refreshNotificationOptInStatus); ForegroundNotifications.configureForegroundNotifications(); -} +}; /** * Register this device for push notifications for the given notificationID. - * - * @param {String|Number} notificationID */ -function register(notificationID) { +const register: Register = (notificationID) => { + // @ts-expect-error FIXME: This condition will never satisfy because Airship.contact.getNamedUserId() returns a promise. if (Airship.contact.getNamedUserId() === notificationID.toString()) { // No need to register again for this notificationID. return; @@ -126,18 +125,18 @@ function register(notificationID) { // Refresh notification opt-in status NVP for the new user. refreshNotificationOptInStatus(); -} +}; /** * Deregister this device from push notifications. */ -function deregister() { +const deregister: Deregister = () => { Log.info('[PushNotification] Unsubscribing from push notifications.'); Airship.contact.reset(); Airship.removeAllListeners(EventType.PushReceived); Airship.removeAllListeners(EventType.NotificationResponse); ForegroundNotifications.disableForegroundNotifications(); -} +}; /** * Bind a callback to a push notification of a given type. @@ -148,45 +147,41 @@ function deregister() { * if we attempt to bind two callbacks to the PushReceived event for reportComment notifications, * the second will overwrite the first. * - * @param {String} notificationType - * @param {Function} callback - * @param {String} [triggerEvent] - The event that should trigger this callback. Should be one of UrbanAirship.EventType + * @param triggerEvent - The event that should trigger this callback. Should be one of UrbanAirship.EventType */ -function bind(notificationType, callback, triggerEvent) { - if (!notificationEventActionMap[triggerEvent]) { - notificationEventActionMap[triggerEvent] = {}; +function bind(notificationType: string, callback: NotificationEventActionCallback, triggerEvent: EventType) { + let actionMap = notificationEventActionMap[triggerEvent]; + + if (!actionMap) { + actionMap = {}; } - notificationEventActionMap[triggerEvent][notificationType] = callback; + + actionMap[notificationType] = callback; + notificationEventActionMap[triggerEvent] = actionMap; } /** * Bind a callback to be executed when a push notification of a given type is received. - * - * @param {String} notificationType - * @param {Function} callback */ -function onReceived(notificationType, callback) { +const onReceived: OnReceived = (notificationType, callback) => { bind(notificationType, callback, EventType.PushReceived); -} +}; /** * Bind a callback to be executed when a push notification of a given type is tapped by the user. - * - * @param {String} notificationType - * @param {Function} callback */ -function onSelected(notificationType, callback) { +const onSelected: OnSelected = (notificationType, callback) => { bind(notificationType, callback, EventType.NotificationResponse); -} +}; /** * Clear all push notifications */ -function clearNotifications() { +const clearNotifications: ClearNotifications = () => { Airship.push.clearNotifications(); -} +}; -export default { +const PushNotification: PushNotificationType = { init, register, deregister, @@ -195,3 +190,5 @@ export default { TYPE: NotificationType, clearNotifications, }; + +export default PushNotification; diff --git a/src/libs/Notification/PushNotification/index.ts b/src/libs/Notification/PushNotification/index.ts index 88136ff5dc72..1e5499d1fe7d 100644 --- a/src/libs/Notification/PushNotification/index.ts +++ b/src/libs/Notification/PushNotification/index.ts @@ -1,7 +1,8 @@ import NotificationType from './NotificationType'; +import PushNotificationType from './types'; // Push notifications are only supported on mobile, so we'll just noop here -export default { +const PushNotification: PushNotificationType = { init: () => {}, register: () => {}, deregister: () => {}, @@ -10,3 +11,5 @@ export default { TYPE: NotificationType, clearNotifications: () => {}, }; + +export default PushNotification; diff --git a/src/libs/Notification/PushNotification/shouldShowPushNotification.ts b/src/libs/Notification/PushNotification/shouldShowPushNotification.ts index ce1f013be90f..46f99fcc9271 100644 --- a/src/libs/Notification/PushNotification/shouldShowPushNotification.ts +++ b/src/libs/Notification/PushNotification/shouldShowPushNotification.ts @@ -1,13 +1,8 @@ import {PushPayload} from '@ua/react-native-airship'; -import {OnyxUpdate} from 'react-native-onyx'; import Log from '@libs/Log'; import * as ReportActionUtils from '@libs/ReportActionsUtils'; import * as Report from '@userActions/Report'; - -type PushData = { - onyxData: OnyxUpdate[]; - reportID?: number; -}; +import {NotificationData} from './NotificationType'; /** * Returns whether the given Airship notification should be shown depending on the current state of the app @@ -22,15 +17,15 @@ export default function shouldShowPushNotification(pushPayload: PushPayload): bo payload = JSON.parse(payload); } - const pushData = payload as PushData; + const data = payload as NotificationData; - if (!pushData.reportID) { + if (!data.reportID) { Log.info('[PushNotification] Not a report action notification. Showing notification'); return true; } - const reportAction = ReportActionUtils.getLatestReportActionFromOnyxData(pushData.onyxData); - const shouldShow = Report.shouldShowReportActionNotification(String(pushData.reportID), reportAction, true); + const reportAction = ReportActionUtils.getLatestReportActionFromOnyxData(data.onyxData ?? null); + const shouldShow = Report.shouldShowReportActionNotification(String(data.reportID), reportAction, true); Log.info(`[PushNotification] ${shouldShow ? 'Showing' : 'Not showing'} notification`); return shouldShow; } diff --git a/src/libs/Notification/PushNotification/subscribeToReportCommentPushNotifications.ts b/src/libs/Notification/PushNotification/subscribeToReportCommentPushNotifications.ts index ede873f79c6e..547ecb1de5b2 100644 --- a/src/libs/Notification/PushNotification/subscribeToReportCommentPushNotifications.ts +++ b/src/libs/Notification/PushNotification/subscribeToReportCommentPushNotifications.ts @@ -12,7 +12,7 @@ import PushNotification from './index'; export default function subscribeToReportCommentPushNotifications() { PushNotification.onReceived(PushNotification.TYPE.REPORT_COMMENT, ({reportID, reportActionID, onyxData}) => { Log.info(`[PushNotification] received report comment notification in the ${Visibility.isVisible() ? 'foreground' : 'background'}`, false, {reportID, reportActionID}); - Onyx.update(onyxData); + Onyx.update(onyxData ?? []); backgroundRefresh(); }); @@ -33,9 +33,14 @@ export default function subscribeToReportCommentPushNotifications() { } Log.info('[PushNotification] onSelected() - Navigation is ready. Navigating...', false, {reportID, reportActionID}); - Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(reportID)); + Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(String(reportID))); } catch (error) { - Log.alert('[PushNotification] onSelected() - failed', {reportID, reportActionID, error: error.message}); + let errorMessage = String(error); + if (error instanceof Error) { + errorMessage = error.message; + } + + Log.alert('[PushNotification] onSelected() - failed', {reportID, reportActionID, error: errorMessage}); } }); }); diff --git a/src/libs/Notification/PushNotification/types.ts b/src/libs/Notification/PushNotification/types.ts new file mode 100644 index 000000000000..f72ee1af887a --- /dev/null +++ b/src/libs/Notification/PushNotification/types.ts @@ -0,0 +1,22 @@ +import {ValueOf} from 'type-fest'; +import NotificationType, {NotificationDataMap} from './NotificationType'; + +type Init = () => void; +type Register = (notificationID: string | number) => void; +type Deregister = () => void; +type OnReceived = >(notificationType: T, callback: (data: NotificationDataMap[T]) => void) => void; +type OnSelected = >(notificationType: T, callback: (data: NotificationDataMap[T]) => void) => void; +type ClearNotifications = () => void; + +type PushNotification = { + init: Init; + register: Register; + deregister: Deregister; + onReceived: OnReceived; + onSelected: OnSelected; + TYPE: typeof NotificationType; + clearNotifications: ClearNotifications; +}; + +export default PushNotification; +export type {ClearNotifications, Deregister, Init, OnReceived, OnSelected, Register}; diff --git a/src/types/onyx/OnyxUpdatesFromServer.ts b/src/types/onyx/OnyxUpdatesFromServer.ts index 50b1503b90bd..843d3ae86e46 100644 --- a/src/types/onyx/OnyxUpdatesFromServer.ts +++ b/src/types/onyx/OnyxUpdatesFromServer.ts @@ -2,9 +2,11 @@ import {OnyxUpdate} from 'react-native-onyx'; import Request from './Request'; import Response from './Response'; +type OnyxServerUpdate = OnyxUpdate & {shouldNotify?: boolean}; + type OnyxUpdateEvent = { eventType: string; - data: OnyxUpdate[]; + data: OnyxServerUpdate[]; }; type OnyxUpdatesFromServer = { @@ -16,4 +18,4 @@ type OnyxUpdatesFromServer = { updates?: OnyxUpdateEvent[]; }; -export type {OnyxUpdatesFromServer, OnyxUpdateEvent}; +export type {OnyxUpdatesFromServer, OnyxUpdateEvent, OnyxServerUpdate}; From b8e581eddb4de337537d930e93d4a47e8e41b2a3 Mon Sep 17 00:00:00 2001 From: Jakub Trzebiatowski Date: Wed, 29 Nov 2023 14:38:05 +0100 Subject: [PATCH 39/92] Implement Localize.formatMessageElementList --- src/libs/Localize/index.ts | 58 +++++++++++-- src/libs/MessageElement.ts | 11 +++ src/libs/ReportActionsUtils.ts | 145 +++++++++++---------------------- src/libs/actions/IOU.js | 2 +- tests/unit/LocalizeTests.js | 8 +- 5 files changed, 115 insertions(+), 109 deletions(-) create mode 100644 src/libs/MessageElement.ts diff --git a/src/libs/Localize/index.ts b/src/libs/Localize/index.ts index 488ff0d9b98a..34116252a7c7 100644 --- a/src/libs/Localize/index.ts +++ b/src/libs/Localize/index.ts @@ -8,6 +8,8 @@ import {TranslationFlatObject, TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; import LocaleListener from './LocaleListener'; import BaseLocaleListener from './LocaleListener/BaseLocaleListener'; +import BaseLocale from "@libs/Localize/LocaleListener/types"; +import {MessageTextElement, MessageElementBase} from "@libs/MessageElement"; // Current user mail is needed for handling missing translations let userEmail = ''; @@ -27,6 +29,7 @@ LocaleListener.connect(); // Note: This has to be initialized inside a function and not at the top level of the file, because Intl is polyfilled, // and if React Native executes this code upon import, then the polyfill will not be available yet and it will barf let CONJUNCTION_LIST_FORMATS_FOR_LOCALES: Record; + function init() { CONJUNCTION_LIST_FORMATS_FOR_LOCALES = Object.values(CONST.LOCALES).reduce((memo: Record, locale) => { // This is not a supported locale, so we'll use ES_ES instead @@ -121,15 +124,51 @@ function translateIfPhraseKey(message: MaybePhraseKey): string { } } +function getPreferredListFormat(): Intl.ListFormat { + if (!CONJUNCTION_LIST_FORMATS_FOR_LOCALES) { + init(); + } + + return CONJUNCTION_LIST_FORMATS_FOR_LOCALES[BaseLocaleListener.getPreferredLocale()]; +} + /** * Format an array into a string with comma and "and" ("a dog, a cat and a chicken") */ -function arrayToString(anArray: string[]) { - if (!CONJUNCTION_LIST_FORMATS_FOR_LOCALES) { - init(); +function formatList(components: string[]) { + const listFormat = getPreferredListFormat(); + return listFormat.format(components); +} + +function formatMessageElementList(elements: readonly E[]): ReadonlyArray { + const listFormat = getPreferredListFormat(); + const parts = listFormat.formatToParts(elements.map((e) => e.content)); + + console.log(parts); + + const resultElements: Array = []; + + let nextElementIndex = 0; + for (const part of parts) { + if (part.type === "element") { + /** + * The standard guarantees that all input elements will be present in the constructed parts, each exactly + * once, and without any modifications: https://tc39.es/ecma402/#sec-createpartsfromlist + */ + const element = elements[nextElementIndex++]; + + resultElements.push(element); + } else { + const literalElement: MessageTextElement = { + kind: "text", + content: part.value, + }; + + resultElements.push(literalElement); + } } - const listFormat = CONJUNCTION_LIST_FORMATS_FOR_LOCALES[BaseLocaleListener.getPreferredLocale()]; - return listFormat.format(anArray); + + return resultElements; } /** @@ -139,5 +178,12 @@ function getDevicePreferredLocale(): string { return RNLocalize.findBestAvailableLanguage([CONST.LOCALES.EN, CONST.LOCALES.ES])?.languageTag ?? CONST.LOCALES.DEFAULT; } -export {translate, translateLocal, translateIfPhraseKey, arrayToString, getDevicePreferredLocale}; +export { + translate, + translateLocal, + translateIfPhraseKey, + formatList, + formatMessageElementList, + getDevicePreferredLocale +}; export type {PhraseParameters, Phrase, MaybePhraseKey}; diff --git a/src/libs/MessageElement.ts b/src/libs/MessageElement.ts new file mode 100644 index 000000000000..584d7e1e289a --- /dev/null +++ b/src/libs/MessageElement.ts @@ -0,0 +1,11 @@ +type MessageElementBase = { + readonly kind: string; + readonly content: string; +}; + +type MessageTextElement = { + readonly kind: 'text'; + readonly content: string; +} & MessageElementBase; + +export type {MessageElementBase, MessageTextElement}; diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 7bfa9a13321d..2710f968039d 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -9,6 +9,7 @@ import {ActionName, ChangeLog} from '@src/types/onyx/OriginalMessage'; import Report from '@src/types/onyx/Report'; import ReportAction, {Message, ReportActions} from '@src/types/onyx/ReportAction'; import {EmptyObject, isEmptyObject} from '@src/types/utils/EmptyObject'; +import {MessageTextElement, MessageElementBase} from "./MessageElement"; import * as CollectionUtils from './CollectionUtils'; import * as Environment from './Environment/Environment'; import isReportMessageAttachment from './isReportMessageAttachment'; @@ -22,28 +23,21 @@ type LastVisibleMessage = { lastMessageHtml?: string; }; -type MemberChangeMessageElementBase = { - readonly kind: string; - readonly content: string; -}; - -type MemberChangeMessageTextElement = { - readonly kind: 'text'; - readonly content: string; -}; - type MemberChangeMessageUserMentionElement = { readonly kind: 'userMention'; readonly accountID: number; -} & MemberChangeMessageElementBase; +} & MessageElementBase; type MemberChangeMessageRoomReferenceElement = { readonly kind: 'roomReference'; readonly roomName: string; readonly roomID: number; -} & MemberChangeMessageElementBase; +} & MessageElementBase; -type MemberChangeMessageElement = MemberChangeMessageTextElement | MemberChangeMessageUserMentionElement | MemberChangeMessageRoomReferenceElement; +type MemberChangeMessageElement = + MessageTextElement + | MemberChangeMessageUserMentionElement + | MemberChangeMessageRoomReferenceElement; const allReports: OnyxCollection = {}; Onyx.connect({ @@ -664,109 +658,64 @@ function isNotifiableReportAction(reportAction: OnyxEntry): boolea return actions.includes(reportAction.actionName); } -function getMemberChangeMessageElements(reportAction: OnyxEntry): MemberChangeMessageElement[] { +function getMemberChangeMessageElements(reportAction: OnyxEntry): readonly MemberChangeMessageElement[] { const isInviteAction = isInviteMemberAction(reportAction); // currently, in the app we only have the logic show the message when invite the members const verb = isInviteAction ? Localize.translateLocal('workspace.invite.invited') : Localize.translateLocal('workspace.invite.removed'); - const messageElements: MemberChangeMessageElement[] = [ - { - kind: 'text', - content: `${verb} `, - }, - ]; - const originalMessage = reportAction?.originalMessage as ChangeLog; const targetAccountIDs: number[] = originalMessage?.targetAccountIDs ?? []; const personalDetails = PersonalDetailsUtils.getPersonalDetailsByIDs(targetAccountIDs, 0); - const mentions = targetAccountIDs.map((accountID) => { + const mentionElements = targetAccountIDs.map((accountID): MemberChangeMessageUserMentionElement => { const personalDetail = personalDetails.find((personal) => personal.accountID === accountID); - return {content: `@${PersonalDetailsUtils.getEffectiveDisplayName(personalDetail) ?? Localize.translateLocal('common.hidden')}`, accountID}; - }); - - const lastMention = mentions.pop(); - if (!lastMention) { - return []; - } + const handleText = PersonalDetailsUtils.getEffectiveDisplayName(personalDetail) ?? Localize.translateLocal('common.hidden'); - if (mentions.length === 0) { - messageElements.push({ + return { kind: 'userMention', - content: lastMention.content, - accountID: lastMention.accountID, - }); - } else if (mentions.length === 1) { - messageElements.push( - { - kind: 'userMention', - content: mentions[0].content, - accountID: mentions[0].accountID, - }, - { - kind: 'text', - content: ` ${Localize.translateLocal('common.and')} `, - }, - { - kind: 'userMention', - content: lastMention.content, - accountID: lastMention.accountID, - }, - ); - } else { - mentions.forEach((mention) => { - messageElements.push( - { - kind: 'userMention', - accountID: mention.accountID, - content: `${mention.content}`, - }, - { - kind: 'text', - content: `${Localize.translateLocal('common.serialComma')}`, - }, - ); - }); - - messageElements.push( - { - kind: 'text', - content: `${Localize.translateLocal('common.and')} `, - }, - { - kind: 'userMention', - content: lastMention.content, - accountID: lastMention.accountID, - }, - ); - } - - const roomName = originalMessage?.roomName; - if (roomName) { - const preposition = isInviteAction ? ` ${Localize.translateLocal('workspace.invite.to')} ` : ` ${Localize.translateLocal('workspace.invite.from')} `; - - if (originalMessage.reportID) { - messageElements.push( - { - kind: 'text', - content: preposition, - }, - { - kind: 'roomReference', - roomName, - roomID: originalMessage.reportID, - content: roomName, - }, - ); + content: `@${handleText}`, + accountID, + }; + }); + + const buildRoomElements = (): readonly MemberChangeMessageElement[] => { + const roomName = originalMessage?.roomName; + + if (roomName) { + const preposition = isInviteAction ? ` ${Localize.translateLocal('workspace.invite.to')} ` : ` ${Localize.translateLocal('workspace.invite.from')} `; + + if (originalMessage.reportID) { + return [ + { + kind: 'text', + content: preposition, + }, + { + kind: 'roomReference', + roomName, + roomID: originalMessage.reportID, + content: roomName, + }, + ]; + } } + + return []; } - return messageElements; + return [ + { + kind: 'text', + content: `${verb} `, + }, + ...Localize.formatMessageElementList(mentionElements), + ...buildRoomElements(), + ]; } function getMemberChangeMessageFragment(reportAction: OnyxEntry): Message { - const messageElements: MemberChangeMessageElement[] = getMemberChangeMessageElements(reportAction); + const messageElements: readonly MemberChangeMessageElement[] = getMemberChangeMessageElements(reportAction); const html = messageElements .map((messageElement) => { switch (messageElement.kind) { diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 939a11dad511..a7db8d0b93cd 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -926,7 +926,7 @@ function createSplitsAndOnyxData(participants, currentUserLogin, currentUserAcco // ReportID is -2 (aka "deleted") on the group transaction: https://github.com/Expensify/Auth/blob/3fa2698654cd4fbc30f9de38acfca3fbeb7842e4/auth/command/SplitTransaction.cpp#L24-L27 const formattedParticipants = isOwnPolicyExpenseChat ? [currentUserLogin, ReportUtils.getReportName(splitChatReport)] - : Localize.arrayToString([currentUserLogin, ..._.map(participants, (participant) => participant.login || '')]); + : Localize.formatList([currentUserLogin, ..._.map(participants, (participant) => participant.login || '')]); const splitTransaction = TransactionUtils.buildOptimisticTransaction( amount, diff --git a/tests/unit/LocalizeTests.js b/tests/unit/LocalizeTests.js index 4c89d587fc06..7693a0a4a88d 100644 --- a/tests/unit/LocalizeTests.js +++ b/tests/unit/LocalizeTests.js @@ -15,7 +15,7 @@ describe('localize', () => { afterEach(() => Onyx.clear()); - describe('arrayToString', () => { + describe('formatList', () => { test.each([ [ [], @@ -52,9 +52,9 @@ describe('localize', () => { [CONST.LOCALES.ES]: 'rory, vit e ionatan', }, ], - ])('arrayToSpokenList(%s)', (input, {[CONST.LOCALES.DEFAULT]: expectedOutput, [CONST.LOCALES.ES]: expectedOutputES}) => { - expect(Localize.arrayToString(input)).toBe(expectedOutput); - return Onyx.set(ONYXKEYS.NVP_PREFERRED_LOCALE, CONST.LOCALES.ES).then(() => expect(Localize.arrayToString(input)).toBe(expectedOutputES)); + ])('formatList(%s)', (input, {[CONST.LOCALES.DEFAULT]: expectedOutput, [CONST.LOCALES.ES]: expectedOutputES}) => { + expect(Localize.formatList(input)).toBe(expectedOutput); + return Onyx.set(ONYXKEYS.NVP_PREFERRED_LOCALE, CONST.LOCALES.ES).then(() => expect(Localize.formatList(input)).toBe(expectedOutputES)); }); }); }); From 2aa37d240e9d53069294ad5d81075c54a969c583 Mon Sep 17 00:00:00 2001 From: Peter Velkov Date: Mon, 6 Nov 2023 09:32:15 +0200 Subject: [PATCH 40/92] Refactor Image for Web/Desktop with Source Headers - Introduced `BaseImage` component that branches between native and web implementations. - **Native**: Utilizes `FastImage` directly. - **Web**: Minor adjustments made to the `onLoad` event signature for compatibility. - Eliminated `Image/index.native.js` as both native and web components now leverage a unified high-level implementation for image loading and rendering. --- src/components/Image/BaseImage.js | 28 +++++++++++ src/components/Image/BaseImage.native.js | 3 ++ src/components/Image/index.js | 52 +++++++------------ src/components/Image/index.native.js | 63 ------------------------ 4 files changed, 50 insertions(+), 96 deletions(-) create mode 100644 src/components/Image/BaseImage.js create mode 100644 src/components/Image/BaseImage.native.js delete mode 100644 src/components/Image/index.native.js diff --git a/src/components/Image/BaseImage.js b/src/components/Image/BaseImage.js new file mode 100644 index 000000000000..199cbb78aab0 --- /dev/null +++ b/src/components/Image/BaseImage.js @@ -0,0 +1,28 @@ +import React, {useCallback} from 'react'; +import {Image as RNImage} from 'react-native'; +import {defaultProps, imagePropTypes} from './imagePropTypes'; + +function BaseImage({onLoad, ...props}) { + const imageLoadedSuccessfully = useCallback( + ({nativeEvent}) => { + // We override `onLoad`, so both web and native have the same signature + const {width, height} = nativeEvent.source; + onLoad({nativeEvent: {width, height}}); + }, + [onLoad], + ); + + return ( + + ); +} + +BaseImage.propTypes = imagePropTypes; +BaseImage.defaultProps = defaultProps; + +export default BaseImage; diff --git a/src/components/Image/BaseImage.native.js b/src/components/Image/BaseImage.native.js new file mode 100644 index 000000000000..a621947267a1 --- /dev/null +++ b/src/components/Image/BaseImage.native.js @@ -0,0 +1,3 @@ +import RNFastImage from 'react-native-fast-image'; + +export default RNFastImage; diff --git a/src/components/Image/index.js b/src/components/Image/index.js index ef1a69e19c12..440f7ada7348 100644 --- a/src/components/Image/index.js +++ b/src/components/Image/index.js @@ -1,51 +1,37 @@ import lodashGet from 'lodash/get'; -import React, {useEffect, useMemo} from 'react'; -import {Image as RNImage} from 'react-native'; +import React, {useMemo} from 'react'; import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import BaseImage from './BaseImage'; import {defaultProps, imagePropTypes} from './imagePropTypes'; import RESIZE_MODES from './resizeModes'; function Image(props) { - const {source: propsSource, isAuthTokenRequired, onLoad, session} = props; - /** - * Check if the image source is a URL - if so the `encryptedAuthToken` is appended - * to the source. - */ + const {source: propsSource, isAuthTokenRequired, session, ...forwardedProps} = props; + + // Update the source to include the auth token if required const source = useMemo(() => { - if (isAuthTokenRequired) { - // There is currently a `react-native-web` bug preventing the authToken being passed - // in the headers of the image request so the authToken is added as a query param. - // On native the authToken IS passed in the image request headers - const authToken = lodashGet(session, 'encryptedAuthToken', null); - return {uri: `${propsSource.uri}?encryptedAuthToken=${encodeURIComponent(authToken)}`}; + if (typeof lodashGet(propsSource, 'uri') === 'number') { + return propsSource.uri; + } + if (typeof propsSource !== 'number' && isAuthTokenRequired) { + const authToken = lodashGet(session, 'encryptedAuthToken'); + return { + ...propsSource, + headers: { + [CONST.CHAT_ATTACHMENT_TOKEN_KEY]: authToken, + }, + }; } + return propsSource; // The session prop is not required, as it causes the image to reload whenever the session changes. For more information, please refer to issue #26034. // eslint-disable-next-line react-hooks/exhaustive-deps }, [propsSource, isAuthTokenRequired]); - /** - * The natural image dimensions are retrieved using the updated source - * and as a result the `onLoad` event needs to be manually invoked to return these dimensions - */ - useEffect(() => { - // If an onLoad callback was specified then manually call it and pass - // the natural image dimensions to match the native API - if (onLoad == null) { - return; - } - RNImage.getSize(source.uri, (width, height) => { - onLoad({nativeEvent: {width, height}}); - }); - }, [onLoad, source]); - - // Omit the props which the underlying RNImage won't use - const forwardedProps = _.omit(props, ['source', 'onLoad', 'session', 'isAuthTokenRequired']); - return ( - { - const {width, height} = evt.nativeEvent; - dimensionsCache.set(source.uri, {width, height}); - if (props.onLoad) { - props.onLoad(evt); - } - }} - /> - ); -} - -Image.propTypes = imagePropTypes; -Image.defaultProps = defaultProps; -Image.displayName = 'Image'; -const ImageWithOnyx = withOnyx({ - session: { - key: ONYXKEYS.SESSION, - }, -})(Image); -ImageWithOnyx.resizeMode = RESIZE_MODES; -ImageWithOnyx.resolveDimensions = resolveDimensions; - -export default ImageWithOnyx; From 19b605e4e52c1c71d1515065c0ba2de7af0c61b1 Mon Sep 17 00:00:00 2001 From: Peter Velkov Date: Mon, 20 Nov 2023 20:22:57 +0200 Subject: [PATCH 41/92] Add react-native-web patch for image header support --- ...-web+0.19.9+005+image-header-support.patch | 200 ++++++++++++++++++ 1 file changed, 200 insertions(+) create mode 100644 patches/react-native-web+0.19.9+005+image-header-support.patch diff --git a/patches/react-native-web+0.19.9+005+image-header-support.patch b/patches/react-native-web+0.19.9+005+image-header-support.patch new file mode 100644 index 000000000000..4652e22662f0 --- /dev/null +++ b/patches/react-native-web+0.19.9+005+image-header-support.patch @@ -0,0 +1,200 @@ +diff --git a/node_modules/react-native-web/dist/exports/Image/index.js b/node_modules/react-native-web/dist/exports/Image/index.js +index 95355d5..19109fc 100644 +--- a/node_modules/react-native-web/dist/exports/Image/index.js ++++ b/node_modules/react-native-web/dist/exports/Image/index.js +@@ -135,7 +135,22 @@ function resolveAssetUri(source) { + } + return uri; + } +-var Image = /*#__PURE__*/React.forwardRef((props, ref) => { ++function raiseOnErrorEvent(uri, _ref) { ++ var onError = _ref.onError, ++ onLoadEnd = _ref.onLoadEnd; ++ if (onError) { ++ onError({ ++ nativeEvent: { ++ error: "Failed to load resource " + uri + " (404)" ++ } ++ }); ++ } ++ if (onLoadEnd) onLoadEnd(); ++} ++function hasSourceDiff(a, b) { ++ return a.uri !== b.uri || JSON.stringify(a.headers) !== JSON.stringify(b.headers); ++} ++var BaseImage = /*#__PURE__*/React.forwardRef((props, ref) => { + var ariaLabel = props['aria-label'], + blurRadius = props.blurRadius, + defaultSource = props.defaultSource, +@@ -236,16 +251,10 @@ var Image = /*#__PURE__*/React.forwardRef((props, ref) => { + } + }, function error() { + updateState(ERRORED); +- if (onError) { +- onError({ +- nativeEvent: { +- error: "Failed to load resource " + uri + " (404)" +- } +- }); +- } +- if (onLoadEnd) { +- onLoadEnd(); +- } ++ raiseOnErrorEvent(uri, { ++ onError, ++ onLoadEnd ++ }); + }); + } + function abortPendingRequest() { +@@ -277,10 +286,78 @@ var Image = /*#__PURE__*/React.forwardRef((props, ref) => { + suppressHydrationWarning: true + }), hiddenImage, createTintColorSVG(tintColor, filterRef.current)); + }); +-Image.displayName = 'Image'; ++BaseImage.displayName = 'Image'; ++ ++/** ++ * This component handles specifically loading an image source with headers ++ * default source is never loaded using headers ++ */ ++var ImageWithHeaders = /*#__PURE__*/React.forwardRef((props, ref) => { ++ // $FlowIgnore: This component would only be rendered when `source` matches `ImageSource` ++ var nextSource = props.source; ++ var _React$useState3 = React.useState(''), ++ blobUri = _React$useState3[0], ++ setBlobUri = _React$useState3[1]; ++ var request = React.useRef({ ++ cancel: () => {}, ++ source: { ++ uri: '', ++ headers: {} ++ }, ++ promise: Promise.resolve('') ++ }); ++ var onError = props.onError, ++ onLoadStart = props.onLoadStart, ++ onLoadEnd = props.onLoadEnd; ++ React.useEffect(() => { ++ if (!hasSourceDiff(nextSource, request.current.source)) { ++ return; ++ } ++ ++ // When source changes we want to clean up any old/running requests ++ request.current.cancel(); ++ if (onLoadStart) { ++ onLoadStart(); ++ } ++ ++ // Store a ref for the current load request so we know what's the last loaded source, ++ // and so we can cancel it if a different source is passed through props ++ request.current = ImageLoader.loadWithHeaders(nextSource); ++ request.current.promise.then(uri => setBlobUri(uri)).catch(() => raiseOnErrorEvent(request.current.source.uri, { ++ onError, ++ onLoadEnd ++ })); ++ }, [nextSource, onLoadStart, onError, onLoadEnd]); ++ ++ // Cancel any request on unmount ++ React.useEffect(() => request.current.cancel, []); ++ var propsToPass = _objectSpread(_objectSpread({}, props), {}, { ++ // `onLoadStart` is called from the current component ++ // We skip passing it down to prevent BaseImage raising it a 2nd time ++ onLoadStart: undefined, ++ // Until the current component resolves the request (using headers) ++ // we skip forwarding the source so the base component doesn't attempt ++ // to load the original source ++ source: blobUri ? _objectSpread(_objectSpread({}, nextSource), {}, { ++ uri: blobUri ++ }) : undefined ++ }); ++ return /*#__PURE__*/React.createElement(BaseImage, _extends({ ++ ref: ref ++ }, propsToPass)); ++}); + + // $FlowIgnore: This is the correct type, but casting makes it unhappy since the variables aren't defined yet +-var ImageWithStatics = Image; ++var ImageWithStatics = /*#__PURE__*/React.forwardRef((props, ref) => { ++ if (props.source && props.source.headers) { ++ return /*#__PURE__*/React.createElement(ImageWithHeaders, _extends({ ++ ref: ref ++ }, props)); ++ } ++ return /*#__PURE__*/React.createElement(BaseImage, _extends({ ++ ref: ref ++ }, props)); ++}); + ImageWithStatics.getSize = function (uri, success, failure) { + ImageLoader.getSize(uri, success, failure); + }; +diff --git a/node_modules/react-native-web/dist/modules/ImageLoader/index.js b/node_modules/react-native-web/dist/modules/ImageLoader/index.js +index bc06a87..e309394 100644 +--- a/node_modules/react-native-web/dist/modules/ImageLoader/index.js ++++ b/node_modules/react-native-web/dist/modules/ImageLoader/index.js +@@ -76,7 +76,7 @@ var ImageLoader = { + var image = requests["" + requestId]; + if (image) { + var naturalHeight = image.naturalHeight, +- naturalWidth = image.naturalWidth; ++ naturalWidth = image.naturalWidth; + if (naturalHeight && naturalWidth) { + success(naturalWidth, naturalHeight); + complete = true; +@@ -102,11 +102,19 @@ var ImageLoader = { + id += 1; + var image = new window.Image(); + image.onerror = onError; +- image.onload = e => { ++ image.onload = nativeEvent => { + // avoid blocking the main thread +- var onDecode = () => onLoad({ +- nativeEvent: e +- }); ++ var onDecode = () => { ++ // Append `source` to match RN's ImageLoadEvent interface ++ nativeEvent.source = { ++ uri: image.src, ++ width: image.naturalWidth, ++ height: image.naturalHeight ++ }; ++ onLoad({ ++ nativeEvent ++ }); ++ }; + if (typeof image.decode === 'function') { + // Safari currently throws exceptions when decoding svgs. + // We want to catch that error and allow the load handler +@@ -120,6 +128,32 @@ var ImageLoader = { + requests["" + id] = image; + return id; + }, ++ loadWithHeaders(source) { ++ var uri; ++ var abortController = new AbortController(); ++ var request = new Request(source.uri, { ++ headers: source.headers, ++ signal: abortController.signal ++ }); ++ request.headers.append('accept', 'image/*'); ++ var promise = fetch(request).then(response => response.blob()).then(blob => { ++ uri = URL.createObjectURL(blob); ++ return uri; ++ }).catch(error => { ++ if (error.name === 'AbortError') { ++ return ''; ++ } ++ throw error; ++ }); ++ return { ++ promise, ++ source, ++ cancel: () => { ++ abortController.abort(); ++ URL.revokeObjectURL(uri); ++ } ++ }; ++ }, + prefetch(uri) { + return new Promise((resolve, reject) => { + ImageLoader.load(uri, () => { From cc4920851bed2a50c5e22cea1a2861669042e258 Mon Sep 17 00:00:00 2001 From: Peter Velkov Date: Fri, 24 Nov 2023 16:55:02 +0200 Subject: [PATCH 42/92] refactor(components): Exclude Auth Token for External Avatar Images This patch focuses on resolving issues encountered with avatar image loading, specifically addressing the challenges related to CORS (Cross-Origin Resource Sharing) errors. Changes: - Removed the `isAuthTokenRequired` flag from the `AttachmentModal` component in various files, including `ProfilePage.js`, `RoomHeaderAvatars.js`, and `DetailsPage.js`. This change is crucial for loading of avatar images that are hosted externally. Rationale: - The primary purpose of this modification is to streamline the loading process for avatars by removing the unnecessary inclusion of authentication tokens in requests for external images. This approach aligns with standard practices for handling externally hosted content and aims to enhance compatibility and performance. - Raised a question here as whether there are cases of avatar images that need authentication: https://github.com/Expensify/App/pull/24425/files#r1404352872 This update is expected to resolve the CORS errors associated with avatar image loading, thereby improving the overall functionality and user experience in our application. --- src/components/RoomHeaderAvatars.js | 2 -- src/pages/DetailsPage.js | 1 - src/pages/ProfilePage.js | 1 - 3 files changed, 4 deletions(-) diff --git a/src/components/RoomHeaderAvatars.js b/src/components/RoomHeaderAvatars.js index 0bb0f11d747c..da8e0b76bede 100644 --- a/src/components/RoomHeaderAvatars.js +++ b/src/components/RoomHeaderAvatars.js @@ -33,7 +33,6 @@ function RoomHeaderAvatars(props) { @@ -78,7 +77,6 @@ function RoomHeaderAvatars(props) { diff --git a/src/pages/DetailsPage.js b/src/pages/DetailsPage.js index 66345107dbb1..6d3f0198bbfe 100755 --- a/src/pages/DetailsPage.js +++ b/src/pages/DetailsPage.js @@ -134,7 +134,6 @@ function DetailsPage(props) { {({show}) => ( diff --git a/src/pages/ProfilePage.js b/src/pages/ProfilePage.js index ffe8271629f4..d21d6c82f34a 100755 --- a/src/pages/ProfilePage.js +++ b/src/pages/ProfilePage.js @@ -159,7 +159,6 @@ function ProfilePage(props) { From c216c935774e93bced01841c89f9ad64d8049080 Mon Sep 17 00:00:00 2001 From: Nam Le Date: Wed, 29 Nov 2023 23:12:11 +0700 Subject: [PATCH 43/92] refactor use Intl handle listing --- src/languages/en.ts | 1 - src/languages/es.ts | 1 - src/libs/Localize/index.ts | 19 ++++--------------- src/libs/ReportActionsUtils.ts | 9 +++------ 4 files changed, 7 insertions(+), 23 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 95c97d1ff770..6ca1ac7447b7 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -270,7 +270,6 @@ export default { selectCurrency: 'Select a currency', card: 'Card', required: 'Required', - serialComma: ', ', showing: 'Showing', of: 'of', }, diff --git a/src/languages/es.ts b/src/languages/es.ts index 6f91f492ecc7..980fc2e13a4c 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -260,7 +260,6 @@ export default { selectCurrency: 'Selecciona una moneda', card: 'Tarjeta', required: 'Obligatorio', - serialComma: ' ', showing: 'Mostrando', of: 'de', }, diff --git a/src/libs/Localize/index.ts b/src/libs/Localize/index.ts index 34116252a7c7..ab65192834d8 100644 --- a/src/libs/Localize/index.ts +++ b/src/libs/Localize/index.ts @@ -1,6 +1,7 @@ import * as RNLocalize from 'react-native-localize'; import Onyx from 'react-native-onyx'; import Log from '@libs/Log'; +import {MessageElementBase, MessageTextElement} from '@libs/MessageElement'; import Config from '@src/CONFIG'; import CONST from '@src/CONST'; import translations from '@src/languages/translations'; @@ -8,8 +9,6 @@ import {TranslationFlatObject, TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; import LocaleListener from './LocaleListener'; import BaseLocaleListener from './LocaleListener/BaseLocaleListener'; -import BaseLocale from "@libs/Localize/LocaleListener/types"; -import {MessageTextElement, MessageElementBase} from "@libs/MessageElement"; // Current user mail is needed for handling missing translations let userEmail = ''; @@ -143,14 +142,11 @@ function formatList(components: string[]) { function formatMessageElementList(elements: readonly E[]): ReadonlyArray { const listFormat = getPreferredListFormat(); const parts = listFormat.formatToParts(elements.map((e) => e.content)); - - console.log(parts); - const resultElements: Array = []; let nextElementIndex = 0; for (const part of parts) { - if (part.type === "element") { + if (part.type === 'element') { /** * The standard guarantees that all input elements will be present in the constructed parts, each exactly * once, and without any modifications: https://tc39.es/ecma402/#sec-createpartsfromlist @@ -160,7 +156,7 @@ function formatMessageElementList(elements: readon resultElements.push(element); } else { const literalElement: MessageTextElement = { - kind: "text", + kind: 'text', content: part.value, }; @@ -178,12 +174,5 @@ function getDevicePreferredLocale(): string { return RNLocalize.findBestAvailableLanguage([CONST.LOCALES.EN, CONST.LOCALES.ES])?.languageTag ?? CONST.LOCALES.DEFAULT; } -export { - translate, - translateLocal, - translateIfPhraseKey, - formatList, - formatMessageElementList, - getDevicePreferredLocale -}; +export {translate, translateLocal, translateIfPhraseKey, formatList, formatMessageElementList, getDevicePreferredLocale}; export type {PhraseParameters, Phrase, MaybePhraseKey}; diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 2710f968039d..c6367e4bc273 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -9,12 +9,12 @@ import {ActionName, ChangeLog} from '@src/types/onyx/OriginalMessage'; import Report from '@src/types/onyx/Report'; import ReportAction, {Message, ReportActions} from '@src/types/onyx/ReportAction'; import {EmptyObject, isEmptyObject} from '@src/types/utils/EmptyObject'; -import {MessageTextElement, MessageElementBase} from "./MessageElement"; import * as CollectionUtils from './CollectionUtils'; import * as Environment from './Environment/Environment'; import isReportMessageAttachment from './isReportMessageAttachment'; import * as Localize from './Localize'; import Log from './Log'; +import {MessageElementBase, MessageTextElement} from './MessageElement'; import * as PersonalDetailsUtils from './PersonalDetailsUtils'; type LastVisibleMessage = { @@ -34,10 +34,7 @@ type MemberChangeMessageRoomReferenceElement = { readonly roomID: number; } & MessageElementBase; -type MemberChangeMessageElement = - MessageTextElement - | MemberChangeMessageUserMentionElement - | MemberChangeMessageRoomReferenceElement; +type MemberChangeMessageElement = MessageTextElement | MemberChangeMessageUserMentionElement | MemberChangeMessageRoomReferenceElement; const allReports: OnyxCollection = {}; Onyx.connect({ @@ -702,7 +699,7 @@ function getMemberChangeMessageElements(reportAction: OnyxEntry): } return []; - } + }; return [ { From 4d403abab2a373a4779b7d7640308ca09628144b Mon Sep 17 00:00:00 2001 From: Nam Le Date: Wed, 29 Nov 2023 23:15:24 +0700 Subject: [PATCH 44/92] remove new line --- src/libs/Localize/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/Localize/index.ts b/src/libs/Localize/index.ts index ab65192834d8..77c34ebdc576 100644 --- a/src/libs/Localize/index.ts +++ b/src/libs/Localize/index.ts @@ -28,7 +28,6 @@ LocaleListener.connect(); // Note: This has to be initialized inside a function and not at the top level of the file, because Intl is polyfilled, // and if React Native executes this code upon import, then the polyfill will not be available yet and it will barf let CONJUNCTION_LIST_FORMATS_FOR_LOCALES: Record; - function init() { CONJUNCTION_LIST_FORMATS_FOR_LOCALES = Object.values(CONST.LOCALES).reduce((memo: Record, locale) => { // This is not a supported locale, so we'll use ES_ES instead From 70bf7426d0c3820e19012e6eb8d2cc86c8daa0c4 Mon Sep 17 00:00:00 2001 From: Viktoryia Kliushun Date: Wed, 29 Nov 2023 17:55:36 +0100 Subject: [PATCH 45/92] Fix TS issues after merging main --- src/SCREENS.ts | 2 ++ .../Navigation/AppNavigator/AuthScreens.tsx | 6 ++++- .../AppNavigator/ModalStackNavigators.tsx | 11 +++++++- .../AppNavigator/Navigators/Overlay.tsx | 3 +-- .../AppNavigator/ReportScreenIDSetter.ts | 10 +++---- .../AppNavigator/ReportScreenWrapper.tsx | 1 + src/libs/compose.ts | 27 ++++++++++--------- 7 files changed, 36 insertions(+), 24 deletions(-) diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 99c0ea877911..6638a207852d 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -89,6 +89,7 @@ const SCREENS = { PRIVATE_NOTES: 'Private_Notes', ROOM_MEMBERS: 'RoomMembers', ROOM_INVITE: 'RoomInvite', + REFERRAL: 'Referral', }, SIGN_IN_WITH_APPLE_DESKTOP: 'AppleSignInDesktop', SIGN_IN_WITH_GOOGLE_DESKTOP: 'GoogleSignInDesktop', @@ -180,6 +181,7 @@ const SCREENS = { REIMBURSEMENT_ACCOUNT_ROOT: 'Reimbursement_Account_Root', WALLET_STATEMENT_ROOT: 'WalletStatement_Root', SIGN_IN_ROOT: 'SignIn_Root', + REFERRAL_DETAILS: 'Referral_Details', SPLIT_DETAILS: { ROOT: 'SplitDetails_Root', diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.tsx b/src/libs/Navigation/AppNavigator/AuthScreens.tsx index c3f2da38cf85..d93cfcc63967 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.tsx +++ b/src/libs/Navigation/AppNavigator/AuthScreens.tsx @@ -29,6 +29,7 @@ import ROUTES from '@src/ROUTES'; import SCREENS from '@src/SCREENS'; import * as OnyxTypes from '@src/types/onyx'; import type {Timezone} from '@src/types/onyx/PersonalDetails'; +import {SelectedTimezone} from '@src/types/onyx/PersonalDetails'; import createCustomStackNavigator from './createCustomStackNavigator'; import defaultScreenOptions from './defaultScreenOptions'; import getRootNavigatorScreenOptions from './getRootNavigatorScreenOptions'; @@ -89,7 +90,7 @@ Onyx.connect({ } timezone = val?.[currentAccountID]?.timezone ?? {}; - const currentTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone; + const currentTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone as SelectedTimezone; // If the current timezone is different than the user's timezone, and their timezone is set to automatic // then update their timezone. @@ -309,6 +310,9 @@ function AuthScreens({isUsingMemoryOnlyKeys = null, lastUpdateIDAppliedToClient require('../../../pages/settings/Profile/LoungeAccessPage').default as React.ComponentType, [SCREENS.SETTINGS.WALLET]: () => require('../../../pages/settings/Wallet/WalletPage').default as React.ComponentType, [SCREENS.SETTINGS.WALLET_CARDS_DIGITAL_DETAILS_UPDATE_ADDRESS]: () => require('../../../pages/settings/Profile/PersonalDetails/AddressPage').default as React.ComponentType, - [SCREENS.SETTINGS.WALLET_DOMAIN_CARDS]: () => require('../../../pages/settings/Wallet/ExpensifyCardPage').default as React.ComponentType, + [SCREENS.SETTINGS.WALLET_DOMAIN_CARD]: () => require('../../../pages/settings/Wallet/ExpensifyCardPage').default as React.ComponentType, [SCREENS.SETTINGS.WALLET_REPORT_VIRTUAL_CARD_FRAUD]: () => require('../../../pages/settings/Wallet/ReportVirtualCardFraudPage').default as React.ComponentType, [SCREENS.SETTINGS.WALLET_CARD_ACTIVATE]: () => require('../../../pages/settings/Wallet/ActivatePhysicalCardPage').default as React.ComponentType, + [SCREENS.SETTINGS.WALLET_CARD_GET_PHYSICAL.NAME]: () => require('../../../pages/settings/Wallet/Card/GetPhysicalCardName').default as React.ComponentType, + [SCREENS.SETTINGS.WALLET_CARD_GET_PHYSICAL.PHONE]: () => require('../../../pages/settings/Wallet/Card/GetPhysicalCardPhone').default as React.ComponentType, + [SCREENS.SETTINGS.WALLET_CARD_GET_PHYSICAL.ADDRESS]: () => require('../../../pages/settings/Wallet/Card/GetPhysicalCardAddress').default as React.ComponentType, + [SCREENS.SETTINGS.WALLET_CARD_GET_PHYSICAL.CONFIRM]: () => require('../../../pages/settings/Wallet/Card/GetPhysicalCardConfirm').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, @@ -223,6 +227,10 @@ const SignInModalStackNavigator = createModalStackNavigator({ [SCREENS.SIGN_IN_ROOT]: () => require('../../../pages/signin/SignInModal').default as React.ComponentType, }); +const ReferralModalStackNavigator = createModalStackNavigator({ + [SCREENS.REFERRAL_DETAILS]: () => require('../../../pages/ReferralDetailsPage').default as React.ComponentType, +}); + export { MoneyRequestModalStackNavigator, SplitDetailsModalStackNavigator, @@ -248,4 +256,5 @@ export { SignInModalStackNavigator, RoomMembersModalStackNavigator, RoomInviteModalStackNavigator, + ReferralModalStackNavigator, }; diff --git a/src/libs/Navigation/AppNavigator/Navigators/Overlay.tsx b/src/libs/Navigation/AppNavigator/Navigators/Overlay.tsx index 96478df1e489..098e9a3dccf5 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/Overlay.tsx +++ b/src/libs/Navigation/AppNavigator/Navigators/Overlay.tsx @@ -13,8 +13,7 @@ type OverlayProps = { function Overlay({onPress}: OverlayProps) { const styles = useThemeStyles(); const {current} = useCardAnimation(); - // TODO: remove type assertion when useLocalize is migrated - const {translate} = useLocalize() as unknown as {translate: (phrase: string) => string}; + const {translate} = useLocalize(); return ( diff --git a/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts b/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts index 40bb2707ea01..5484aca5acdf 100644 --- a/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts +++ b/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts @@ -30,19 +30,15 @@ const getLastAccessedReportID = ( policies: OnyxCollection, isFirstTimeNewExpensifyUser: OnyxEntry, openOnAdminRoom: boolean, -): number | string => { +): string | undefined => { // If deeplink url contains reportID params, we should show the report that has this reportID. const currentRoute = Navigation.getActiveRoute(); - // TODO: get rid of assertion when ReportUtils is migrated to TS - const {reportID} = ReportUtils.parseReportRouteParams(currentRoute) as {reportID: string}; + const {reportID} = ReportUtils.parseReportRouteParams(currentRoute); if (reportID) { return reportID; } - // 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; + const lastReport = ReportUtils.findLastAccessedReport(reports, ignoreDefaultRooms, policies, !!isFirstTimeNewExpensifyUser, openOnAdminRoom); return lastReport?.reportID; }; diff --git a/src/libs/Navigation/AppNavigator/ReportScreenWrapper.tsx b/src/libs/Navigation/AppNavigator/ReportScreenWrapper.tsx index fda939a0e15d..a155cb6f3085 100644 --- a/src/libs/Navigation/AppNavigator/ReportScreenWrapper.tsx +++ b/src/libs/Navigation/AppNavigator/ReportScreenWrapper.tsx @@ -8,6 +8,7 @@ function ReportScreenWrapper({route, navigation}: ReportScreenWrapperProps) { // until the reportID is loaded and set in the route param return ( <> + {/* @ts-expect-error Error will be resolved after ReportScreen migration */} = (...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: Func): Func; +export default function compose(f1: (...args: A) => R1, f2: (a: R1) => R2): (...args: A) => R2; /* three functions */ -export default function compose(f1: (args: A) => R1, f2: (a: R1) => R2, f3: Func): Func; +export default function compose(f1: (...args: A) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3): (...args: A) => R3; /* four functions */ -export default function compose(f1: (args: A) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3, f4: Func): Func; +export default function compose(f1: (...args: A) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3, f4: (a: R3) => R4): (...args: A) => R4; /* five functions */ -export default function compose(f1: (args: A) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3, f4: (a: R3) => R4, f5: Func): Func; +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; /* 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: Func, -): Func; + f6: (a: R5) => R6, +): (...args: A) => R6; /* 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 From 6799a1d4fe8539ff799520412554a61f6af50406 Mon Sep 17 00:00:00 2001 From: Viktoryia Kliushun Date: Wed, 29 Nov 2023 18:06:37 +0100 Subject: [PATCH 46/92] Fix TS issues after merging main --- .../AppNavigator/ModalStackNavigators.tsx | 21 ++++++++++++------- .../AppNavigator/RHPScreenOptions.ts | 10 ++++----- .../getRootNavigatorScreenOptions.ts | 5 +++-- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx index f7bf35c1d876..f0289b2927bf 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx @@ -1,14 +1,8 @@ import {CardStyleInterpolators, createStackNavigator} from '@react-navigation/stack'; -import React from 'react'; -import styles from '@styles/styles'; +import React, {useMemo} from 'react'; +import useThemeStyles from '@styles/useThemeStyles'; import SCREENS from '@src/SCREENS'; -const defaultSubRouteOptions = { - cardStyle: styles.navigationScreenCardStyle, - headerShown: false, - cardStyleInterpolator: CardStyleInterpolators.forHorizontalIOS, -}; - type Screens = Record React.ComponentType>; /** @@ -20,6 +14,17 @@ function createModalStackNavigator(screens: Screens): () => React.JSX.Element { const ModalStackNavigator = createStackNavigator(); function ModalStack() { + const styles = useThemeStyles(); + + const defaultSubRouteOptions = useMemo( + () => ({ + cardStyle: styles.navigationScreenCardStyle, + headerShown: false, + cardStyleInterpolator: CardStyleInterpolators.forHorizontalIOS, + }), + [styles], + ); + return ( {Object.keys(screens).map((name) => ( diff --git a/src/libs/Navigation/AppNavigator/RHPScreenOptions.ts b/src/libs/Navigation/AppNavigator/RHPScreenOptions.ts index 5e04bf4d0905..6b56bb00cf56 100644 --- a/src/libs/Navigation/AppNavigator/RHPScreenOptions.ts +++ b/src/libs/Navigation/AppNavigator/RHPScreenOptions.ts @@ -1,16 +1,16 @@ import {CardStyleInterpolators, StackNavigationOptions} from '@react-navigation/stack'; +import styles from '@styles/styles'; /** * RHP stack navigator screen options generator function - * @function - * @param {Object} styles - The styles object - * @returns {Object} - The screen options object + * @param themeStyles - The styles object + * @returns The screen options object */ -const RHPScreenOptions = (styles): StackNavigationOptions => ({ +const RHPScreenOptions = (themeStyles: typeof styles): StackNavigationOptions => ({ headerShown: false, animationEnabled: true, gestureDirection: 'horizontal', - cardStyle: styles.navigationScreenCardStyle, + cardStyle: themeStyles.navigationScreenCardStyle, cardStyleInterpolator: CardStyleInterpolators.forHorizontalIOS, }); diff --git a/src/libs/Navigation/AppNavigator/getRootNavigatorScreenOptions.ts b/src/libs/Navigation/AppNavigator/getRootNavigatorScreenOptions.ts index a04260fc4b59..7f7dfdda7adc 100644 --- a/src/libs/Navigation/AppNavigator/getRootNavigatorScreenOptions.ts +++ b/src/libs/Navigation/AppNavigator/getRootNavigatorScreenOptions.ts @@ -1,5 +1,6 @@ import {StackCardInterpolationProps, StackNavigationOptions} from '@react-navigation/stack'; import getNavigationModalCardStyle from '@styles/getNavigationModalCardStyles'; +import styles from '@styles/styles'; import variables from '@styles/variables'; import CONFIG from '@src/CONFIG'; import modalCardStyleInterpolator from './modalCardStyleInterpolator'; @@ -12,7 +13,7 @@ const commonScreenOptions: StackNavigationOptions = { animationTypeForReplace: 'push', }; -export default (isSmallScreenWidth: boolean, styles) => ({ +export default (isSmallScreenWidth: boolean, themeStyles: typeof styles) => ({ rightModalNavigator: { ...commonScreenOptions, cardStyleInterpolator: (props: StackCardInterpolationProps) => modalCardStyleInterpolator(isSmallScreenWidth, false, props), @@ -41,7 +42,7 @@ export default (isSmallScreenWidth: boolean, styles) => ({ // We need to translate the sidebar to not be covered by the StackNavigator so it can be clickable. transform: [{translateX: isSmallScreenWidth ? 0 : -variables.sideBarWidth}], - ...(isSmallScreenWidth ? {} : styles.borderRight), + ...(isSmallScreenWidth ? {} : themeStyles.borderRight), }, } as StackNavigationOptions, fullScreen: { From 7580e3ad45fc7e74ac16a886c1519b3b56e7093f Mon Sep 17 00:00:00 2001 From: Viktoryia Kliushun Date: Wed, 29 Nov 2023 18:10:23 +0100 Subject: [PATCH 47/92] Fix lint issue --- src/libs/Navigation/AppNavigator/types.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/Navigation/AppNavigator/types.ts b/src/libs/Navigation/AppNavigator/types.ts index fa5ac3702597..8b8423e9bada 100644 --- a/src/libs/Navigation/AppNavigator/types.ts +++ b/src/libs/Navigation/AppNavigator/types.ts @@ -1,9 +1,9 @@ import {DefaultNavigatorOptions, DefaultRouterOptions, NavigatorScreenParams, ParamListBase, StackNavigationState} from '@react-navigation/native'; import {StackNavigationEventMap, StackNavigationOptions, StackScreenProps} from '@react-navigation/stack'; import {ValueOf} from 'type-fest'; -import CONST from '../../../CONST'; -import NAVIGATORS from '../../../NAVIGATORS'; -import SCREENS from '../../../SCREENS'; +import CONST from '@src/CONST'; +import NAVIGATORS from '@src/NAVIGATORS'; +import SCREENS from '@src/SCREENS'; type AccountValidationParams = { /** AccountID associated with the validation link */ From 8cb67ac73a3c7e537a09f0c83dd18bfd0bdd4dfb Mon Sep 17 00:00:00 2001 From: Viktoryia Kliushun Date: Wed, 29 Nov 2023 18:15:48 +0100 Subject: [PATCH 48/92] Run prettier --- src/libs/Navigation/AppNavigator/modalCardStyleInterpolator.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/Navigation/AppNavigator/modalCardStyleInterpolator.ts b/src/libs/Navigation/AppNavigator/modalCardStyleInterpolator.ts index 711905104acf..f7e772148e79 100644 --- a/src/libs/Navigation/AppNavigator/modalCardStyleInterpolator.ts +++ b/src/libs/Navigation/AppNavigator/modalCardStyleInterpolator.ts @@ -1,5 +1,5 @@ -import {Animated} from 'react-native'; import type {StackCardInterpolatedStyle, StackCardInterpolationProps} from '@react-navigation/stack'; +import {Animated} from 'react-native'; import getCardStyles from '@styles/cardStyles'; import variables from '@styles/variables'; From 8d989192aa18c66ad0122851d26fdad412155681 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Thu, 30 Nov 2023 05:40:14 -0300 Subject: [PATCH 49/92] Minor improvements --- src/libs/Notification/PushNotification/index.native.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/libs/Notification/PushNotification/index.native.ts b/src/libs/Notification/PushNotification/index.native.ts index 537989116021..853edfb1b10c 100644 --- a/src/libs/Notification/PushNotification/index.native.ts +++ b/src/libs/Notification/PushNotification/index.native.ts @@ -9,13 +9,15 @@ import PushNotificationType, {ClearNotifications, Deregister, Init, OnReceived, type NotificationEventActionCallback = (data: NotificationData) => void; +type NotificationEventActionMap = Partial>>; + let isUserOptedInToPushNotifications = false; Onyx.connect({ key: ONYXKEYS.PUSH_NOTIFICATIONS_ENABLED, - callback: (val) => (isUserOptedInToPushNotifications = val ?? false), + callback: (value) => (isUserOptedInToPushNotifications = value ?? false), }); -const notificationEventActionMap: Partial>> = {}; +const notificationEventActionMap: NotificationEventActionMap = {}; /** * Handle a push notification event, and trigger and bound actions. From 0dea62f2f17eb22e61f4fc2cf392006dd8b76a44 Mon Sep 17 00:00:00 2001 From: Nam Le <39086067+namhihi237@users.noreply.github.com> Date: Thu, 30 Nov 2023 22:01:01 +0700 Subject: [PATCH 50/92] update comment Co-authored-by: Vit Horacek <36083550+mountiny@users.noreply.github.com> --- src/libs/ReportActionsUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index c6367e4bc273..59807a983597 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -658,7 +658,7 @@ function isNotifiableReportAction(reportAction: OnyxEntry): boolea function getMemberChangeMessageElements(reportAction: OnyxEntry): readonly MemberChangeMessageElement[] { const isInviteAction = isInviteMemberAction(reportAction); - // currently, in the app we only have the logic show the message when invite the members + // Currently, we only render messages when members are invited const verb = isInviteAction ? Localize.translateLocal('workspace.invite.invited') : Localize.translateLocal('workspace.invite.removed'); const originalMessage = reportAction?.originalMessage as ChangeLog; From f4194320a5b3c54859540781d3e8ba1b20999b34 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Thu, 30 Nov 2023 19:05:08 +0100 Subject: [PATCH 51/92] fix: naming --- src/components/ColorSchemeWrapper/index.tsx | 4 +- .../EmojiPicker/EmojiPickerMenuItem/index.js | 8 +-- .../EmojiPickerMenuItem/index.native.js | 8 +-- src/components/FloatingActionButton.js | 4 +- src/components/HeaderGap/index.desktop.js | 2 +- src/components/Icon/index.tsx | 4 +- .../NewDatePicker/CalendarPicker/index.js | 43 +++++++-------- src/components/OptionsListSkeletonView.js | 4 +- .../OptionsSelector/BaseOptionsSelector.js | 54 +++++++------------ src/components/PDFView/index.js | 4 +- src/components/PDFView/index.native.js | 14 ++--- src/components/QRShare/index.js | 8 +-- src/components/withThemeStyles.tsx | 6 +-- .../ReimbursementAccountPage.js | 2 +- src/pages/SearchPage.js | 2 +- src/pages/ShareCodePage.js | 4 +- .../Contacts/ContactMethodDetailsPage.js | 14 ++--- .../workspace/WorkspaceInviteMessagePage.js | 20 +++---- .../reimburse/WorkspaceRateAndUnitPage.js | 6 +-- 19 files changed, 96 insertions(+), 115 deletions(-) diff --git a/src/components/ColorSchemeWrapper/index.tsx b/src/components/ColorSchemeWrapper/index.tsx index 577ccf9f3794..2909f1ffbe9f 100644 --- a/src/components/ColorSchemeWrapper/index.tsx +++ b/src/components/ColorSchemeWrapper/index.tsx @@ -5,9 +5,9 @@ import useThemeStyles from '@styles/useThemeStyles'; function ColorSchemeWrapper({children}: React.PropsWithChildren): React.ReactElement { const theme = useTheme(); - const themeStyles = useThemeStyles(); + const styles = useThemeStyles(); - return {children}; + return {children}; } export default ColorSchemeWrapper; diff --git a/src/components/EmojiPicker/EmojiPickerMenuItem/index.js b/src/components/EmojiPicker/EmojiPickerMenuItem/index.js index f674d3c4fa0e..f56143ac3701 100644 --- a/src/components/EmojiPicker/EmojiPickerMenuItem/index.js +++ b/src/components/EmojiPicker/EmojiPickerMenuItem/index.js @@ -98,15 +98,15 @@ class EmojiPickerMenuItem extends PureComponent { onBlur={this.props.onBlur} ref={(ref) => (this.ref = ref)} style={({pressed}) => [ - this.props.isFocused ? this.props.themeStyles.emojiItemKeyboardHighlighted : {}, - this.state.isHovered || this.props.isHighlighted ? this.props.themeStyles.emojiItemHighlighted : {}, + this.props.isFocused ? this.props.styles.emojiItemKeyboardHighlighted : {}, + this.state.isHovered || this.props.isHighlighted ? this.props.styles.emojiItemHighlighted : {}, Browser.isMobile() && StyleUtils.getButtonBackgroundColorStyle(this.props.theme, getButtonState(false, pressed)), - this.props.themeStyles.emojiItem, + this.props.styles.emojiItem, ]} accessibilityLabel={this.props.emoji} role={CONST.ACCESSIBILITY_ROLE.BUTTON} > - {this.props.emoji} + {this.props.emoji} ); } diff --git a/src/components/EmojiPicker/EmojiPickerMenuItem/index.native.js b/src/components/EmojiPicker/EmojiPickerMenuItem/index.native.js index ca24dde6b861..8b8e398bb0b9 100644 --- a/src/components/EmojiPicker/EmojiPickerMenuItem/index.native.js +++ b/src/components/EmojiPicker/EmojiPickerMenuItem/index.native.js @@ -76,14 +76,14 @@ class EmojiPickerMenuItem extends PureComponent { ref={(ref) => (this.ref = ref)} style={({pressed}) => [ StyleUtils.getButtonBackgroundColorStyle(this.props.theme, getButtonState(false, pressed)), - this.props.isHighlighted && this.props.isUsingKeyboardMovement ? this.props.themeStyles.emojiItemKeyboardHighlighted : {}, - this.props.isHighlighted && !this.props.isUsingKeyboardMovement ? this.props.themeStyles.emojiItemHighlighted : {}, - this.props.themeStyles.emojiItem, + this.props.isHighlighted && this.props.isUsingKeyboardMovement ? this.props.styles.emojiItemKeyboardHighlighted : {}, + this.props.isHighlighted && !this.props.isUsingKeyboardMovement ? this.props.styles.emojiItemHighlighted : {}, + this.props.styles.emojiItem, ]} accessibilityLabel={this.props.emoji} role={CONST.ACCESSIBILITY_ROLE.BUTTON} > - {this.props.emoji} + {this.props.emoji} ); } diff --git a/src/components/FloatingActionButton.js b/src/components/FloatingActionButton.js index c49f69c336eb..7f2212a1b965 100644 --- a/src/components/FloatingActionButton.js +++ b/src/components/FloatingActionButton.js @@ -83,7 +83,7 @@ class FloatingActionButton extends PureComponent { return ( - + { this.fabPressable = el; @@ -100,7 +100,7 @@ class FloatingActionButton extends PureComponent { this.props.onPress(e); }} onLongPress={() => {}} - style={[this.props.themeStyles.floatingActionButton, StyleUtils.getAnimatedFABStyle(rotate, backgroundColor)]} + style={[this.props.styles.floatingActionButton, StyleUtils.getAnimatedFABStyle(rotate, backgroundColor)]} > ; + return ; } HeaderGap.displayName = 'HeaderGap'; diff --git a/src/components/Icon/index.tsx b/src/components/Icon/index.tsx index 25bd0a083be0..e61f9696afed 100644 --- a/src/components/Icon/index.tsx +++ b/src/components/Icon/index.tsx @@ -62,14 +62,14 @@ class Icon extends PureComponent { render() { const width = this.props.small ? variables.iconSizeSmall : this.props.width; const height = this.props.small ? variables.iconSizeSmall : this.props.height; - const iconStyles = [StyleUtils.getWidthAndHeightStyle(width ?? 0, height), IconWrapperStyles, this.props.themeStyles.pAbsolute, this.props.additionalStyles]; + const iconStyles = [StyleUtils.getWidthAndHeightStyle(width ?? 0, height), IconWrapperStyles, this.props.styles.pAbsolute, this.props.additionalStyles]; const fill = this.props.fill ?? this.props.theme.icon; if (this.props.inline) { return ( this.setState({isYearPickerVisible: true})} - style={[this.props.themeStyles.alignItemsCenter, this.props.themeStyles.flexRow, this.props.themeStyles.flex1, this.props.themeStyles.justifyContentStart]} - wrapperStyle={[this.props.themeStyles.alignItemsCenter]} + style={[this.props.styles.alignItemsCenter, this.props.styles.flexRow, this.props.styles.flex1, this.props.styles.justifyContentStart]} + wrapperStyle={[this.props.styles.alignItemsCenter]} hoverDimmingValue={1} testID="currentYearButton" accessibilityLabel={this.props.translate('common.currentYear')} > @@ -162,9 +162,9 @@ class CalendarPicker extends React.PureComponent { - + @@ -195,26 +195,21 @@ class CalendarPicker extends React.PureComponent { - + {_.map(daysOfWeek, (dayOfWeek) => ( - {dayOfWeek[0]} + {dayOfWeek[0]} ))} {_.map(calendarDaysMatrix, (week) => ( {_.map(week, (day, index) => { const currentDate = new Date(currentYearView, currentMonthView, day); @@ -227,7 +222,7 @@ class CalendarPicker extends React.PureComponent { key={`${index}_day-${day}`} disabled={isDisabled} onPress={() => this.onDayPressed(day)} - style={this.props.themeStyles.calendarDayRoot} + style={this.props.styles.calendarDayRoot} accessibilityLabel={day ? day.toString() : undefined} tabIndex={day ? 0 : -1} accessible={Boolean(day)} @@ -236,12 +231,12 @@ class CalendarPicker extends React.PureComponent { {({hovered, pressed}) => ( - {day} + {day} )} diff --git a/src/components/OptionsListSkeletonView.js b/src/components/OptionsListSkeletonView.js index 2c46ac5d4d7a..365402c1be51 100644 --- a/src/components/OptionsListSkeletonView.js +++ b/src/components/OptionsListSkeletonView.js @@ -65,7 +65,7 @@ class OptionsListSkeletonView extends React.Component { height={CONST.LHN_SKELETON_VIEW_ITEM_HEIGHT} backgroundColor={this.props.theme.skeletonLHNIn} foregroundColor={this.props.theme.skeletonLHNOut} - style={this.props.themeStyles.mr5} + style={this.props.styles.mr5} > { const numItems = Math.ceil(event.nativeEvent.layout.height / CONST.LHN_SKELETON_VIEW_ITEM_HEIGHT); this.generateSkeletonViewItems(numItems); diff --git a/src/components/OptionsSelector/BaseOptionsSelector.js b/src/components/OptionsSelector/BaseOptionsSelector.js index a151dca1f211..38dd93905a61 100755 --- a/src/components/OptionsSelector/BaseOptionsSelector.js +++ b/src/components/OptionsSelector/BaseOptionsSelector.js @@ -462,8 +462,8 @@ class BaseOptionsSelector extends Component { const defaultConfirmButtonText = _.isUndefined(this.props.confirmButtonText) ? this.props.translate('common.confirm') : this.props.confirmButtonText; const shouldShowDefaultConfirmButton = !this.props.footerContent && defaultConfirmButtonText; const safeAreaPaddingBottomStyle = shouldShowFooter ? undefined : this.props.safeAreaPaddingBottomStyle; - const listContainerStyles = this.props.listContainerStyles || [this.props.themeStyles.flex1]; - const optionHoveredStyle = this.props.optionHoveredStyle || this.props.themeStyles.hoveredComponentBG; + const listContainerStyles = this.props.listContainerStyles || [this.props.styles.flex1]; + const optionHoveredStyle = this.props.optionHoveredStyle || this.props.styles.hoveredComponentBG; const textInput = ( shouldShowShowMoreButton && ( - + {optionsList} - + {this.props.children} {this.props.shouldShowTextInput && textInput} @@ -577,18 +563,18 @@ class BaseOptionsSelector extends Component { onFocusedIndexChanged={this.props.disableArrowKeysActions ? () => {} : this.updateFocusedIndex} shouldResetIndexOnEndReached={false} > - + {/* * The OptionsList component uses a SectionList which uses a VirtualizedList internally. * VirtualizedList cannot be directly nested within ScrollViews of the same orientation. * To work around this, we wrap the OptionsList component with a horizontal ScrollView. */} {this.props.shouldTextInputAppearBelowOptions && this.props.shouldAllowScrollingChildren && ( - + {optionsAndInputsBelowThem} @@ -599,13 +585,13 @@ class BaseOptionsSelector extends Component { {!this.props.shouldTextInputAppearBelowOptions && ( <> - + {this.props.children} {this.props.shouldShowTextInput && textInput} {Boolean(this.props.textInputAlert) && ( )} @@ -615,19 +601,19 @@ class BaseOptionsSelector extends Component { )} {this.props.shouldShowReferralCTA && ( - + { Navigation.navigate(ROUTES.REFERRAL_DETAILS_MODAL.getRoute(this.props.referralContentType)); }} style={[ - this.props.themeStyles.p5, - this.props.themeStyles.w100, - this.props.themeStyles.br2, - this.props.themeStyles.highlightBG, - this.props.themeStyles.flexRow, - this.props.themeStyles.justifyContentBetween, - this.props.themeStyles.alignItemsCenter, + this.props.styles.p5, + this.props.styles.w100, + this.props.styles.br2, + this.props.styles.highlightBG, + this.props.styles.flexRow, + this.props.styles.justifyContentBetween, + this.props.styles.alignItemsCenter, {gap: 10}, ]} accessibilityLabel="referral" @@ -637,7 +623,7 @@ class BaseOptionsSelector extends Component { {this.props.translate(`referralProgram.${this.props.referralContentType}.buttonText1`)} {this.props.translate(`referralProgram.${this.props.referralContentType}.buttonText2`)} @@ -656,7 +642,7 @@ class BaseOptionsSelector extends Component { {shouldShowDefaultConfirmButton && (