Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 29 additions & 6 deletions src/libs/Navigation/AppNavigator/AuthScreens.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type {OnyxEntry} from 'react-native-onyx';
import Onyx, {useOnyx, withOnyx} from 'react-native-onyx';
import ActiveWorkspaceContextProvider from '@components/ActiveWorkspaceProvider';
import ComposeProviders from '@components/ComposeProviders';
import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator';
import OptionsListContextProvider from '@components/OptionListContextProvider';
import {SearchContextProvider} from '@components/Search/SearchContext';
import {useSearchRouterContext} from '@components/Search/SearchRouter/SearchRouterContext';
Expand All @@ -14,6 +15,7 @@ import useOnboardingFlowRouter from '@hooks/useOnboardingFlow';
import {ReportIDsContextProvider} from '@hooks/useReportIDs';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useTheme from '@hooks/useTheme';
import {connect} from '@libs/actions/Delegate';
import setFullscreenVisibility from '@libs/actions/setFullscreenVisibility';
import {READ_COMMANDS} from '@libs/API/types';
import HttpUtils from '@libs/HttpUtils';
Expand All @@ -31,6 +33,7 @@ import onyxSubscribe from '@libs/onyxSubscribe';
import Pusher from '@libs/Pusher';
import PusherConnectionManager from '@libs/PusherConnectionManager';
import * as SessionUtils from '@libs/SessionUtils';
import {getSearchParamFromUrl} from '@libs/Url';
import ConnectionCompletePage from '@pages/ConnectionCompletePage';
import NotFoundPage from '@pages/ErrorPage/NotFoundPage';
import RequireTwoFactorAuthenticationPage from '@pages/RequireTwoFactorAuthenticationPage';
Expand Down Expand Up @@ -216,13 +219,18 @@ function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDApplie
const rootNavigatorScreenOptions = useRootNavigatorScreenOptions();
const currentUserPersonalDetails = useCurrentUserPersonalDetails();
const {toggleSearch} = useSearchRouterContext();
const currentUrl = getCurrentUrl();
const delegatorEmail = getSearchParamFromUrl(currentUrl, 'delegatorEmail');

const [account] = useOnyx(ONYXKEYS.ACCOUNT);
const modal = useRef<OnyxTypes.Modal>({});
const {isOnboardingCompleted} = useOnboardingFlowRouter();
const [shouldShowRequire2FAPage, setShouldShowRequire2FAPage] = useState(!!account?.needsTwoFactorAuthSetup && !account.requiresTwoFactorAuth);
const navigation = useNavigation();

// State to track whether the delegator's authentication is completed before displaying data
const [isDelegatorFromOldDotIsReady, setIsDelegatorFromOldDotIsReady] = useState(false);

