Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TS migration] Migrate 'Expensify.js' file to TypeScript #35296

Merged
merged 18 commits into from
Mar 1, 2024
Merged
1 change: 0 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,6 @@ function App({url}: AppProps) {
<CustomStatusBarAndBackground />
<ErrorBoundary errorMessage="NewExpensify crash caught by error boundary">
<ColorSchemeWrapper>
{/* @ts-expect-error TODO: Remove this once Expensify (https://github.com/Expensify/App/issues/25231) is migrated to TypeScript. */}
<Expensify />
</ColorSchemeWrapper>
</ErrorBoundary>
Expand Down
193 changes: 88 additions & 105 deletions src/Expensify.js → src/Expensify.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -12,31 +11,32 @@ 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';
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';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, this line caused unexpected regression. This was not a problem before migrating to TS.
More details: #38256 (comment)

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') {
Expand All @@ -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<boolean>;

/** 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<Session>;

/** Whether a new update is available and ready to install. */
updateAvailable: PropTypes.bool,
updateAvailable: OnyxEntry<boolean>;

/** 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<boolean>;

/** 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<ScreenShareRequest>;

/** True when the user must update to the latest minimum version of the app */
updateRequired: PropTypes.bool,
updateRequired: OnyxEntry<boolean>;

/** Whether we should display the notification alerting the user that focus mode has been auto-enabled */
focusModeNotification: PropTypes.bool,
focusModeNotification: OnyxEntry<boolean>;

/** Last visited path in the app */
lastVisitedPath: PropTypes.string,

...withLocalizePropTypes,
lastVisitedPath: OnyxEntry<string | undefined>;
};

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<NativeEventSubscription | null>(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<string | null>(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(
() => ({
Expand Down Expand Up @@ -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<ExpensifyProps & {isAuthenticated: boolean}, 'children' | 'session'> = {
isCheckingPublicRoom,
updateRequired,
updateAvailable,
isSidebarLoaded,
screenShareRequest,
focusModeNotification,
isAuthenticated,
lastVisitedPath,
};
Log.alert('[BootSplash] splash screen is still visible', {propsToLog}, false);
}
});
Expand All @@ -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)
Expand All @@ -216,7 +205,7 @@ function Expensify(props) {
return null;
}

if (props.updateRequired) {
if (updateRequired) {
throw new Error(CONST.ERROR.UPDATE_REQUIRED);
}

Expand All @@ -231,20 +220,19 @@ function Expensify(props) {
<PopoverReportActionContextMenu ref={ReportActionContextMenu.contextMenuRef} />
<EmojiPicker ref={EmojiPickerAction.emojiPickerRef} />
{/* 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 ? <UpdateAppModal /> : null}
{props.screenShareRequest ? (
{updateAvailable && !updateRequired ? <UpdateAppModal /> : null}
{screenShareRequest ? (
<ConfirmModal
title={props.translate('guides.screenShare')}
onConfirm={() => 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 ? <FocusModeNotification /> : null}
{focusModeNotification ? <FocusModeNotification /> : null}
</>
)}

Expand All @@ -254,7 +242,7 @@ function Expensify(props) {
<NavigationRoot
onReady={setNavigationReady}
authenticated={isAuthenticated}
lastVisitedPath={props.lastVisitedPath}
lastVisitedPath={lastVisitedPath as Route}
initialUrl={initialUrl}
/>
</SplashScreenHiddenContext.Provider>
Expand All @@ -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<ExpensifyProps, ExpensifyOnyxProps>({
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};
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ function extractPointerEvent(event: GestureResponderEvent | MouseEvent): MouseEv
}

// eslint-disable-next-line @typescript-eslint/naming-convention
function PopoverReportActionContextMenu(_props: never, ref: ForwardedRef<ReportActionContextMenu>) {
function PopoverReportActionContextMenu(_props: unknown, ref: ForwardedRef<ReportActionContextMenu>) {
const {translate} = useLocalize();
const reportIDRef = useRef('0');
const typeRef = useRef<ContextMenuType>();
Expand Down
Loading