Skip to content

Commit

Permalink
Merge pull request #35296 from dukenv0307/fix/25231
Browse files Browse the repository at this point in the history
[TS migration] Migrate 'Expensify.js' file to TypeScript
  • Loading branch information
lakchote authored Mar 1, 2024
2 parents 48657b4 + 8a6a30f commit c8d92b7
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 107 deletions.
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';
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

0 comments on commit c8d92b7

Please sign in to comment.