useEffect(() => {
const unsubscribe = navigation.addListener('state', () => {
PriorityMode.autoSwitchToFocusMode();
Expand Down Expand Up @@ -261,7 +269,6 @@ function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDApplie
const shortcutsOverviewShortcutConfig = CONST.KEYBOARD_SHORTCUTS.SHORTCUTS;
const searchShortcutConfig = CONST.KEYBOARD_SHORTCUTS.SEARCH;
const chatShortcutConfig = CONST.KEYBOARD_SHORTCUTS.NEW_CHAT;
const currentUrl = getCurrentUrl();
const isLoggingInAsNewUser = !!session?.email && SessionUtils.isLoggingInAsNewUser(currentUrl, session.email);
// Sign out the current user if we're transitioning with a different user
const isTransitioning = currentUrl.includes(ROUTES.TRANSITION_BETWEEN_APPS);
Expand All @@ -279,17 +286,25 @@ function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDApplie
// In Hybrid App we decide to call one of those method when booting ND and we don't want to duplicate calls
if (!CONFIG.IS_HYBRID_APP) {
// If we are on this screen then we are "logged in", but the user might not have "just logged in". They could be reopening the app
// or returning from background. If so, we'll assume they have some app data already and we can call reconnectApp() instead of openApp().
if (SessionUtils.didUserLogInDuringSession()) {
App.openApp();
// or returning from background. If so, we'll assume they have some app data already and we can call reconnectApp() instead of openApp() and connect() for delegator from OldDot.
if (SessionUtils.didUserLogInDuringSession() || delegatorEmail) {
if (delegatorEmail) {
connect(delegatorEmail, true)
?.then((success) => {
App.setAppLoading(!!success);
})
.finally(() => {
setIsDelegatorFromOldDotIsReady(true);
});
} else {
App.openApp();
}
} else {
Log.info('[AuthScreens] Sending ReconnectApp');
App.reconnectApp(initialLastUpdateIDAppliedToClient);
}
}

PriorityMode.autoSwitchToFocusMode();

App.setUpPoliciesAndNavigate(session);

App.redirectThirdPartyDesktopSignIn();
Expand All @@ -300,6 +315,10 @@ function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDApplie
}
Download.clearDownloads();

Navigation.isNavigationReady().then(() => {
PriorityMode.autoSwitchToFocusMode();
});

const unsubscribeOnyxModal = onyxSubscribe({
key: ONYXKEYS.MODAL,
callback: (modalArg) => {
Expand Down Expand Up @@ -454,6 +473,10 @@ function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDApplie
clearStatus();
}, [currentUserPersonalDetails.status?.clearAfter]);

if (delegatorEmail && !isDelegatorFromOldDotIsReady) {
return <FullScreenLoadingIndicator />;
}

return (
<ComposeProviders components={[OptionsListContextProvider, ActiveWorkspaceContextProvider, ReportIDsContextProvider, SearchContextProvider]}>
<RootStack.Navigator persistentScreens={[NAVIGATORS.REPORTS_SPLIT_NAVIGATOR, SCREENS.SEARCH.ROOT]}>
Expand Down
1 change: 1 addition & 0 deletions src/libs/Navigation/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1756,6 +1756,7 @@ type SharedScreensParamList = {
exitTo?: Routes | HybridAppRoute;
shouldForceLogin: string;
domain?: Routes;
delegatorEmail?: string;
};
[SCREENS.VALIDATE_LOGIN]: {
accountID: string;
Expand Down
6 changes: 5 additions & 1 deletion src/libs/Url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,8 @@ function extractUrlDomain(url: string): string | undefined {
return match?.[1];
}

export {addTrailingForwardSlash, hasSameExpensifyOrigin, getPathFromURL, appendParam, hasURL, addLeadingForwardSlash, extractUrlDomain};
function getSearchParamFromUrl(currentUrl: string, param: string) {
return currentUrl ? new URL(currentUrl).searchParams.get(param) : null;
}

export {getSearchParamFromUrl, addTrailingForwardSlash, hasSameExpensifyOrigin, getPathFromURL, appendParam, hasURL, addLeadingForwardSlash, extractUrlDomain};
5 changes: 5 additions & 0 deletions src/libs/actions/App.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,10 @@ function setSidebarLoaded() {
Timing.end(CONST.TIMING.SIDEBAR_LOADED);
}

function setAppLoading(isLoading: boolean) {
Onyx.set(ONYXKEYS.IS_LOADING_APP, isLoading);
}

let appState: AppStateStatus;
AppState.addEventListener('change', (nextAppState) => {
if (nextAppState.match(/inactive|background/) && appState === 'active') {
Expand Down Expand Up @@ -617,6 +621,7 @@ export {
setUpPoliciesAndNavigate,
redirectThirdPartyDesktopSignIn,
openApp,
setAppLoading,
reconnectApp,
confirmReadyToOpenApp,
handleRestrictedEvent,
Expand Down
20 changes: 13 additions & 7 deletions src/libs/actions/Delegate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,12 @@ const KEYS_TO_PRESERVE_DELEGATE_ACCESS = [
ONYXKEYS.IS_SIDEBAR_LOADED,
];

function connect(email: string) {
if (!delegatedAccess?.delegators) {
/**
* Connects the user as a delegate to another account.
* Returns a Promise that resolves to true on success, false on failure, or undefined if not applicable.
*/
function connect(email: string, isFromOldDot = false) {
if (!delegatedAccess?.delegators && !isFromOldDot) {
return;
}

Expand Down Expand Up @@ -135,14 +139,14 @@ function connect(email: string) {

// We need to access the authToken directly from the response to update the session
// eslint-disable-next-line rulesdir/no-api-side-effects-method
API.makeRequestWithSideEffects(SIDE_EFFECT_REQUEST_COMMANDS.CONNECT_AS_DELEGATE, {to: email}, {optimisticData, successData, failureData})
return API.makeRequestWithSideEffects(SIDE_EFFECT_REQUEST_COMMANDS.CONNECT_AS_DELEGATE, {to: email}, {optimisticData, successData, failureData})
.then((response) => {
if (!response?.restrictedToken || !response?.encryptedAuthToken) {
Log.alert('[Delegate] No auth token returned while connecting as a delegate');
Onyx.update(failureData);
return;
}
if (!activePolicyID) {
if (!activePolicyID && CONFIG.IS_HYBRID_APP) {
Log.alert('[Delegate] Unable to access activePolicyID');
Onyx.update(failureData);
return;
Expand All @@ -158,22 +162,24 @@ function connect(email: string) {

NetworkStore.setAuthToken(response?.restrictedToken ?? null);
confirmReadyToOpenApp();
openApp().then(() => {
if (!CONFIG.IS_HYBRID_APP) {
return;
return openApp().then(() => {
if (!CONFIG.IS_HYBRID_APP || !policyID) {
return true;
}
HybridAppModule.switchAccount({
newDotCurrentAccountEmail: email,
authToken: restrictedToken,
policyID,
accountID: String(previousAccountID),
});
return true;
});
});
})
.catch((error) => {
Log.alert('[Delegate] Error connecting as delegate', {error});
Onyx.update(failureData);
return false;
});
}

Expand Down
107 changes: 52 additions & 55 deletions src/pages/settings/ExitSurvey/ExitSurveyBookCall.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import React from 'react';
import {View} from 'react-native';
import Button from '@components/Button';
import DelegateNoAccessWrapper from '@components/DelegateNoAccessWrapper';
import FixedFooter from '@components/FixedFooter';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import ScreenWrapper from '@components/ScreenWrapper';
Expand All @@ -10,7 +9,7 @@ import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
import useThemeStyles from '@hooks/useThemeStyles';
import Navigation from '@navigation/Navigation';
import * as Link from '@userActions/Link';
import {openExternalLink} from '@userActions/Link';
import * as Expensicons from '@src/components/Icon/Expensicons';
import CONST from '@src/CONST';
import ROUTES from '@src/ROUTES';
Expand All @@ -23,60 +22,58 @@ function ExitSurveyBookCallPage() {

return (
<ScreenWrapper testID={ExitSurveyBookCallPage.displayName}>
<DelegateNoAccessWrapper accessDeniedVariants={[CONST.DELEGATE.DENIED_ACCESS_VARIANTS.DELEGATE]}>
<HeaderWithBackButton
title={translate('exitSurvey.header')}
onBackButtonPress={() => Navigation.goBack()}
<HeaderWithBackButton
title={translate('exitSurvey.header')}
onBackButtonPress={() => Navigation.goBack()}
/>
<View style={[styles.flex1, styles.mh5]}>
{isOffline && <ExitSurveyOffline />}
{!isOffline && (
<>
<Text style={[styles.headerAnonymousFooter, styles.mt3]}>{translate('exitSurvey.bookACallTitle')}</Text>
<Text style={[styles.mt2]}>{translate('exitSurvey.bookACallTextTop')}</Text>
<View style={styles.mv4}>
{Object.values(CONST.EXIT_SURVEY.BENEFIT).map((value) => {
return (
<View
key={value}
style={[styles.flexRow]}
>
<View style={styles.liDot} />
<Text>{translate(`exitSurvey.benefits.${value}`)}</Text>
</View>
);
})}
</View>
<Text>{translate('exitSurvey.bookACallTextBottom')}</Text>
</>
)}
</View>
<FixedFooter>
<Button
style={styles.mb3}
large
text={translate('exitSurvey.noThanks')}
onPress={() => {
Navigation.navigate(ROUTES.SETTINGS_EXIT_SURVEY_REASON.getRoute(ROUTES.SETTINGS_EXIT_SURVERY_BOOK_CALL.route));
}}
isDisabled={isOffline}
/>
<View style={[styles.flex1, styles.mh5]}>
{isOffline && <ExitSurveyOffline />}
{!isOffline && (
<>
<Text style={[styles.headerAnonymousFooter, styles.mt3]}>{translate('exitSurvey.bookACallTitle')}</Text>
<Text style={[styles.mt2]}>{translate('exitSurvey.bookACallTextTop')}</Text>
<View style={styles.mv4}>
{Object.values(CONST.EXIT_SURVEY.BENEFIT).map((value) => {
return (
<View
key={value}
style={[styles.flexRow]}
>
<View style={styles.liDot} />
<Text>{translate(`exitSurvey.benefits.${value}`)}</Text>
</View>
);
})}
</View>
<Text>{translate('exitSurvey.bookACallTextBottom')}</Text>
</>
)}
</View>
<FixedFooter>
<Button
style={styles.mb3}
large
text={translate('exitSurvey.noThanks')}
onPress={() => {
Navigation.navigate(ROUTES.SETTINGS_EXIT_SURVEY_REASON.getRoute(ROUTES.SETTINGS_EXIT_SURVERY_BOOK_CALL.route));
}}
isDisabled={isOffline}
/>
<Button
success
large
iconRight={Expensicons.NewWindow}
shouldShowRightIcon
isContentCentered
text={translate('exitSurvey.bookACall')}
pressOnEnter
onPress={() => {
Navigation.dismissModal();
Link.openExternalLink(CONST.EXIT_SURVEY.BOOK_MEETING_LINK);
}}
isDisabled={isOffline}
/>
</FixedFooter>
</DelegateNoAccessWrapper>
<Button
success
large
iconRight={Expensicons.NewWindow}
shouldShowRightIcon
isContentCentered
text={translate('exitSurvey.bookACall')}
pressOnEnter
onPress={() => {
Navigation.dismissModal();
openExternalLink(CONST.EXIT_SURVEY.BOOK_MEETING_LINK);
}}
isDisabled={isOffline}
/>
</FixedFooter>
</ScreenWrapper>
);
}
Expand Down
Loading