diff --git a/src/CONST.ts b/src/CONST.ts
index 496b7d8b28ec..cd222656acf8 100755
--- a/src/CONST.ts
+++ b/src/CONST.ts
@@ -4551,6 +4551,12 @@ const CONST = {
ALL: 'all',
SUBMITTER: 'submitter',
},
+ DELEGATE: {
+ DENIED_ACCESS_VARIANTS: {
+ DELEGATE: 'delegate',
+ SUBMITTER: 'submitter',
+ },
+ },
DELEGATE_ROLE_HELPDOT_ARTICLE_LINK: 'https://help.expensify.com/expensify-classic/hubs/copilots-and-delegates/',
STRIPE_GBP_AUTH_STATUSES: {
SUCCEEDED: 'succeeded',
diff --git a/src/components/DelegateNoAccessModal.tsx b/src/components/DelegateNoAccessModal.tsx
index 442c3ec9c4e2..e1e5917ef757 100644
--- a/src/components/DelegateNoAccessModal.tsx
+++ b/src/components/DelegateNoAccessModal.tsx
@@ -1,4 +1,5 @@
import React from 'react';
+import useDelegateUserDetails from '@hooks/useDelegateUserDetails';
import useLocalize from '@hooks/useLocalize';
import CONST from '@src/CONST';
import ConfirmModal from './ConfirmModal';
@@ -8,14 +9,13 @@ import TextLink from './TextLink';
type DelegateNoAccessModalProps = {
isNoDelegateAccessMenuVisible: boolean;
onClose: () => void;
- delegatorEmail: string;
};
-export default function DelegateNoAccessModal({isNoDelegateAccessMenuVisible = false, onClose, delegatorEmail = ''}: DelegateNoAccessModalProps) {
+export default function DelegateNoAccessModal({isNoDelegateAccessMenuVisible = false, onClose}: DelegateNoAccessModalProps) {
const {translate} = useLocalize();
- const noDelegateAccessPromptStart = translate('delegate.notAllowedMessageStart', {accountOwnerEmail: delegatorEmail});
+ const {delegatorEmail} = useDelegateUserDetails();
+ const noDelegateAccessPromptStart = translate('delegate.notAllowedMessageStart', {accountOwnerEmail: delegatorEmail ?? ''});
const noDelegateAccessHyperLinked = translate('delegate.notAllowedMessageHyperLinked');
-
const delegateNoAccessPrompt = (
{noDelegateAccessPromptStart}
diff --git a/src/components/DelegateNoAccessWrapper.tsx b/src/components/DelegateNoAccessWrapper.tsx
new file mode 100644
index 000000000000..c49d15890112
--- /dev/null
+++ b/src/components/DelegateNoAccessWrapper.tsx
@@ -0,0 +1,67 @@
+import React from 'react';
+import {useOnyx} from 'react-native-onyx';
+import type {OnyxEntry} from 'react-native-onyx';
+import useResponsiveLayout from '@hooks/useResponsiveLayout';
+import AccountUtils from '@libs/AccountUtils';
+import Navigation from '@libs/Navigation/Navigation';
+import CONST from '@src/CONST';
+import ONYXKEYS from '@src/ONYXKEYS';
+import type {Account} from '@src/types/onyx';
+import callOrReturn from '@src/types/utils/callOrReturn';
+import FullPageNotFoundView from './BlockingViews/FullPageNotFoundView';
+
+const DENIED_ACCESS_VARIANTS = {
+ // To Restrict All Delegates From Accessing The Page.
+ [CONST.DELEGATE.DENIED_ACCESS_VARIANTS.DELEGATE]: (account: OnyxEntry) => isDelegate(account),
+ // To Restrict Only Limited Access Delegates From Accessing The Page.
+ [CONST.DELEGATE.DENIED_ACCESS_VARIANTS.SUBMITTER]: (account: OnyxEntry) => isSubmitter(account),
+} as const satisfies Record) => boolean>;
+
+type AccessDeniedVariants = keyof typeof DENIED_ACCESS_VARIANTS;
+
+type DelegateNoAccessWrapperProps = {
+ accessDeniedVariants?: AccessDeniedVariants[];
+ shouldForceFullScreen?: boolean;
+ children?: (() => React.ReactNode) | React.ReactNode;
+};
+
+function isDelegate(account: OnyxEntry) {
+ const isActingAsDelegate = !!account?.delegatedAccess?.delegate;
+ return isActingAsDelegate;
+}
+
+function isSubmitter(account: OnyxEntry) {
+ const isDelegateOnlySubmitter = AccountUtils.isDelegateOnlySubmitter(account);
+ return isDelegateOnlySubmitter;
+}
+
+function DelegateNoAccessWrapper({accessDeniedVariants = [], shouldForceFullScreen, ...props}: DelegateNoAccessWrapperProps) {
+ const [account] = useOnyx(ONYXKEYS.ACCOUNT);
+ const isPageAccessDenied = accessDeniedVariants.reduce((acc, variant) => {
+ const accessDeniedFunction = DENIED_ACCESS_VARIANTS[variant];
+ return acc || accessDeniedFunction(account);
+ }, false);
+ const {shouldUseNarrowLayout} = useResponsiveLayout();
+
+ if (isPageAccessDenied) {
+ return (
+ {
+ if (shouldUseNarrowLayout) {
+ Navigation.dismissModal();
+ return;
+ }
+ Navigation.goBack();
+ }}
+ titleKey="delegate.notAllowed"
+ subtitleKey="delegate.noAccessMessage"
+ shouldShowLink={false}
+ />
+ );
+ }
+ return callOrReturn(props.children);
+}
+
+export default DelegateNoAccessWrapper;
diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx
index d01b69ed5649..a438bae497f7 100644
--- a/src/components/MoneyReportHeader.tsx
+++ b/src/components/MoneyReportHeader.tsx
@@ -174,7 +174,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
const isAnyTransactionOnHold = ReportUtils.hasHeldExpenses(moneyRequestReport?.reportID);
const displayedAmount = isAnyTransactionOnHold && canAllowSettlement && hasValidNonHeldAmount ? nonHeldAmount : formattedAmount;
const isMoreContentShown = shouldShowNextStep || shouldShowStatusBar || (shouldShowAnyButton && shouldUseNarrowLayout);
- const {isDelegateAccessRestricted, delegatorEmail} = useDelegateUserDetails();
+ const {isDelegateAccessRestricted} = useDelegateUserDetails();
const [isNoDelegateAccessMenuVisible, setIsNoDelegateAccessMenuVisible] = useState(false);
const isReportInRHP = route.name === SCREENS.SEARCH.REPORT_RHP;
@@ -494,7 +494,6 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
setIsNoDelegateAccessMenuVisible(false)}
- delegatorEmail={delegatorEmail ?? ''}
/>
setIsPaidAnimationRunning(false), []);
@@ -592,7 +592,6 @@ function ReportPreview({
setIsNoDelegateAccessMenuVisible(false)}
- delegatorEmail={delegatorEmail ?? ''}
/>
{isHoldMenuVisible && !!iouReport && requestType !== undefined && (
diff --git a/src/languages/en.ts b/src/languages/en.ts
index 3e1ea7f0d7cc..3b4c3cae6ef5 100755
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -5307,6 +5307,7 @@ const translations = {
enterMagicCode: ({contactMethod}: EnterMagicCodeParams) => `Please enter the magic code sent to ${contactMethod} to add a copilot. It should arrive within a minute or two.`,
enterMagicCodeUpdate: ({contactMethod}: EnterMagicCodeParams) => `Please enter the magic code sent to ${contactMethod} to update your copilot.`,
notAllowed: 'Not so fast...',
+ noAccessMessage: "As a copilot, you don't have access to \nthis page. Sorry!",
notAllowedMessageStart: ({accountOwnerEmail}: AccountOwnerParams) => `You don't have permission to take this action for ${accountOwnerEmail} as a`,
notAllowedMessageHyperLinked: ' copilot',
},
diff --git a/src/languages/es.ts b/src/languages/es.ts
index d28a19fbb1be..e2fd20ef0172 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -5826,6 +5826,7 @@ const translations = {
enterMagicCodeUpdate: ({contactMethod}: EnterMagicCodeParams) =>
`Por favor, introduce el código mágico enviado a ${contactMethod} para actualizar el nivel de acceso de tu copiloto.`,
notAllowed: 'No tan rápido...',
+ noAccessMessage: 'Como copiloto, no tienes acceso a esta página. ¡Lo sentimos!',
notAllowedMessageStart: ({accountOwnerEmail}: AccountOwnerParams) => `No tienes permiso para realizar esta acción para ${accountOwnerEmail}`,
notAllowedMessageHyperLinked: ' copiloto',
},
diff --git a/src/pages/AddPersonalBankAccountPage.tsx b/src/pages/AddPersonalBankAccountPage.tsx
index 7b0a76e59922..d99db40d07ee 100644
--- a/src/pages/AddPersonalBankAccountPage.tsx
+++ b/src/pages/AddPersonalBankAccountPage.tsx
@@ -3,6 +3,7 @@ import {useOnyx} from 'react-native-onyx';
import AddPlaidBankAccount from '@components/AddPlaidBankAccount';
import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView';
import ConfirmationPage from '@components/ConfirmationPage';
+import DelegateNoAccessWrapper from '@components/DelegateNoAccessWrapper';
import FormProvider from '@components/Form/FormProvider';
import InputWrapper from '@components/Form/InputWrapper';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
@@ -13,6 +14,7 @@ import getPlaidOAuthReceivedRedirectURI from '@libs/getPlaidOAuthReceivedRedirec
import Navigation from '@libs/Navigation/Navigation';
import * as BankAccounts from '@userActions/BankAccounts';
import * as PaymentMethods from '@userActions/PaymentMethods';
+import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import SCREENS from '@src/SCREENS';
@@ -77,41 +79,43 @@ function AddPersonalBankAccountPage() {
testID={AddPersonalBankAccountPage.displayName}
>
-
- {shouldShowSuccess ? (
- exitFlow(true)}
+
+
- ) : (
- 0}
- submitButtonText={translate('common.saveAndContinue')}
- scrollContextEnabled
- onSubmit={submitBankAccountForm}
- validate={BankAccounts.validatePlaidSelection}
- style={[styles.mh5, styles.flex1]}
- >
- exitFlow(true)}
/>
-
- )}
+ ) : (
+ 0}
+ submitButtonText={translate('common.saveAndContinue')}
+ scrollContextEnabled
+ onSubmit={submitBankAccountForm}
+ validate={BankAccounts.validatePlaidSelection}
+ style={[styles.mh5, styles.flex1]}
+ >
+
+
+ )}
+
);
diff --git a/src/pages/AddressPage.tsx b/src/pages/AddressPage.tsx
index 88e52409751b..31cffbe9b511 100644
--- a/src/pages/AddressPage.tsx
+++ b/src/pages/AddressPage.tsx
@@ -1,6 +1,7 @@
import React, {useCallback, useEffect, useState} from 'react';
import type {OnyxEntry} from 'react-native-onyx';
import AddressForm from '@components/AddressForm';
+import DelegateNoAccessWrapper from '@components/DelegateNoAccessWrapper';
import FullscreenLoadingIndicator from '@components/FullscreenLoadingIndicator';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import ScreenWrapper from '@components/ScreenWrapper';
@@ -10,6 +11,7 @@ import Navigation from '@libs/Navigation/Navigation';
import type {BackToParams} from '@libs/Navigation/types';
import type {FormOnyxValues} from '@src/components/Form/types';
import type {Country} from '@src/CONST';
+import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import INPUT_IDS from '@src/types/form/HomeAddressForm';
import type {Address} from '@src/types/onyx/PrivatePersonalDetails';
@@ -81,27 +83,29 @@ function AddressPage({title, address, updateAddress, isLoadingApp = true, backTo
includeSafeAreaPaddingBottom={false}
testID={AddressPage.displayName}
>
- Navigation.goBack(backTo)}
- />
- {isLoadingApp ? (
-
- ) : (
-
+ Navigation.goBack(backTo)}
/>
- )}
+ {isLoadingApp ? (
+
+ ) : (
+
+ )}
+
);
}
diff --git a/src/pages/EnablePayments/EnablePayments.tsx b/src/pages/EnablePayments/EnablePayments.tsx
index 357a2be9b1e0..b55141fec299 100644
--- a/src/pages/EnablePayments/EnablePayments.tsx
+++ b/src/pages/EnablePayments/EnablePayments.tsx
@@ -1,5 +1,6 @@
import React, {useEffect} from 'react';
import {useOnyx} from 'react-native-onyx';
+import DelegateNoAccessWrapper from '@components/DelegateNoAccessWrapper';
import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import ScreenWrapper from '@components/ScreenWrapper';
@@ -22,6 +23,7 @@ function EnablePaymentsPage() {
const {isOffline} = useNetwork();
const [userWallet] = useOnyx(ONYXKEYS.USER_WALLET);
const [bankAccountList] = useOnyx(ONYXKEYS.BANK_ACCOUNT_LIST);
+ const [isActingAsDelegate] = useOnyx(ONYXKEYS.ACCOUNT, {selector: (account) => !!account?.delegatedAccess?.delegate});
useEffect(() => {
if (isOffline) {
@@ -33,6 +35,18 @@ function EnablePaymentsPage() {
}
}, [isOffline, userWallet]);
+ if (isActingAsDelegate) {
+ return (
+
+
+
+ );
+ }
+
if (isEmptyObject(userWallet)) {
return ;
}
@@ -54,7 +68,6 @@ function EnablePaymentsPage() {
}
const currentStep = isEmptyObject(bankAccountList) ? CONST.WALLET.STEP.ADD_BANK_ACCOUNT : userWallet?.currentStep || CONST.WALLET.STEP.ADDITIONAL_DETAILS;
-
switch (currentStep) {
case CONST.WALLET.STEP.ADD_BANK_ACCOUNT:
return ;
diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx
index 8bf3da6e33e0..5934afc09cd9 100644
--- a/src/pages/ReportDetailsPage.tsx
+++ b/src/pages/ReportDetailsPage.tsx
@@ -256,7 +256,7 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta
const [moneyRequestReportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${moneyRequestReport?.reportID}`);
const isMoneyRequestExported = ReportUtils.isExported(moneyRequestReportActions);
- const {isDelegateAccessRestricted, delegatorEmail} = useDelegateUserDetails();
+ const {isDelegateAccessRestricted} = useDelegateUserDetails();
const [isNoDelegateAccessMenuVisible, setIsNoDelegateAccessMenuVisible] = useState(false);
const unapproveExpenseReportOrShowModal = useCallback(() => {
@@ -942,7 +942,6 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta
setIsNoDelegateAccessMenuVisible(false)}
- delegatorEmail={delegatorEmail ?? ''}
/>
setIsNoDelegateAccessMenuVisible(false)}
- delegatorEmail={delegatorEmail ?? ''}
/>
>
);
diff --git a/src/pages/settings/ExitSurvey/ExitSurveyBookCall.tsx b/src/pages/settings/ExitSurvey/ExitSurveyBookCall.tsx
index 93191b809ee8..10085dd7a569 100644
--- a/src/pages/settings/ExitSurvey/ExitSurveyBookCall.tsx
+++ b/src/pages/settings/ExitSurvey/ExitSurveyBookCall.tsx
@@ -1,6 +1,7 @@
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';
@@ -22,58 +23,60 @@ function ExitSurveyBookCallPage() {
return (
- Navigation.goBack()}
- />
-
- {isOffline && }
- {!isOffline && (
- <>
- {translate('exitSurvey.bookACallTitle')}
- {translate('exitSurvey.bookACallTextTop')}
-
- {Object.values(CONST.EXIT_SURVEY.BENEFIT).map((value) => {
- return (
-
-
- {translate(`exitSurvey.benefits.${value}`)}
-
- );
- })}
-
- {translate('exitSurvey.bookACallTextBottom')}
- >
- )}
-
-
-
+
+ {isOffline && }
+ {!isOffline && (
+ <>
+ {translate('exitSurvey.bookACallTitle')}
+ {translate('exitSurvey.bookACallTextTop')}
+
+ {Object.values(CONST.EXIT_SURVEY.BENEFIT).map((value) => {
+ return (
+
+
+ {translate(`exitSurvey.benefits.${value}`)}
+
+ );
+ })}
+
+ {translate('exitSurvey.bookACallTextBottom')}
+ >
+ )}
+
+
+ {
+ Navigation.navigate(ROUTES.SETTINGS_EXIT_SURVEY_REASON.getRoute(ROUTES.SETTINGS_EXIT_SURVERY_BOOK_CALL.route));
+ }}
+ isDisabled={isOffline}
+ />
+ {
+ Navigation.dismissModal();
+ Link.openExternalLink(CONST.EXIT_SURVEY.BOOK_MEETING_LINK);
+ }}
+ isDisabled={isOffline}
+ />
+
+
);
}
diff --git a/src/pages/settings/ExitSurvey/ExitSurveyConfirmPage.tsx b/src/pages/settings/ExitSurvey/ExitSurveyConfirmPage.tsx
index 905937ba8dc4..da4b06d7819d 100644
--- a/src/pages/settings/ExitSurvey/ExitSurveyConfirmPage.tsx
+++ b/src/pages/settings/ExitSurvey/ExitSurveyConfirmPage.tsx
@@ -5,6 +5,7 @@ import {useOnyx} from 'react-native-onyx';
import type {OnyxEntry} from 'react-native-onyx';
import Icon from '@components//Icon';
import Button from '@components/Button';
+import DelegateNoAccessWrapper from '@components/DelegateNoAccessWrapper';
import FixedFooter from '@components/FixedFooter';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import {MushroomTopHat} from '@components/Icon/Illustrations';
@@ -60,40 +61,42 @@ function ExitSurveyConfirmPage({route, navigation}: ExitSurveyConfirmPageProps)
return (
- Navigation.goBack()}
- />
-
- {isOffline && }
- {!isOffline && (
- <>
-
-
- {translate(shouldShowQuickTips ? 'exitSurvey.quickTip' : 'exitSurvey.thankYou')}
-
- {translate(shouldShowQuickTips ? 'exitSurvey.quickTipSubTitle' : 'exitSurvey.thankYouSubtitle')}
- >
- )}
-
-
- {
- ExitSurvey.switchToOldDot();
- Navigation.dismissModal();
- Link.openOldDotLink(CONST.OLDDOT_URLS.INBOX, true);
- }}
- isDisabled={isOffline}
+
+ Navigation.goBack()}
/>
-
+
+ {isOffline && }
+ {!isOffline && (
+ <>
+
+
+ {translate(shouldShowQuickTips ? 'exitSurvey.quickTip' : 'exitSurvey.thankYou')}
+
+ {translate(shouldShowQuickTips ? 'exitSurvey.quickTipSubTitle' : 'exitSurvey.thankYouSubtitle')}
+ >
+ )}
+
+
+ {
+ ExitSurvey.switchToOldDot();
+ Navigation.dismissModal();
+ Link.openOldDotLink(CONST.OLDDOT_URLS.INBOX, true);
+ }}
+ isDisabled={isOffline}
+ />
+
+
);
}
diff --git a/src/pages/settings/ExitSurvey/ExitSurveyReasonPage.tsx b/src/pages/settings/ExitSurvey/ExitSurveyReasonPage.tsx
index b5bdcf9ac45c..4e34fc47a6ed 100644
--- a/src/pages/settings/ExitSurvey/ExitSurveyReasonPage.tsx
+++ b/src/pages/settings/ExitSurvey/ExitSurveyReasonPage.tsx
@@ -1,6 +1,7 @@
import React, {useEffect, useMemo, useState} from 'react';
import type {OnyxEntry} from 'react-native-onyx';
import {useOnyx} from 'react-native-onyx';
+import DelegateNoAccessWrapper from '@components/DelegateNoAccessWrapper';
import FormProvider from '@components/Form/FormProvider';
import InputWrapper from '@components/Form/InputWrapper';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
@@ -48,47 +49,49 @@ function ExitSurveyReasonPage() {
return (
- Navigation.goBack()}
- />
- {
- const errors: Errors = {};
- if (!reason) {
- errors[INPUT_IDS.REASON] = translate('common.error.fieldRequired');
- }
- return errors;
- }}
- onSubmit={() => {
- if (!reason) {
- return;
- }
- ExitSurvey.saveExitReason(reason);
- Navigation.navigate(ROUTES.SETTINGS_EXIT_SURVEY_RESPONSE.getRoute(reason, ROUTES.SETTINGS_EXIT_SURVEY_REASON.route));
- }}
- submitButtonText={translate('common.next')}
- shouldValidateOnBlur
- shouldValidateOnChange
- >
- {isOffline && }
- {!isOffline && (
- <>
- {translate('exitSurvey.reasonPage.title')}
- {translate('exitSurvey.reasonPage.subtitle')}
- void}
- shouldSaveDraft
- />
- >
- )}
-
+
+ Navigation.goBack()}
+ />
+ {
+ const errors: Errors = {};
+ if (!reason) {
+ errors[INPUT_IDS.REASON] = translate('common.error.fieldRequired');
+ }
+ return errors;
+ }}
+ onSubmit={() => {
+ if (!reason) {
+ return;
+ }
+ ExitSurvey.saveExitReason(reason);
+ Navigation.navigate(ROUTES.SETTINGS_EXIT_SURVEY_RESPONSE.getRoute(reason, ROUTES.SETTINGS_EXIT_SURVEY_REASON.route));
+ }}
+ submitButtonText={translate('common.next')}
+ shouldValidateOnBlur
+ shouldValidateOnChange
+ >
+ {isOffline && }
+ {!isOffline && (
+ <>
+ {translate('exitSurvey.reasonPage.title')}
+ {translate('exitSurvey.reasonPage.subtitle')}
+ void}
+ shouldSaveDraft
+ />
+ >
+ )}
+
+
);
}
diff --git a/src/pages/settings/ExitSurvey/ExitSurveyResponsePage.tsx b/src/pages/settings/ExitSurvey/ExitSurveyResponsePage.tsx
index d33828b722eb..2ea73a1cc6db 100644
--- a/src/pages/settings/ExitSurvey/ExitSurveyResponsePage.tsx
+++ b/src/pages/settings/ExitSurvey/ExitSurveyResponsePage.tsx
@@ -1,6 +1,7 @@
import type {StackScreenProps} from '@react-navigation/stack';
import React, {useCallback, useEffect} from 'react';
import {useOnyx} from 'react-native-onyx';
+import DelegateNoAccessWrapper from '@components/DelegateNoAccessWrapper';
import FormProvider from '@components/Form/FormProvider';
import InputWrapper from '@components/Form/InputWrapper';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
@@ -86,46 +87,48 @@ function ExitSurveyResponsePage({route, navigation}: ExitSurveyResponsePageProps
testID={ExitSurveyResponsePage.displayName}
shouldEnableMaxHeight
>
- Navigation.goBack()}
- />
- {
- const errors: Errors = {};
- if (!draftResponse?.trim()) {
- errors[INPUT_IDS.RESPONSE] = translate('common.error.fieldRequired');
- }
- return errors;
- }}
- shouldValidateOnBlur
- shouldValidateOnChange
- >
- {isOffline && }
- {!isOffline && (
- <>
- {translate(`exitSurvey.prompts.${reason}`)}
-
- >
- )}
-
+
+ Navigation.goBack()}
+ />
+ {
+ const errors: Errors = {};
+ if (!draftResponse?.trim()) {
+ errors[INPUT_IDS.RESPONSE] = translate('common.error.fieldRequired');
+ }
+ return errors;
+ }}
+ shouldValidateOnBlur
+ shouldValidateOnChange
+ >
+ {isOffline && }
+ {!isOffline && (
+ <>
+ {translate(`exitSurvey.prompts.${reason}`)}
+
+ >
+ )}
+
+
);
}
diff --git a/src/pages/settings/InitialSettingsPage.tsx b/src/pages/settings/InitialSettingsPage.tsx
index 0b5344ba662a..3eba9246c91c 100755
--- a/src/pages/settings/InitialSettingsPage.tsx
+++ b/src/pages/settings/InitialSettingsPage.tsx
@@ -8,6 +8,7 @@ import type {ValueOf} from 'type-fest';
import AccountSwitcher from '@components/AccountSwitcher';
import AccountSwitcherSkeletonView from '@components/AccountSwitcherSkeletonView';
import ConfirmModal from '@components/ConfirmModal';
+import DelegateNoAccessModal from '@components/DelegateNoAccessModal';
import Icon from '@components/Icon';
import * as Expensicons from '@components/Icon/Expensicons';
import {InitialURLContext} from '@components/InitialURLContextProvider';
@@ -84,6 +85,10 @@ function InitialSettingsPage({currentUserPersonalDetails}: InitialSettingsPagePr
const [privatePersonalDetails] = useOnyx(ONYXKEYS.PRIVATE_PERSONAL_DETAILS);
const [tryNewDot] = useOnyx(ONYXKEYS.NVP_TRYNEWDOT);
+ const [isActingAsDelegate] = useOnyx(ONYXKEYS.ACCOUNT, {selector: (account) => !!account?.delegatedAccess?.delegate});
+
+ const [isNoDelegateAccessMenuVisible, setIsNoDelegateAccessMenuVisible] = useState(false);
+
const network = useNetwork();
const theme = useTheme();
const styles = useThemeStyles();
@@ -244,6 +249,10 @@ function InitialSettingsPage({currentUserPersonalDetails}: InitialSettingsPagePr
}
: {
action() {
+ if (isActingAsDelegate) {
+ setIsNoDelegateAccessMenuVisible(true);
+ return;
+ }
resetExitSurveyForm(() => {
if (shouldOpenBookACall) {
Navigation.navigate(ROUTES.SETTINGS_EXIT_SURVERY_BOOK_CALL.route);
@@ -278,7 +287,7 @@ function InitialSettingsPage({currentUserPersonalDetails}: InitialSettingsPagePr
},
],
};
- }, [styles.pt4, signOut, setInitialURL, shouldOpenBookACall]);
+ }, [styles.pt4, signOut, setInitialURL, shouldOpenBookACall, isActingAsDelegate]);
/**
* Retuns JSX.Element with menu items
@@ -443,6 +452,10 @@ function InitialSettingsPage({currentUserPersonalDetails}: InitialSettingsPagePr
onCancel={() => toggleSignoutConfirmModal(false)}
/>
+ setIsNoDelegateAccessMenuVisible(false)}
+ />
);
}
diff --git a/src/pages/settings/Profile/Contacts/ContactMethodsPage.tsx b/src/pages/settings/Profile/Contacts/ContactMethodsPage.tsx
index 92a246949c53..16e98513a13b 100644
--- a/src/pages/settings/Profile/Contacts/ContactMethodsPage.tsx
+++ b/src/pages/settings/Profile/Contacts/ContactMethodsPage.tsx
@@ -13,7 +13,6 @@ import OfflineWithFeedback from '@components/OfflineWithFeedback';
import ScreenWrapper from '@components/ScreenWrapper';
import ScrollView from '@components/ScrollView';
import Text from '@components/Text';
-import useDelegateUserDetails from '@hooks/useDelegateUserDetails';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import Navigation from '@libs/Navigation/Navigation';
@@ -33,10 +32,9 @@ function ContactMethodsPage({route}: ContactMethodsPageProps) {
const [session] = useOnyx(ONYXKEYS.SESSION);
const loginNames = Object.keys(loginList ?? {});
const navigateBackTo = route?.params?.backTo;
- const [account] = useOnyx(ONYXKEYS.ACCOUNT);
- const isActingAsDelegate = !!account?.delegatedAccess?.delegate;
+
+ const [isActingAsDelegate] = useOnyx(ONYXKEYS.ACCOUNT, {selector: (account) => !!account?.delegatedAccess?.delegate});
const [isNoDelegateAccessMenuVisible, setIsNoDelegateAccessMenuVisible] = useState(false);
- const {delegatorEmail} = useDelegateUserDetails();
// Sort the login names by placing the one corresponding to the default contact method as the first item before displaying the contact methods.
// The default contact method is determined by checking against the session email (the current login).
@@ -132,7 +130,6 @@ function ContactMethodsPage({route}: ContactMethodsPageProps) {
setIsNoDelegateAccessMenuVisible(false)}
- delegatorEmail={delegatorEmail ?? ''}
/>
);
diff --git a/src/pages/settings/Profile/Contacts/NewContactMethodPage.tsx b/src/pages/settings/Profile/Contacts/NewContactMethodPage.tsx
index 8f69663fb936..ef0348273db6 100644
--- a/src/pages/settings/Profile/Contacts/NewContactMethodPage.tsx
+++ b/src/pages/settings/Profile/Contacts/NewContactMethodPage.tsx
@@ -3,6 +3,7 @@ import {Str} from 'expensify-common';
import React, {useCallback, useEffect, useRef, useState} from 'react';
import {View} from 'react-native';
import {useOnyx} from 'react-native-onyx';
+import DelegateNoAccessWrapper from '@components/DelegateNoAccessWrapper';
import DotIndicatorMessage from '@components/DotIndicatorMessage';
import FormProvider from '@components/Form/FormProvider';
import InputWrapper from '@components/Form/InputWrapper';
@@ -21,7 +22,6 @@ import Navigation from '@libs/Navigation/Navigation';
import type {SettingsNavigatorParamList} from '@libs/Navigation/types';
import {addSMSDomainIfPhoneNumber} from '@libs/PhoneNumber';
import * as UserUtils from '@libs/UserUtils';
-import NotFoundPage from '@pages/ErrorPage/NotFoundPage';
import * as User from '@userActions/User';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
@@ -42,8 +42,6 @@ function NewContactMethodPage({route}: NewContactMethodPageProps) {
const [loginList] = useOnyx(ONYXKEYS.LOGIN_LIST);
const loginData = loginList?.[pendingContactAction?.contactMethod ?? contactMethod];
const validateLoginError = ErrorUtils.getLatestErrorField(loginData, 'addedLogin');
- const [account] = useOnyx(ONYXKEYS.ACCOUNT);
- const isActingAsDelegate = !!account?.delegatedAccess?.delegate;
const navigateBackTo = route?.params?.backTo ?? ROUTES.SETTINGS_PROFILE;
@@ -109,10 +107,6 @@ function NewContactMethodPage({route}: NewContactMethodPageProps) {
Navigation.goBack(ROUTES.SETTINGS_CONTACT_METHODS.getRoute(navigateBackTo));
}, [navigateBackTo]);
- if (isActingAsDelegate) {
- return ;
- }
-
return (
loginInputRef.current?.focus()}
@@ -120,62 +114,64 @@ function NewContactMethodPage({route}: NewContactMethodPageProps) {
shouldEnableMaxHeight
testID={NewContactMethodPage.displayName}
>
-
-
- {translate('common.pleaseEnterEmailOrPhoneNumber')}
-
-
-
- {hasFailedToSendVerificationCode && (
-
- )}
-
- {
- if (!loginData) {
- return;
- }
- User.clearContactMethodErrors(addSMSDomainIfPhoneNumber(pendingContactAction?.contactMethod ?? contactMethod), 'addedLogin');
- }}
- onClose={() => {
- if (loginData?.errorFields && pendingContactAction?.contactMethod) {
- User.clearContactMethod(pendingContactAction?.contactMethod);
- User.clearUnvalidatedNewContactMethodAction();
- }
- setIsValidateCodeActionModalVisible(false);
- }}
- isVisible={isValidateCodeActionModalVisible}
- hasMagicCodeBeenSent={!!loginData?.validateCodeSent}
- title={translate('delegate.makeSureItIsYou')}
- sendValidateCode={() => User.requestValidateCodeAction()}
- descriptionPrimary={translate('contacts.enterMagicCode', {contactMethod})}
- />
+
+
+
+ {translate('common.pleaseEnterEmailOrPhoneNumber')}
+
+
+
+ {hasFailedToSendVerificationCode && (
+
+ )}
+
+ {
+ if (!loginData) {
+ return;
+ }
+ User.clearContactMethodErrors(addSMSDomainIfPhoneNumber(pendingContactAction?.contactMethod ?? contactMethod), 'addedLogin');
+ }}
+ onClose={() => {
+ if (loginData?.errorFields && pendingContactAction?.contactMethod) {
+ User.clearContactMethod(pendingContactAction?.contactMethod);
+ User.clearUnvalidatedNewContactMethodAction();
+ }
+ setIsValidateCodeActionModalVisible(false);
+ }}
+ isVisible={isValidateCodeActionModalVisible}
+ hasMagicCodeBeenSent={!!loginData?.validateCodeSent}
+ title={translate('delegate.makeSureItIsYou')}
+ sendValidateCode={() => User.requestValidateCodeAction()}
+ descriptionPrimary={translate('contacts.enterMagicCode', {contactMethod})}
+ />
+
);
}
diff --git a/src/pages/settings/Profile/PersonalDetails/DateOfBirthPage.tsx b/src/pages/settings/Profile/PersonalDetails/DateOfBirthPage.tsx
index 07d65c710429..7e462ca60153 100644
--- a/src/pages/settings/Profile/PersonalDetails/DateOfBirthPage.tsx
+++ b/src/pages/settings/Profile/PersonalDetails/DateOfBirthPage.tsx
@@ -2,6 +2,7 @@ import {subYears} from 'date-fns';
import React, {useCallback} from 'react';
import {useOnyx} from 'react-native-onyx';
import DatePicker from '@components/DatePicker';
+import DelegateNoAccessWrapper from '@components/DelegateNoAccessWrapper';
import FormProvider from '@components/Form/FormProvider';
import InputWrapper from '@components/Form/InputWrapper';
import type {FormOnyxValues} from '@components/Form/types';
@@ -22,7 +23,6 @@ function DateOfBirthPage() {
const [isLoadingApp] = useOnyx(ONYXKEYS.IS_LOADING_APP, {initialValue: true});
const {translate} = useLocalize();
const styles = useThemeStyles();
-
/**
* @returns An object containing the errors for each inputID
*/
@@ -46,31 +46,33 @@ function DateOfBirthPage() {
includeSafeAreaPaddingBottom={false}
testID={DateOfBirthPage.displayName}
>
- Navigation.goBack()}
- />
- {isLoadingApp ? (
-
- ) : (
-
-
-
- )}
+
+ Navigation.goBack()}
+ />
+ {isLoadingApp ? (
+
+ ) : (
+
+
+
+ )}
+
);
}
diff --git a/src/pages/settings/Profile/PersonalDetails/LegalNamePage.tsx b/src/pages/settings/Profile/PersonalDetails/LegalNamePage.tsx
index 505ee55a1ec5..0873451eba53 100644
--- a/src/pages/settings/Profile/PersonalDetails/LegalNamePage.tsx
+++ b/src/pages/settings/Profile/PersonalDetails/LegalNamePage.tsx
@@ -1,6 +1,7 @@
import React, {useCallback} from 'react';
import {View} from 'react-native';
import {useOnyx} from 'react-native-onyx';
+import DelegateNoAccessWrapper from '@components/DelegateNoAccessWrapper';
import FormProvider from '@components/Form/FormProvider';
import InputWrapper from '@components/Form/InputWrapper';
import type {FormOnyxValues} from '@components/Form/types';
@@ -82,47 +83,49 @@ function LegalNamePage() {
shouldEnableMaxHeight
testID={LegalNamePage.displayName}
>
- Navigation.goBack()}
- />
- {isLoadingApp ? (
-
- ) : (
-
-
-
-
-
-
-
-
- )}
+
+ Navigation.goBack()}
+ />
+ {isLoadingApp ? (
+
+ ) : (
+
+
+
+
+
+
+
+
+ )}
+
);
}
diff --git a/src/pages/settings/Profile/PersonalDetails/PhoneNumberPage.tsx b/src/pages/settings/Profile/PersonalDetails/PhoneNumberPage.tsx
index 12c6f011c6a2..add5eaf3dd9f 100644
--- a/src/pages/settings/Profile/PersonalDetails/PhoneNumberPage.tsx
+++ b/src/pages/settings/Profile/PersonalDetails/PhoneNumberPage.tsx
@@ -1,6 +1,7 @@
import {Str} from 'expensify-common';
import React, {useCallback} from 'react';
import {useOnyx} from 'react-native-onyx';
+import DelegateNoAccessWrapper from '@components/DelegateNoAccessWrapper';
import FormProvider from '@components/Form/FormProvider';
import InputWrapper from '@components/Form/InputWrapper';
import type {FormInputErrors, FormOnyxValues} from '@components/Form/types';
@@ -73,45 +74,47 @@ function PhoneNumberPage() {
shouldEnableMaxHeight
testID={PhoneNumberPage.displayName}
>
- Navigation.goBack()}
- />
- {isLoadingApp ? (
-
- ) : (
-
- PersonalDetails.clearPhoneNumberError()}
+
+ Navigation.goBack()}
+ />
+ {isLoadingApp ? (
+
+ ) : (
+
- {
- if (!validateLoginError) {
- return;
- }
- PersonalDetails.clearPhoneNumberError();
- }}
- />
-
-
- )}
+ PersonalDetails.clearPhoneNumberError()}
+ >
+ {
+ if (!validateLoginError) {
+ return;
+ }
+ PersonalDetails.clearPhoneNumberError();
+ }}
+ />
+
+
+ )}
+
);
}
diff --git a/src/pages/settings/Profile/ProfilePage.tsx b/src/pages/settings/Profile/ProfilePage.tsx
index faa399598d05..b33f67bae3f1 100755
--- a/src/pages/settings/Profile/ProfilePage.tsx
+++ b/src/pages/settings/Profile/ProfilePage.tsx
@@ -1,9 +1,10 @@
-import React from 'react';
+import React, {useState} from 'react';
import {View} from 'react-native';
import {useOnyx} from 'react-native-onyx';
import AvatarSkeleton from '@components/AvatarSkeleton';
import AvatarWithImagePicker from '@components/AvatarWithImagePicker';
import Button from '@components/Button';
+import DelegateNoAccessModal from '@components/DelegateNoAccessModal';
import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import * as Expensicons from '@components/Icon/Expensicons';
@@ -56,6 +57,9 @@ function ProfilePage() {
const privateDetails = privatePersonalDetails ?? {};
const legalName = `${privateDetails.legalFirstName ?? ''} ${privateDetails.legalLastName ?? ''}`.trim();
+ const [isActingAsDelegate] = useOnyx(ONYXKEYS.ACCOUNT, {selector: (account) => !!account?.delegatedAccess?.delegate});
+ const [isNoDelegateAccessMenuVisible, setIsNoDelegateAccessMenuVisible] = useState(false);
+
const publicOptions = [
{
description: translate('displayNamePage.headerTitle'),
@@ -89,23 +93,47 @@ function ProfilePage() {
{
description: translate('privatePersonalDetails.legalName'),
title: legalName,
- pageRoute: ROUTES.SETTINGS_LEGAL_NAME,
+ action: () => {
+ if (isActingAsDelegate) {
+ setIsNoDelegateAccessMenuVisible(true);
+ return;
+ }
+ Navigation.navigate(ROUTES.SETTINGS_LEGAL_NAME);
+ },
},
{
description: translate('common.dob'),
title: privateDetails.dob ?? '',
- pageRoute: ROUTES.SETTINGS_DATE_OF_BIRTH,
+ action: () => {
+ if (isActingAsDelegate) {
+ setIsNoDelegateAccessMenuVisible(true);
+ return;
+ }
+ Navigation.navigate(ROUTES.SETTINGS_DATE_OF_BIRTH);
+ },
},
{
description: translate('common.phoneNumber'),
title: privateDetails.phoneNumber ?? '',
- pageRoute: ROUTES.SETTINGS_PHONE_NUMBER,
+ action: () => {
+ if (isActingAsDelegate) {
+ setIsNoDelegateAccessMenuVisible(true);
+ return;
+ }
+ Navigation.navigate(ROUTES.SETTINGS_PHONE_NUMBER);
+ },
brickRoadIndicator: privatePersonalDetails?.errorFields?.phoneNumber ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined,
},
{
description: translate('privatePersonalDetails.address'),
title: PersonalDetailsUtils.getFormattedAddress(privateDetails),
- pageRoute: ROUTES.SETTINGS_ADDRESS,
+ action: () => {
+ if (isActingAsDelegate) {
+ setIsNoDelegateAccessMenuVisible(true);
+ return;
+ }
+ Navigation.navigate(ROUTES.SETTINGS_ADDRESS);
+ },
},
];
@@ -191,7 +219,7 @@ function ProfilePage() {
{isLoadingApp ? (
) : (
- <>
+
{privateOptions.map((detail, index) => (
Navigation.navigate(detail.pageRoute)}
+ onPress={detail.action}
brickRoadIndicator={detail.brickRoadIndicator}
/>
))}
- >
+
)}
+ setIsNoDelegateAccessMenuVisible(false)}
+ />
);
}
diff --git a/src/pages/settings/Security/AddDelegate/AddDelegatePage.tsx b/src/pages/settings/Security/AddDelegate/AddDelegatePage.tsx
index 1b5fc3ce246c..6d4ec9d1d4de 100644
--- a/src/pages/settings/Security/AddDelegate/AddDelegatePage.tsx
+++ b/src/pages/settings/Security/AddDelegate/AddDelegatePage.tsx
@@ -1,6 +1,7 @@
import React, {useCallback, useEffect, useMemo, useState} from 'react';
import {View} from 'react-native';
import {useOnyx} from 'react-native-onyx';
+import DelegateNoAccessWrapper from '@components/DelegateNoAccessWrapper';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import {useBetas} from '@components/OnyxProvider';
import {useOptionsList} from '@components/OptionListContextProvider';
@@ -128,24 +129,26 @@ function AddDelegatePage() {
includeSafeAreaPaddingBottom={false}
testID={AddDelegatePage.displayName}
>
- Navigation.goBack()}
- />
-
-
+ Navigation.goBack()}
/>
-
+
+
+
+
);
}
diff --git a/src/pages/settings/Security/AddDelegate/ConfirmDelegatePage.tsx b/src/pages/settings/Security/AddDelegate/ConfirmDelegatePage.tsx
index 8dd3996a769a..dd0a1d8f8cf1 100644
--- a/src/pages/settings/Security/AddDelegate/ConfirmDelegatePage.tsx
+++ b/src/pages/settings/Security/AddDelegate/ConfirmDelegatePage.tsx
@@ -2,6 +2,7 @@ import type {StackScreenProps} from '@react-navigation/stack';
import React, {useState} from 'react';
import type {ValueOf} from 'type-fest';
import Button from '@components/Button';
+import DelegateNoAccessWrapper from '@components/DelegateNoAccessWrapper';
import HeaderPageLayout from '@components/HeaderPageLayout';
import {FallbackAvatar} from '@components/Icon/Expensicons';
import MenuItem from '@components/MenuItem';
@@ -57,34 +58,36 @@ function ConfirmDelegatePage({route}: ConfirmDelegatePageProps) {
footer={submitButton}
childrenContainerStyles={[styles.pt3, styles.gap6]}
>
- {translate('delegate.confirmCopilot')}
-
- Navigation.navigate(ROUTES.SETTINGS_DELEGATE_ROLE.getRoute(login, role))}
- shouldShowRightIcon
- />
- {
- if (!showValidateActionModal) {
- setIsValidateCodeActionModalVisible(false);
- return;
- }
- Navigation.navigate(ROUTES.SETTINGS_SECURITY);
- }}
- isValidateCodeActionModalVisible={isValidateCodeActionModalVisible}
- />
+
+ {translate('delegate.confirmCopilot')}
+
+ Navigation.navigate(ROUTES.SETTINGS_DELEGATE_ROLE.getRoute(login, role))}
+ shouldShowRightIcon
+ />
+ {
+ if (!showValidateActionModal) {
+ setIsValidateCodeActionModalVisible(false);
+ return;
+ }
+ Navigation.navigate(ROUTES.SETTINGS_SECURITY);
+ }}
+ isValidateCodeActionModalVisible={isValidateCodeActionModalVisible}
+ />
+
);
}
diff --git a/src/pages/settings/Security/AddDelegate/SelectDelegateRolePage.tsx b/src/pages/settings/Security/AddDelegate/SelectDelegateRolePage.tsx
index 4270441775cd..877ea7929981 100644
--- a/src/pages/settings/Security/AddDelegate/SelectDelegateRolePage.tsx
+++ b/src/pages/settings/Security/AddDelegate/SelectDelegateRolePage.tsx
@@ -1,5 +1,6 @@
import type {StackScreenProps} from '@react-navigation/stack';
import React from 'react';
+import DelegateNoAccessWrapper from '@components/DelegateNoAccessWrapper';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import ScreenWrapper from '@components/ScreenWrapper';
import SelectionList from '@components/SelectionList';
@@ -34,34 +35,36 @@ function SelectDelegateRolePage({route}: SelectDelegateRolePageProps) {
includeSafeAreaPaddingBottom={false}
testID={SelectDelegateRolePage.displayName}
>
- Navigation.goBack(ROUTES.SETTINGS_ADD_DELEGATE)}
- />
- role.isSelected)?.keyForList}
- shouldUpdateFocusedIndex
- headerContent={
-
- <>
- {translate('delegate.accessLevelDescription')}{' '}
-
- {translate('common.learnMore')}
-
- >
-
- }
- onSelectRow={(option) => {
- Navigation.navigate(ROUTES.SETTINGS_DELEGATE_CONFIRM.getRoute(login, option.value));
- }}
- sections={[{data: roleOptions}]}
- ListItem={RadioListItem}
- />
+
+ Navigation.goBack(ROUTES.SETTINGS_ADD_DELEGATE)}
+ />
+ role.isSelected)?.keyForList}
+ shouldUpdateFocusedIndex
+ headerContent={
+
+ <>
+ {translate('delegate.accessLevelDescription')}{' '}
+
+ {translate('common.learnMore')}
+
+ >
+
+ }
+ onSelectRow={(option) => {
+ Navigation.navigate(ROUTES.SETTINGS_DELEGATE_CONFIRM.getRoute(login, option.value));
+ }}
+ sections={[{data: roleOptions}]}
+ ListItem={RadioListItem}
+ />
+
);
}
diff --git a/src/pages/settings/Security/AddDelegate/UpdateDelegateRole/UpdateDelegateMagicCodePage.tsx b/src/pages/settings/Security/AddDelegate/UpdateDelegateRole/UpdateDelegateMagicCodePage.tsx
index 3bc82e8d7e65..60a15354a0ed 100644
--- a/src/pages/settings/Security/AddDelegate/UpdateDelegateRole/UpdateDelegateMagicCodePage.tsx
+++ b/src/pages/settings/Security/AddDelegate/UpdateDelegateRole/UpdateDelegateMagicCodePage.tsx
@@ -2,6 +2,7 @@ import type {StackScreenProps} from '@react-navigation/stack';
import React, {useEffect, useRef} from 'react';
import {useOnyx} from 'react-native-onyx';
import type {ValueOf} from 'type-fest';
+import DelegateNoAccessWrapper from '@components/DelegateNoAccessWrapper';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import ScreenWrapper from '@components/ScreenWrapper';
import Text from '@components/Text';
@@ -9,7 +10,7 @@ import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import Navigation from '@libs/Navigation/Navigation';
import type {SettingsNavigatorParamList} from '@libs/Navigation/types';
-import type CONST from '@src/CONST';
+import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type SCREENS from '@src/SCREENS';
@@ -51,7 +52,7 @@ function UpdateDelegateMagicCodePage({route}: UpdateDelegateMagicCodePageProps)
offlineIndicatorStyle={styles.mtAuto}
>
{({safeAreaPaddingBottomStyle}) => (
- <>
+
- >
+
)}
);
diff --git a/src/pages/settings/Security/AddDelegate/UpdateDelegateRole/UpdateDelegateRolePage.tsx b/src/pages/settings/Security/AddDelegate/UpdateDelegateRole/UpdateDelegateRolePage.tsx
index 1ac362a50457..829e723c761f 100644
--- a/src/pages/settings/Security/AddDelegate/UpdateDelegateRole/UpdateDelegateRolePage.tsx
+++ b/src/pages/settings/Security/AddDelegate/UpdateDelegateRole/UpdateDelegateRolePage.tsx
@@ -1,5 +1,6 @@
import type {StackScreenProps} from '@react-navigation/stack';
import React, {useEffect, useState} from 'react';
+import DelegateNoAccessWrapper from '@components/DelegateNoAccessWrapper';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import ScreenWrapper from '@components/ScreenWrapper';
import SelectionList from '@components/SelectionList';
@@ -43,41 +44,43 @@ function UpdateDelegateRolePage({route}: UpdateDelegateRolePageProps) {
includeSafeAreaPaddingBottom={false}
testID={UpdateDelegateRolePage.displayName}
>
- Navigation.goBack()}
- />
-
- <>
- {translate('delegate.accessLevelDescription')}{' '}
-
- {translate('common.learnMore')}
-
- >
-
- }
- onSelectRow={(option) => {
- if (option.isSelected) {
- Navigation.dismissModal();
- return;
+
+ Navigation.goBack()}
+ />
+
+ <>
+ {translate('delegate.accessLevelDescription')}{' '}
+
+ {translate('common.learnMore')}
+
+ >
+
}
+ onSelectRow={(option) => {
+ if (option.isSelected) {
+ Navigation.dismissModal();
+ return;
+ }
- requestValidationCode();
- setCurrentRole(option.value);
- Navigation.navigate(ROUTES.SETTINGS_UPDATE_DELEGATE_ROLE_MAGIC_CODE.getRoute(login, option.value));
- }}
- sections={[{data: roleOptions}]}
- ListItem={RadioListItem}
- />
+ requestValidationCode();
+ setCurrentRole(option.value);
+ Navigation.navigate(ROUTES.SETTINGS_UPDATE_DELEGATE_ROLE_MAGIC_CODE.getRoute(login, option.value));
+ }}
+ sections={[{data: roleOptions}]}
+ ListItem={RadioListItem}
+ />
+
);
}
diff --git a/src/pages/settings/Security/CloseAccountPage.tsx b/src/pages/settings/Security/CloseAccountPage.tsx
index 6036512df169..434b992febca 100644
--- a/src/pages/settings/Security/CloseAccountPage.tsx
+++ b/src/pages/settings/Security/CloseAccountPage.tsx
@@ -3,6 +3,7 @@ import React, {useEffect, useState} from 'react';
import {View} from 'react-native';
import {useOnyx} from 'react-native-onyx';
import ConfirmModal from '@components/ConfirmModal';
+import DelegateNoAccessWrapper from '@components/DelegateNoAccessWrapper';
import FormProvider from '@components/Form/FormProvider';
import InputWrapper from '@components/Form/InputWrapper';
import type {FormInputErrors, FormOnyxValues} from '@components/Form/types';
@@ -74,58 +75,60 @@ function CloseAccountPage() {
includeSafeAreaPaddingBottom={false}
testID={CloseAccountPage.displayName}
>
- Navigation.goBack()}
- />
-
-
- {translate('closeAccountPage.reasonForLeavingPrompt')}
-
-
- {translate('closeAccountPage.enterDefaultContactToConfirm')} {userEmailOrPhone}
-
-
-
-
-
+
+ Navigation.goBack()}
+ />
+
+
+ {translate('closeAccountPage.reasonForLeavingPrompt')}
+
+
+ {translate('closeAccountPage.enterDefaultContactToConfirm')} {userEmailOrPhone}
+
+
+
+
+
+
);
}
diff --git a/src/pages/settings/Security/SecuritySettingsPage.tsx b/src/pages/settings/Security/SecuritySettingsPage.tsx
index ac66c368f631..1d2a51f10f8a 100644
--- a/src/pages/settings/Security/SecuritySettingsPage.tsx
+++ b/src/pages/settings/Security/SecuritySettingsPage.tsx
@@ -5,6 +5,7 @@ import {Dimensions, View} from 'react-native';
import type {GestureResponderEvent} from 'react-native';
import {useOnyx} from 'react-native-onyx';
import ConfirmModal from '@components/ConfirmModal';
+import DelegateNoAccessModal from '@components/DelegateNoAccessModal';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import * as Expensicons from '@components/Icon/Expensicons';
import {FallbackAvatar} from '@components/Icon/Expensicons';
@@ -63,6 +64,8 @@ function SecuritySettingsPage() {
anchorPositionRight: 0,
});
+ const [isNoDelegateAccessMenuVisible, setIsNoDelegateAccessMenuVisible] = useState(false);
+
const setMenuPosition = useCallback(() => {
if (!delegateButtonRef.current) {
return;
@@ -92,7 +95,9 @@ function SecuritySettingsPage() {
setShouldShowDelegatePopoverMenu(true);
setSelectedDelegate(delegate);
};
-
+ const showDelegateNoAccessMenu = () => {
+ setIsNoDelegateAccessMenuVisible(true);
+ };
useLayoutEffect(() => {
const popoverPositionListener = Dimensions.addEventListener('change', () => {
debounce(setMenuPosition, CONST.TIMING.RESIZE_DEBOUNCE_TIME)();
@@ -111,12 +116,12 @@ function SecuritySettingsPage() {
{
translationKey: 'twoFactorAuth.headerTitle',
icon: Expensicons.Shield,
- action: waitForNavigate(() => Navigation.navigate(ROUTES.SETTINGS_2FA.getRoute())),
+ action: isActingAsDelegate ? showDelegateNoAccessMenu : waitForNavigate(() => Navigation.navigate(ROUTES.SETTINGS_2FA.getRoute())),
},
{
translationKey: 'closeAccountPage.closeAccount',
icon: Expensicons.ClosedSign,
- action: waitForNavigate(() => Navigation.navigate(ROUTES.SETTINGS_CLOSE)),
+ action: isActingAsDelegate ? showDelegateNoAccessMenu : waitForNavigate(() => Navigation.navigate(ROUTES.SETTINGS_CLOSE)),
},
];
@@ -129,7 +134,7 @@ function SecuritySettingsPage() {
link: '',
wrapperStyle: [styles.sectionMenuItemTopDescription],
}));
- }, [translate, waitForNavigate, styles]);
+ }, [translate, waitForNavigate, styles, isActingAsDelegate]);
const delegateMenuItems: MenuItemProps[] = useMemo(
() =>
@@ -292,8 +297,14 @@ function SecuritySettingsPage() {
title={translate('delegate.changeAccessLevel')}
icon={Expensicons.Pencil}
onPress={() => {
- Navigation.navigate(ROUTES.SETTINGS_UPDATE_DELEGATE_ROLE.getRoute(selectedDelegate?.email ?? '', selectedDelegate?.role ?? ''));
+ if (isActingAsDelegate) {
+ Modal.close(() => {
+ setIsNoDelegateAccessMenuVisible(true);
+ });
+ return;
+ }
setShouldShowDelegatePopoverMenu(false);
+ Navigation.navigate(ROUTES.SETTINGS_UPDATE_DELEGATE_ROLE.getRoute(selectedDelegate?.email ?? '', selectedDelegate?.role ?? ''));
setSelectedDelegate(undefined);
}}
wrapperStyle={[styles.pv3, styles.ph5, !shouldUseNarrowLayout ? styles.sidebarPopover : {}]}
@@ -303,6 +314,12 @@ function SecuritySettingsPage() {
icon={Expensicons.Trashcan}
onPress={() =>
Modal.close(() => {
+ if (isActingAsDelegate) {
+ Modal.close(() => {
+ setIsNoDelegateAccessMenuVisible(true);
+ });
+ return;
+ }
setShouldShowDelegatePopoverMenu(false);
setShouldShowRemoveDelegateModal(true);
})
@@ -331,6 +348,10 @@ function SecuritySettingsPage() {
/>
+ setIsNoDelegateAccessMenuVisible(false)}
+ />
>
)}
diff --git a/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthPage.tsx b/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthPage.tsx
index 99f57a5a5a9d..bc3dc588c1b1 100644
--- a/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthPage.tsx
+++ b/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthPage.tsx
@@ -1,8 +1,25 @@
import React from 'react';
+import {useOnyx} from 'react-native-onyx';
import AnimatedStepProvider from '@components/AnimatedStep/AnimatedStepProvider';
+import DelegateNoAccessWrapper from '@components/DelegateNoAccessWrapper';
+import ScreenWrapper from '@components/ScreenWrapper';
+import CONST from '@src/CONST';
+import ONYXKEYS from '@src/ONYXKEYS';
import TwoFactorAuthSteps from './TwoFactorAuthSteps';
function TwoFactorAuthPage() {
+ const [isActingAsDelegate] = useOnyx(ONYXKEYS.ACCOUNT, {selector: (account) => !!account?.delegatedAccess?.delegate});
+ if (isActingAsDelegate) {
+ return (
+
+
+
+ );
+ }
return (
@@ -10,4 +27,6 @@ function TwoFactorAuthPage() {
);
}
+TwoFactorAuthPage.displayName = 'TwoFactorAuthPage';
+
export default TwoFactorAuthPage;
diff --git a/src/pages/settings/Subscription/CardSection/CardSectionDataEmpty/index.tsx b/src/pages/settings/Subscription/CardSection/CardSectionDataEmpty/index.tsx
index 627556e7b5bd..79a200c4772c 100644
--- a/src/pages/settings/Subscription/CardSection/CardSectionDataEmpty/index.tsx
+++ b/src/pages/settings/Subscription/CardSection/CardSectionDataEmpty/index.tsx
@@ -1,26 +1,43 @@
-import React, {useCallback} from 'react';
+import React, {useCallback, useState} from 'react';
+import {useOnyx} from 'react-native-onyx';
import Button from '@components/Button';
+import DelegateNoAccessModal from '@components/DelegateNoAccessModal';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import Navigation from '@navigation/Navigation';
+import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
function CardSectionDataEmpty() {
const {translate} = useLocalize();
const styles = useThemeStyles();
+ const [isActingAsDelegate] = useOnyx(ONYXKEYS.ACCOUNT, {selector: (account) => account?.delegatedAccess?.delegate});
+ const [isNoDelegateAccessMenuVisible, setIsNoDelegateAccessMenuVisible] = useState(false);
const openAddPaymentCardScreen = useCallback(() => {
Navigation.navigate(ROUTES.SETTINGS_SUBSCRIPTION_ADD_PAYMENT_CARD);
}, []);
-
+ const handleAddPaymentCardPress = () => {
+ if (isActingAsDelegate) {
+ setIsNoDelegateAccessMenuVisible(true);
+ return;
+ }
+ openAddPaymentCardScreen();
+ };
return (
-
+ <>
+
+ setIsNoDelegateAccessMenuVisible(false)}
+ />
+ >
);
}
diff --git a/src/pages/settings/Subscription/CardSection/RequestEarlyCancellationMenuItem/index.tsx b/src/pages/settings/Subscription/CardSection/RequestEarlyCancellationMenuItem/index.tsx
index 2d1af54b5b20..4ad35dad8c45 100644
--- a/src/pages/settings/Subscription/CardSection/RequestEarlyCancellationMenuItem/index.tsx
+++ b/src/pages/settings/Subscription/CardSection/RequestEarlyCancellationMenuItem/index.tsx
@@ -1,23 +1,41 @@
-import React from 'react';
+import React, {useState} from 'react';
+import {useOnyx} from 'react-native-onyx';
+import DelegateNoAccessModal from '@components/DelegateNoAccessModal';
import * as Expensicons from '@components/Icon/Expensicons';
import MenuItem from '@components/MenuItem';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import Navigation from '@libs/Navigation/Navigation';
+import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
function RequestEarlyCancellationMenuItem() {
const {translate} = useLocalize();
const styles = useThemeStyles();
+ const [isActingAsDelegate] = useOnyx(ONYXKEYS.ACCOUNT, {selector: (account) => account?.delegatedAccess?.delegate});
+ const [isNoDelegateAccessMenuVisible, setIsNoDelegateAccessMenuVisible] = useState(false);
+ const handleRequestEarlyCancellationPress = () => {
+ if (isActingAsDelegate) {
+ setIsNoDelegateAccessMenuVisible(true);
+ return;
+ }
+ Navigation.navigate(ROUTES.SETTINGS_SUBSCRIPTION_REQUEST_EARLY_CANCELLATION);
+ };
return (
-