diff --git a/src/App.tsx b/src/App.tsx index cbe5948f8d4e..0e247d5faa53 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -83,7 +83,6 @@ function App({url}: AppProps) { - {/* @ts-expect-error TODO: Remove this once Expensify (https://github.com/Expensify/App/issues/25231) is migrated to TypeScript. */} diff --git a/src/Expensify.js b/src/Expensify.tsx similarity index 64% rename from src/Expensify.js rename to src/Expensify.tsx index dfb59a0f8848..f822862ec434 100644 --- a/src/Expensify.js +++ b/src/Expensify.tsx @@ -1,9 +1,8 @@ -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; import React, {useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState} from 'react'; +import type {NativeEventSubscription} from 'react-native'; import {AppState, Linking} from 'react-native'; +import type {OnyxEntry} from 'react-native-onyx'; import Onyx, {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; import ConfirmModal from './components/ConfirmModal'; import DeeplinkWrapper from './components/DeeplinkWrapper'; import EmojiPicker from './components/EmojiPicker/EmojiPicker'; @@ -12,14 +11,13 @@ import GrowlNotification from './components/GrowlNotification'; import AppleAuthWrapper from './components/SignInButtons/AppleAuthWrapper'; import SplashScreenHider from './components/SplashScreenHider'; import UpdateAppModal from './components/UpdateAppModal'; -import withLocalize, {withLocalizePropTypes} from './components/withLocalize'; import CONST from './CONST'; +import useLocalize from './hooks/useLocalize'; import * as EmojiPickerAction from './libs/actions/EmojiPickerAction'; import * as Report from './libs/actions/Report'; import * as User from './libs/actions/User'; import * as ActiveClientManager from './libs/ActiveClientManager'; import BootSplash from './libs/BootSplash'; -import compose from './libs/compose'; import * as Growl from './libs/Growl'; import Log from './libs/Log'; import migrateOnyx from './libs/migrateOnyx'; @@ -27,16 +25,18 @@ import Navigation from './libs/Navigation/Navigation'; import NavigationRoot from './libs/Navigation/NavigationRoot'; import NetworkConnection from './libs/NetworkConnection'; import PushNotification from './libs/Notification/PushNotification'; -// eslint-disable-next-line no-unused-vars -import subscribePushNotification from './libs/Notification/PushNotification/subscribePushNotification'; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import './libs/Notification/PushNotification/subscribePushNotification'; import StartupTimer from './libs/StartupTimer'; // This lib needs to be imported, but it has nothing to export since all it contains is an Onyx connection -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars import UnreadIndicatorUpdater from './libs/UnreadIndicatorUpdater'; import Visibility from './libs/Visibility'; import ONYXKEYS from './ONYXKEYS'; import PopoverReportActionContextMenu from './pages/home/report/ContextMenu/PopoverReportActionContextMenu'; import * as ReportActionContextMenu from './pages/home/report/ContextMenu/ReportActionContextMenu'; +import type {Route} from './ROUTES'; +import type {ScreenShareRequest, Session} from './types/onyx'; Onyx.registerLogger(({level, message}) => { if (level === 'alert') { @@ -47,82 +47,63 @@ Onyx.registerLogger(({level, message}) => { } }); -const propTypes = { - /* Onyx Props */ +type ExpensifyOnyxProps = { + /** Whether the app is waiting for the server's response to determine if a room is public */ + isCheckingPublicRoom: OnyxEntry; /** Session info for the currently logged in user. */ - session: PropTypes.shape({ - /** Currently logged in user authToken */ - authToken: PropTypes.string, - - /** Currently logged in user accountID */ - accountID: PropTypes.number, - }), + session: OnyxEntry; /** Whether a new update is available and ready to install. */ - updateAvailable: PropTypes.bool, + updateAvailable: OnyxEntry; - /** Tells us if the sidebar has rendered - TODO: We don't use it as temporary solution to fix not hidding splashscreen */ - // eslint-disable-next-line react/no-unused-prop-types - isSidebarLoaded: PropTypes.bool, + /** Tells us if the sidebar has rendered */ + isSidebarLoaded: OnyxEntry; /** Information about a screen share call requested by a GuidesPlus agent */ - screenShareRequest: PropTypes.shape({ - /** Access token required to join a screen share room, generated by the backend */ - accessToken: PropTypes.string, - - /** Name of the screen share room to join */ - roomName: PropTypes.string, - }), - - /** Whether the app is waiting for the server's response to determine if a room is public */ - isCheckingPublicRoom: PropTypes.bool, + screenShareRequest: OnyxEntry; /** True when the user must update to the latest minimum version of the app */ - updateRequired: PropTypes.bool, + updateRequired: OnyxEntry; /** Whether we should display the notification alerting the user that focus mode has been auto-enabled */ - focusModeNotification: PropTypes.bool, + focusModeNotification: OnyxEntry; /** Last visited path in the app */ - lastVisitedPath: PropTypes.string, - - ...withLocalizePropTypes, + lastVisitedPath: OnyxEntry; }; -const defaultProps = { - session: { - authToken: null, - accountID: null, - }, - updateAvailable: false, - isSidebarLoaded: false, - screenShareRequest: null, - isCheckingPublicRoom: true, - updateRequired: false, - focusModeNotification: false, - lastVisitedPath: undefined, -}; +type ExpensifyProps = ExpensifyOnyxProps; const SplashScreenHiddenContext = React.createContext({}); -function Expensify(props) { - const appStateChangeListener = useRef(null); +function Expensify({ + isCheckingPublicRoom = true, + session, + updateAvailable, + isSidebarLoaded = false, + screenShareRequest, + updateRequired = false, + focusModeNotification = false, + lastVisitedPath, +}: ExpensifyProps) { + const appStateChangeListener = useRef(null); const [isNavigationReady, setIsNavigationReady] = useState(false); const [isOnyxMigrated, setIsOnyxMigrated] = useState(false); const [isSplashHidden, setIsSplashHidden] = useState(false); const [hasAttemptedToOpenPublicRoom, setAttemptedToOpenPublicRoom] = useState(false); - const [initialUrl, setInitialUrl] = useState(null); + const {translate} = useLocalize(); + const [initialUrl, setInitialUrl] = useState(null); useEffect(() => { - if (props.isCheckingPublicRoom) { + if (isCheckingPublicRoom) { return; } setAttemptedToOpenPublicRoom(true); - }, [props.isCheckingPublicRoom]); + }, [isCheckingPublicRoom]); - const isAuthenticated = useMemo(() => Boolean(lodashGet(props.session, 'authToken', null)), [props.session]); - const autoAuthState = useMemo(() => lodashGet(props.session, 'autoAuthState', ''), [props.session]); + const isAuthenticated = useMemo(() => !!(session?.authToken ?? null), [session]); + const autoAuthState = useMemo(() => session?.autoAuthState ?? '', [session]); const contextValue = useMemo( () => ({ @@ -168,8 +149,16 @@ function Expensify(props) { Log.info('[BootSplash] splash screen status', false, {appState, status}); if (status === 'visible') { - const propsToLog = _.omit(props, ['children', 'session']); - propsToLog.isAuthenticated = isAuthenticated; + const propsToLog: Omit = { + isCheckingPublicRoom, + updateRequired, + updateAvailable, + isSidebarLoaded, + screenShareRequest, + focusModeNotification, + isAuthenticated, + lastVisitedPath, + }; Log.alert('[BootSplash] splash screen is still visible', {propsToLog}, false); } }); @@ -194,7 +183,7 @@ function Expensify(props) { // If the app is opened from a deep link, get the reportID (if exists) from the deep link and navigate to the chat report Linking.getInitialURL().then((url) => { setInitialUrl(url); - Report.openReportFromDeepLink(url, isAuthenticated); + Report.openReportFromDeepLink(url ?? '', isAuthenticated); }); // Open chat report from a deep link (only mobile native) @@ -216,7 +205,7 @@ function Expensify(props) { return null; } - if (props.updateRequired) { + if (updateRequired) { throw new Error(CONST.ERROR.UPDATE_REQUIRED); } @@ -231,20 +220,19 @@ function Expensify(props) { {/* We include the modal for showing a new update at the top level so the option is always present. */} - {/* If the update is required we won't show this option since a full screen update view will be displayed instead. */} - {props.updateAvailable && !props.updateRequired ? : null} - {props.screenShareRequest ? ( + {updateAvailable && !updateRequired ? : null} + {screenShareRequest ? ( User.joinScreenShare(props.screenShareRequest.accessToken, props.screenShareRequest.roomName)} + title={translate('guides.screenShare')} + onConfirm={() => User.joinScreenShare(screenShareRequest.accessToken, screenShareRequest.roomName)} onCancel={User.clearScreenShareRequest} - prompt={props.translate('guides.screenShareRequest')} - confirmText={props.translate('common.join')} - cancelText={props.translate('common.decline')} + prompt={translate('guides.screenShareRequest')} + confirmText={translate('common.join')} + cancelText={translate('common.decline')} isVisible /> ) : null} - {props.focusModeNotification ? : null} + {focusModeNotification ? : null} )} @@ -254,7 +242,7 @@ function Expensify(props) { @@ -265,40 +253,35 @@ function Expensify(props) { ); } -Expensify.propTypes = propTypes; -Expensify.defaultProps = defaultProps; -export default compose( - withLocalize, - withOnyx({ - isCheckingPublicRoom: { - key: ONYXKEYS.IS_CHECKING_PUBLIC_ROOM, - initWithStoredValues: false, - }, - session: { - key: ONYXKEYS.SESSION, - }, - updateAvailable: { - key: ONYXKEYS.UPDATE_AVAILABLE, - initWithStoredValues: false, - }, - isSidebarLoaded: { - key: ONYXKEYS.IS_SIDEBAR_LOADED, - }, - screenShareRequest: { - key: ONYXKEYS.SCREEN_SHARE_REQUEST, - }, - updateRequired: { - key: ONYXKEYS.UPDATE_REQUIRED, - initWithStoredValues: false, - }, - focusModeNotification: { - key: ONYXKEYS.FOCUS_MODE_NOTIFICATION, - initWithStoredValues: false, - }, - lastVisitedPath: { - key: ONYXKEYS.LAST_VISITED_PATH, - }, - }), -)(Expensify); +export default withOnyx({ + isCheckingPublicRoom: { + key: ONYXKEYS.IS_CHECKING_PUBLIC_ROOM, + initWithStoredValues: false, + }, + session: { + key: ONYXKEYS.SESSION, + }, + updateAvailable: { + key: ONYXKEYS.UPDATE_AVAILABLE, + initWithStoredValues: false, + }, + updateRequired: { + key: ONYXKEYS.UPDATE_REQUIRED, + initWithStoredValues: false, + }, + isSidebarLoaded: { + key: ONYXKEYS.IS_SIDEBAR_LOADED, + }, + screenShareRequest: { + key: ONYXKEYS.SCREEN_SHARE_REQUEST, + }, + focusModeNotification: { + key: ONYXKEYS.FOCUS_MODE_NOTIFICATION, + initWithStoredValues: false, + }, + lastVisitedPath: { + key: ONYXKEYS.LAST_VISITED_PATH, + }, +})(Expensify); export {SplashScreenHiddenContext}; diff --git a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx index 0b4154a15e80..47cbc559a67b 100644 --- a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx +++ b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx @@ -31,7 +31,7 @@ function extractPointerEvent(event: GestureResponderEvent | MouseEvent): MouseEv } // eslint-disable-next-line @typescript-eslint/naming-convention -function PopoverReportActionContextMenu(_props: never, ref: ForwardedRef) { +function PopoverReportActionContextMenu(_props: unknown, ref: ForwardedRef) { const {translate} = useLocalize(); const reportIDRef = useRef('0'); const typeRef = useRef();