From dfb768d570eadf61e5368cc88b7bb0afa2677f68 Mon Sep 17 00:00:00 2001 From: NikkiWines Date: Wed, 11 Jun 2025 22:04:40 +0100 Subject: [PATCH 01/12] add DelegateNoAccessModalProvider --- .../DelegateNoAccessModalProvider.tsx | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 src/components/DelegateNoAccessModalProvider.tsx diff --git a/src/components/DelegateNoAccessModalProvider.tsx b/src/components/DelegateNoAccessModalProvider.tsx new file mode 100644 index 000000000000..6f47d0c57c74 --- /dev/null +++ b/src/components/DelegateNoAccessModalProvider.tsx @@ -0,0 +1,62 @@ +import React, {createContext, useMemo, useState} from 'react'; +import useLocalize from '@hooks/useLocalize'; +import useOnyx from '@hooks/useOnyx'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ConfirmModal from './ConfirmModal'; +import CONST from '@src/CONST'; +import TextLink from './TextLink'; +import useDelegateUserDetails from '@hooks/useDelegateUserDetails'; +import Text from './Text'; + +type DelegateNoAccessContextType = { + showDelegateNoAccessModal: () => void; + isActingAsDelegate: boolean; +}; + +const DelegateNoAccessContext = createContext({ + showDelegateNoAccessModal: () => {}, + isActingAsDelegate: false, +}); + +function DelegateNoAccessModalProvider({children}: React.PropsWithChildren) { + const {translate} = useLocalize(); + const {delegatorEmail} = useDelegateUserDetails(); + const [isModalOpen, setIsModalOpen] = useState(false); + const [account] = useOnyx(ONYXKEYS.ACCOUNT, {canBeMissing: true}); + const isActingAsDelegate = !!account?.delegatedAccess?.delegate; + + const delegateNoAccessPrompt = ( + + {translate('delegate.notAllowedMessageStart')} + {translate('delegate.notAllowedMessageHyperLinked')} + {translate('delegate.notAllowedMessageEnd', {accountOwnerEmail: delegatorEmail ?? ''})} + + ); + const contextValue = useMemo( + () => ({ + isActingAsDelegate, + showDelegateNoAccessModal: () => setIsModalOpen(true), + }), + [isActingAsDelegate], + ); + + return ( + + {children} + setIsModalOpen(false)} + onCancel={() => setIsModalOpen(false)} + title={translate('delegate.notAllowed')} + prompt={delegateNoAccessPrompt} + confirmText={translate('common.buttonConfirm')} + shouldShowCancelButton={false} + /> + + ); +} + +DelegateNoAccessModalProvider.displayName = 'DelegateNoAccessModal'; + +export default DelegateNoAccessModalProvider; +export {DelegateNoAccessContext}; From 530eb1de847b82da81790072e3eacc3971499624 Mon Sep 17 00:00:00 2001 From: NikkiWines Date: Wed, 11 Jun 2025 22:26:26 +0100 Subject: [PATCH 02/12] add to auth screens, test on security settings page --- src/libs/Navigation/AppNavigator/AuthScreens.tsx | 3 ++- src/pages/settings/Security/SecuritySettingsPage.tsx | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.tsx b/src/libs/Navigation/AppNavigator/AuthScreens.tsx index 0e85b8e4b412..0bdcb09d6d2e 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.tsx +++ b/src/libs/Navigation/AppNavigator/AuthScreens.tsx @@ -73,6 +73,7 @@ import TestDriveModalNavigator from './Navigators/TestDriveModalNavigator'; import WelcomeVideoModalNavigator from './Navigators/WelcomeVideoModalNavigator'; import TestDriveDemoNavigator from './TestDriveDemoNavigator'; import useRootNavigatorScreenOptions from './useRootNavigatorScreenOptions'; +import DelegateNoAccessModalProvider from '@components/DelegateNoAccessModalProvider'; type AuthScreensProps = { /** Session of currently logged in user */ @@ -536,7 +537,7 @@ function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDApplie } return ( - + {/* This has to be the first navigator in auth screens. */} Date: Wed, 11 Jun 2025 22:27:21 +0100 Subject: [PATCH 03/12] style --- src/components/DelegateNoAccessModalProvider.tsx | 6 +++--- src/libs/Navigation/AppNavigator/AuthScreens.tsx | 2 +- src/pages/settings/Security/SecuritySettingsPage.tsx | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/DelegateNoAccessModalProvider.tsx b/src/components/DelegateNoAccessModalProvider.tsx index 6f47d0c57c74..72c07f7bc6a8 100644 --- a/src/components/DelegateNoAccessModalProvider.tsx +++ b/src/components/DelegateNoAccessModalProvider.tsx @@ -1,12 +1,12 @@ import React, {createContext, useMemo, useState} from 'react'; +import useDelegateUserDetails from '@hooks/useDelegateUserDetails'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ConfirmModal from './ConfirmModal'; -import CONST from '@src/CONST'; -import TextLink from './TextLink'; -import useDelegateUserDetails from '@hooks/useDelegateUserDetails'; import Text from './Text'; +import TextLink from './TextLink'; type DelegateNoAccessContextType = { showDelegateNoAccessModal: () => void; diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.tsx b/src/libs/Navigation/AppNavigator/AuthScreens.tsx index 0bdcb09d6d2e..5b85bfcf7e32 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.tsx +++ b/src/libs/Navigation/AppNavigator/AuthScreens.tsx @@ -4,6 +4,7 @@ import React, {memo, useEffect, useMemo, useRef, useState} from 'react'; import type {OnyxEntry} from 'react-native-onyx'; import Onyx, {useOnyx, withOnyx} from 'react-native-onyx'; import ComposeProviders from '@components/ComposeProviders'; +import DelegateNoAccessModalProvider from '@components/DelegateNoAccessModalProvider'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import LockedAccountModalProvider from '@components/LockedAccountModalProvider'; import OptionsListContextProvider from '@components/OptionListContextProvider'; @@ -73,7 +74,6 @@ import TestDriveModalNavigator from './Navigators/TestDriveModalNavigator'; import WelcomeVideoModalNavigator from './Navigators/WelcomeVideoModalNavigator'; import TestDriveDemoNavigator from './TestDriveDemoNavigator'; import useRootNavigatorScreenOptions from './useRootNavigatorScreenOptions'; -import DelegateNoAccessModalProvider from '@components/DelegateNoAccessModalProvider'; type AuthScreensProps = { /** Session of currently logged in user */ diff --git a/src/pages/settings/Security/SecuritySettingsPage.tsx b/src/pages/settings/Security/SecuritySettingsPage.tsx index 7db2650be54f..a184a3d8e5c4 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, StyleProp, ViewStyle} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import ConfirmModal from '@components/ConfirmModal'; +import {DelegateNoAccessContext} from '@components/DelegateNoAccessModalProvider'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import * as Expensicons from '@components/Icon/Expensicons'; import {FallbackAvatar} from '@components/Icon/Expensicons'; @@ -43,7 +44,6 @@ import ROUTES from '@src/ROUTES'; import type {Delegate} from '@src/types/onyx/Account'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import type IconAsset from '@src/types/utils/IconAsset'; -import { DelegateNoAccessContext } from '@components/DelegateNoAccessModalProvider'; type BaseMenuItemType = { translationKey: TranslationPaths; From 6a86bdc83ba6327f730d48d54ea04990b0c64cee Mon Sep 17 00:00:00 2001 From: NikkiWines Date: Wed, 11 Jun 2025 22:56:33 +0100 Subject: [PATCH 04/12] update profile, contact methods, and wallet page to use context provider --- .../Contacts/ContactMethodDetailsPage.tsx | 11 +++------ .../Profile/Contacts/ContactMethodsPage.tsx | 11 +++------ src/pages/settings/Profile/ProfilePage.tsx | 20 ++++++---------- .../Security/SecuritySettingsPage.tsx | 23 ++++++------------- .../settings/Wallet/WalletPage/WalletPage.tsx | 18 +++++---------- 5 files changed, 26 insertions(+), 57 deletions(-) diff --git a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.tsx b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.tsx index 93042bce02b9..6de3aa90fe6d 100644 --- a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.tsx +++ b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.tsx @@ -4,7 +4,7 @@ import {InteractionManager, Keyboard} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import ConfirmModal from '@components/ConfirmModal'; -import DelegateNoAccessModal from '@components/DelegateNoAccessModal'; +import {DelegateNoAccessContext} from '@components/DelegateNoAccessModalProvider'; import ErrorMessageRow from '@components/ErrorMessageRow'; import FullscreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; @@ -58,8 +58,7 @@ function ContactMethodDetailsPage({route}: ContactMethodDetailsPageProps) { const [securityGroups, securityGroupsResult] = useOnyx(ONYXKEYS.COLLECTION.SECURITY_GROUP, {canBeMissing: true}); const [isLoadingReportData, isLoadingReportDataResult] = useOnyx(ONYXKEYS.IS_LOADING_REPORT_DATA, {initialValue: true, canBeMissing: true}); const [isValidateCodeFormVisible, setIsValidateCodeFormVisible] = useState(true); - const [isActingAsDelegate] = useOnyx(ONYXKEYS.ACCOUNT, {selector: (account) => !!account?.delegatedAccess?.delegate, canBeMissing: true}); - const [isNoDelegateAccessMenuVisible, setIsNoDelegateAccessMenuVisible] = useState(false); + const {isActingAsDelegate, showDelegateNoAccessModal} = useContext(DelegateNoAccessContext); const isLoadingOnyxValues = isLoadingOnyxValue(loginListResult, sessionResult, myDomainSecurityGroupsResult, securityGroupsResult, isLoadingReportDataResult); const {isAccountLocked, showLockedAccountModal} = useContext(LockedAccountContext); @@ -271,7 +270,7 @@ function ContactMethodDetailsPage({route}: ContactMethodDetailsPageProps) { iconFill={theme.danger} onPress={() => { if (isActingAsDelegate) { - setIsNoDelegateAccessMenuVisible(true); + showDelegateNoAccessModal(); return; } if (isAccountLocked) { @@ -361,10 +360,6 @@ function ContactMethodDetailsPage({route}: ContactMethodDetailsPageProps) { {!isValidateCodeFormVisible && !!loginData.validatedDate && getMenuItems()} {getDeleteConfirmationModal()} - setIsNoDelegateAccessMenuVisible(false)} - /> ); } diff --git a/src/pages/settings/Profile/Contacts/ContactMethodsPage.tsx b/src/pages/settings/Profile/Contacts/ContactMethodsPage.tsx index 433656a1fdfb..e59d2683ef78 100644 --- a/src/pages/settings/Profile/Contacts/ContactMethodsPage.tsx +++ b/src/pages/settings/Profile/Contacts/ContactMethodsPage.tsx @@ -5,6 +5,7 @@ import {useOnyx} from 'react-native-onyx'; import Button from '@components/Button'; import CopyTextToClipboard from '@components/CopyTextToClipboard'; import DelegateNoAccessModal from '@components/DelegateNoAccessModal'; +import {DelegateNoAccessContext} from '@components/DelegateNoAccessModalProvider'; import FixedFooter from '@components/FixedFooter'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import {LockedAccountContext} from '@components/LockedAccountModalProvider'; @@ -34,9 +35,7 @@ function ContactMethodsPage({route}: ContactMethodsPageProps) { const loginNames = Object.keys(loginList ?? {}); const navigateBackTo = route?.params?.backTo; - const [isActingAsDelegate] = useOnyx(ONYXKEYS.ACCOUNT, {selector: (account) => !!account?.delegatedAccess?.delegate, canBeMissing: false}); - const [isNoDelegateAccessMenuVisible, setIsNoDelegateAccessMenuVisible] = useState(false); - + const {isActingAsDelegate, showDelegateNoAccessModal} = useContext(DelegateNoAccessContext); const [isUserValidated] = useOnyx(ONYXKEYS.ACCOUNT, {selector: (account) => account?.validated, canBeMissing: false}); const {isAccountLocked, showLockedAccountModal} = useContext(LockedAccountContext); @@ -94,7 +93,7 @@ function ContactMethodsPage({route}: ContactMethodsPageProps) { const onNewContactMethodButtonPress = useCallback(() => { if (isActingAsDelegate) { - setIsNoDelegateAccessMenuVisible(true); + showDelegateNoAccessModal(); return; } if (isAccountLocked) { @@ -141,10 +140,6 @@ function ContactMethodsPage({route}: ContactMethodsPageProps) { /> - setIsNoDelegateAccessMenuVisible(false)} - /> ); } diff --git a/src/pages/settings/Profile/ProfilePage.tsx b/src/pages/settings/Profile/ProfilePage.tsx index d33bd317ac15..681514cf10ca 100755 --- a/src/pages/settings/Profile/ProfilePage.tsx +++ b/src/pages/settings/Profile/ProfilePage.tsx @@ -1,11 +1,11 @@ import {useRoute} from '@react-navigation/native'; -import React, {useState} from 'react'; +import React, {useContext} 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 {DelegateNoAccessContext} from '@components/DelegateNoAccessModalProvider'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import * as Expensicons from '@components/Icon/Expensicons'; @@ -64,9 +64,7 @@ function ProfilePage() { const privateDetails = privatePersonalDetails ?? {}; const legalName = `${privateDetails.legalFirstName ?? ''} ${privateDetails.legalLastName ?? ''}`.trim(); - const [isActingAsDelegate] = useOnyx(ONYXKEYS.ACCOUNT, {selector: (account) => !!account?.delegatedAccess?.delegate, canBeMissing: false}); - const [isNoDelegateAccessMenuVisible, setIsNoDelegateAccessMenuVisible] = useState(false); - + const {isActingAsDelegate, showDelegateNoAccessModal} = useContext(DelegateNoAccessContext); const publicOptions = [ { description: translate('displayNamePage.headerTitle'), @@ -102,7 +100,7 @@ function ProfilePage() { title: legalName, action: () => { if (isActingAsDelegate) { - setIsNoDelegateAccessMenuVisible(true); + showDelegateNoAccessModal(); return; } Navigation.navigate(ROUTES.SETTINGS_LEGAL_NAME); @@ -113,7 +111,7 @@ function ProfilePage() { title: privateDetails.dob ?? '', action: () => { if (isActingAsDelegate) { - setIsNoDelegateAccessMenuVisible(true); + showDelegateNoAccessModal(); return; } Navigation.navigate(ROUTES.SETTINGS_DATE_OF_BIRTH); @@ -124,7 +122,7 @@ function ProfilePage() { title: privateDetails.phoneNumber ?? '', action: () => { if (isActingAsDelegate) { - setIsNoDelegateAccessMenuVisible(true); + showDelegateNoAccessModal(); return; } Navigation.navigate(ROUTES.SETTINGS_PHONE_NUMBER); @@ -136,7 +134,7 @@ function ProfilePage() { title: getFormattedAddress(privateDetails), action: () => { if (isActingAsDelegate) { - setIsNoDelegateAccessMenuVisible(true); + showDelegateNoAccessModal(); return; } Navigation.navigate(ROUTES.SETTINGS_ADDRESS); @@ -256,10 +254,6 @@ function ProfilePage() { - setIsNoDelegateAccessMenuVisible(false)} - /> ); } diff --git a/src/pages/settings/Security/SecuritySettingsPage.tsx b/src/pages/settings/Security/SecuritySettingsPage.tsx index a184a3d8e5c4..eb743588b4da 100644 --- a/src/pages/settings/Security/SecuritySettingsPage.tsx +++ b/src/pages/settings/Security/SecuritySettingsPage.tsx @@ -80,9 +80,7 @@ function SecuritySettingsPage() { }); const {isAccountLocked, showLockedAccountModal} = useContext(LockedAccountContext); - const isActingAsDelegate = !!account?.delegatedAccess?.delegate || false; - const [isNoDelegateAccessMenuVisible, setIsNoDelegateAccessMenuVisible] = useState(false); - + const {isActingAsDelegate, showDelegateNoAccessModal} = useContext(DelegateNoAccessContext); const delegates = account?.delegatedAccess?.delegates ?? []; const delegators = account?.delegatedAccess?.delegators ?? []; @@ -109,9 +107,6 @@ function SecuritySettingsPage() { setSelectedEmail(delegate.email); }; - const showDelegateNoAccessMenu = () => { - setIsNoDelegateAccessMenuVisible(true); - }; useLayoutEffect(() => { const popoverPositionListener = Dimensions.addEventListener('change', () => { debounce(setMenuPosition, CONST.TIMING.RESIZE_DEBOUNCE_TIME)(); @@ -132,7 +127,7 @@ function SecuritySettingsPage() { icon: Expensicons.Shield, action: () => { if (isActingAsDelegate) { - showDelegateNoAccessMenu(); + showDelegateNoAccessModal(); return; } if (isAccountLocked) { @@ -150,7 +145,7 @@ function SecuritySettingsPage() { icon: Expensicons.ArrowCollapse, action: () => { if (isActingAsDelegate) { - showDelegateNoAccessMenu(); + showDelegateNoAccessModal(); return; } if (isAccountLocked) { @@ -181,7 +176,7 @@ function SecuritySettingsPage() { icon: Expensicons.ClosedSign, action: () => { if (isActingAsDelegate) { - showDelegateNoAccessMenu(); + showDelegateNoAccessModal(); return; } @@ -201,7 +196,7 @@ function SecuritySettingsPage() { link: '', wrapperStyle: [styles.sectionMenuItemTopDescription], })); - }, [translate, waitForNavigate, styles, isActingAsDelegate, isBetaEnabled, isAccountLocked, showLockedAccountModal]); + }, [translate, waitForNavigate, styles, isActingAsDelegate, showDelegateNoAccessModal, isBetaEnabled, isAccountLocked, showLockedAccountModal]); const delegateMenuItems: MenuItemProps[] = useMemo( () => @@ -281,7 +276,7 @@ function SecuritySettingsPage() { icon: Expensicons.Pencil, onPress: () => { if (isActingAsDelegate) { - modalClose(() => setIsNoDelegateAccessMenuVisible(true)); + modalClose(() => showDelegateNoAccessModal()); return; } if (isAccountLocked) { @@ -299,7 +294,7 @@ function SecuritySettingsPage() { icon: Expensicons.Trashcan, onPress: () => { if (isActingAsDelegate) { - modalClose(() => setIsNoDelegateAccessMenuVisible(true)); + modalClose(() => showDelegateNoAccessModal()); return; } if (isAccountLocked) { @@ -441,10 +436,6 @@ function SecuritySettingsPage() { /> - setIsNoDelegateAccessMenuVisible(false)} - /> )} diff --git a/src/pages/settings/Wallet/WalletPage/WalletPage.tsx b/src/pages/settings/Wallet/WalletPage/WalletPage.tsx index 9dab754a4d7d..ea97b00de6f9 100644 --- a/src/pages/settings/Wallet/WalletPage/WalletPage.tsx +++ b/src/pages/settings/Wallet/WalletPage/WalletPage.tsx @@ -7,7 +7,7 @@ import {ActivityIndicator, View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import AddPaymentMethodMenu from '@components/AddPaymentMethodMenu'; import ConfirmModal from '@components/ConfirmModal'; -import DelegateNoAccessModal from '@components/DelegateNoAccessModal'; +import {DelegateNoAccessContext} from '@components/DelegateNoAccessModalProvider'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import Icon from '@components/Icon'; @@ -65,11 +65,9 @@ function WalletPage({shouldListenForResize = false}: WalletPageProps) { const [isLoadingApp] = useOnyx(ONYXKEYS.IS_LOADING_APP, {canBeMissing: false}); const [userAccount] = useOnyx(ONYXKEYS.ACCOUNT, {canBeMissing: true}); const isUserValidated = userAccount?.validated ?? false; - const isActingAsDelegate = !!userAccount?.delegatedAccess?.delegate || false; + const {isActingAsDelegate, showDelegateNoAccessModal} = useContext(DelegateNoAccessContext); const {isAccountLocked, showLockedAccountModal} = useContext(LockedAccountContext); - const [isNoDelegateAccessMenuVisible, setIsNoDelegateAccessMenuVisible] = useState(false); - const theme = useTheme(); const styles = useThemeStyles(); const {translate} = useLocalize(); @@ -191,7 +189,7 @@ function WalletPage({shouldListenForResize = false}: WalletPageProps) { return; } if (isActingAsDelegate) { - setIsNoDelegateAccessMenuVisible(true); + showDelegateNoAccessModal(); return; } if (isAccountLocked) { @@ -578,7 +576,7 @@ function WalletPage({shouldListenForResize = false}: WalletPageProps) { ref={buttonRef as ForwardedRef} onPress={() => { if (isActingAsDelegate) { - setIsNoDelegateAccessMenuVisible(true); + showDelegateNoAccessModal(); return; } if (isAccountLocked) { @@ -650,7 +648,7 @@ function WalletPage({shouldListenForResize = false}: WalletPageProps) { onPress={() => { if (isActingAsDelegate) { closeModal(() => { - setIsNoDelegateAccessMenuVisible(true); + showDelegateNoAccessModal(); }); return; } @@ -671,7 +669,7 @@ function WalletPage({shouldListenForResize = false}: WalletPageProps) { onPress={() => { if (isActingAsDelegate) { closeModal(() => { - setIsNoDelegateAccessMenuVisible(true); + showDelegateNoAccessModal(); }); return; } @@ -762,10 +760,6 @@ function WalletPage({shouldListenForResize = false}: WalletPageProps) { anchorRef={addPaymentMethodAnchorRef} shouldShowPersonalBankAccountOption /> - setIsNoDelegateAccessMenuVisible(false)} - /> ); } From ef28c6cdf34434a43a05c7c4bc91f0b1acbeba24 Mon Sep 17 00:00:00 2001 From: NikkiWines Date: Thu, 12 Jun 2025 12:28:34 +0100 Subject: [PATCH 05/12] update attachment picker, subscription settings, workspace settings to use context provider --- .../AttachmentPickerWithMenuItems.tsx | 6 +----- .../CardSection/CardSectionDataEmpty/index.tsx | 13 ++++--------- .../RequestEarlyCancellationMenuItem/index.tsx | 12 ++++-------- .../SubscriptionSettings/index.tsx | 18 +++++++----------- .../WorkspaceCompanyCardPageEmptyState.tsx | 12 ++++-------- .../companyCards/WorkspaceCompanyCardsPage.tsx | 12 ++++-------- .../WorkspaceExpensifyCardListPage.tsx | 10 +++------- .../WorkspaceExpensifyCardPageEmptyState.tsx | 11 +++-------- 8 files changed, 30 insertions(+), 64 deletions(-) diff --git a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx index fed5611707b1..93c7a6a16be2 100644 --- a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx +++ b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx @@ -167,7 +167,7 @@ function AttachmentPickerWithMenuItems({ onSelected: () => { if (isDelegateAccessRestricted) { close(() => { - setIsNoDelegateAccessMenuVisible(true); + showDelegateNoAccessModal(); }); return; } @@ -408,10 +408,6 @@ function AttachmentPickerWithMenuItems({ withoutOverlay anchorRef={actionButtonRef} /> - setIsNoDelegateAccessMenuVisible(false)} - /> ); }} diff --git a/src/pages/settings/Subscription/CardSection/CardSectionDataEmpty/index.tsx b/src/pages/settings/Subscription/CardSection/CardSectionDataEmpty/index.tsx index 79a200c4772c..cae9735932b1 100644 --- a/src/pages/settings/Subscription/CardSection/CardSectionDataEmpty/index.tsx +++ b/src/pages/settings/Subscription/CardSection/CardSectionDataEmpty/index.tsx @@ -1,7 +1,7 @@ -import React, {useCallback, useState} from 'react'; +import React, {useCallback, useContext} from 'react'; import {useOnyx} from 'react-native-onyx'; import Button from '@components/Button'; -import DelegateNoAccessModal from '@components/DelegateNoAccessModal'; +import {DelegateNoAccessContext} from '@components/DelegateNoAccessModalProvider'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@navigation/Navigation'; @@ -11,15 +11,14 @@ 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 {isActingAsDelegate, showDelegateNoAccessModal} = useContext(DelegateNoAccessContext); const openAddPaymentCardScreen = useCallback(() => { Navigation.navigate(ROUTES.SETTINGS_SUBSCRIPTION_ADD_PAYMENT_CARD); }, []); const handleAddPaymentCardPress = () => { if (isActingAsDelegate) { - setIsNoDelegateAccessMenuVisible(true); + showDelegateNoAccessModal(); return; } openAddPaymentCardScreen(); @@ -33,10 +32,6 @@ function CardSectionDataEmpty() { success large /> - setIsNoDelegateAccessMenuVisible(false)} - /> ); } diff --git a/src/pages/settings/Subscription/CardSection/RequestEarlyCancellationMenuItem/index.tsx b/src/pages/settings/Subscription/CardSection/RequestEarlyCancellationMenuItem/index.tsx index 4ad35dad8c45..f850ba23ea6c 100644 --- a/src/pages/settings/Subscription/CardSection/RequestEarlyCancellationMenuItem/index.tsx +++ b/src/pages/settings/Subscription/CardSection/RequestEarlyCancellationMenuItem/index.tsx @@ -1,6 +1,7 @@ -import React, {useState} from 'react'; +import React, {useContext, useState} from 'react'; import {useOnyx} from 'react-native-onyx'; import DelegateNoAccessModal from '@components/DelegateNoAccessModal'; +import {DelegateNoAccessContext} from '@components/DelegateNoAccessModalProvider'; import * as Expensicons from '@components/Icon/Expensicons'; import MenuItem from '@components/MenuItem'; import useLocalize from '@hooks/useLocalize'; @@ -12,12 +13,11 @@ 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 {isActingAsDelegate, showDelegateNoAccessModal} = useContext(DelegateNoAccessContext); const handleRequestEarlyCancellationPress = () => { if (isActingAsDelegate) { - setIsNoDelegateAccessMenuVisible(true); + showDelegateNoAccessModal(); return; } Navigation.navigate(ROUTES.SETTINGS_SUBSCRIPTION_REQUEST_EARLY_CANCELLATION); @@ -31,10 +31,6 @@ function RequestEarlyCancellationMenuItem() { wrapperStyle={styles.sectionMenuItemTopDescription} onPress={handleRequestEarlyCancellationPress} /> - setIsNoDelegateAccessMenuVisible(false)} - /> ); } diff --git a/src/pages/settings/Subscription/SubscriptionSettings/index.tsx b/src/pages/settings/Subscription/SubscriptionSettings/index.tsx index 40cb8e04bb3b..f39ce201bd2b 100644 --- a/src/pages/settings/Subscription/SubscriptionSettings/index.tsx +++ b/src/pages/settings/Subscription/SubscriptionSettings/index.tsx @@ -1,8 +1,9 @@ -import React, {useState} from 'react'; +import React, {useContext, useState} from 'react'; import type {StyleProp, TextStyle} from 'react-native'; import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import DelegateNoAccessModal from '@components/DelegateNoAccessModal'; +import {DelegateNoAccessContext} from '@components/DelegateNoAccessModalProvider'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; @@ -66,9 +67,8 @@ function SubscriptionSettings() { const preferredCurrency = usePreferredCurrency(); const illustrations = useThemeIllustrations(); const possibleCostSavings = useSubscriptionPossibleCostSavings(); - const isActingAsDelegate = !!account?.delegatedAccess?.delegate; + const {isActingAsDelegate, showDelegateNoAccessModal} = useContext(DelegateNoAccessContext); const isAnnual = privateSubscription?.type === CONST.SUBSCRIPTION.TYPE.ANNUAL; - const [isNoDelegateAccessMenuVisible, setIsNoDelegateAccessMenuVisible] = useState(false); const [privateTaxExempt] = useOnyx(ONYXKEYS.NVP_PRIVATE_TAX_EXEMPT, {canBeMissing: true}); const subscriptionPrice = getSubscriptionPrice(subscriptionPlan, preferredCurrency, privateSubscription?.type); const priceDetails = translate(`subscription.yourPlan.${subscriptionPlan === CONST.POLICY.TYPE.CORPORATE ? 'control' : 'collect'}.${isAnnual ? 'priceAnnual' : 'pricePayPerUse'}`, { @@ -79,7 +79,7 @@ function SubscriptionSettings() { const onOptionSelected = (option: SubscriptionType) => { if (privateSubscription?.type !== option && isActingAsDelegate) { - setIsNoDelegateAccessMenuVisible(true); + showDelegateNoAccessModal(); return; } if (privateSubscription?.type === CONST.SUBSCRIPTION.TYPE.ANNUAL && option === CONST.SUBSCRIPTION.TYPE.PAY_PER_USE && !account?.canDowngrade) { @@ -92,7 +92,7 @@ function SubscriptionSettings() { const onSubscriptionSizePress = () => { if (isActingAsDelegate) { - setIsNoDelegateAccessMenuVisible(true); + showDelegateNoAccessModal(); return; } Navigation.navigate(ROUTES.SETTINGS_SUBSCRIPTION_SIZE.getRoute(1)); @@ -126,7 +126,7 @@ function SubscriptionSettings() { const handleAutoRenewToggle = () => { if (isActingAsDelegate) { - setIsNoDelegateAccessMenuVisible(true); + showDelegateNoAccessModal(); return; } if (!privateSubscription?.autoRenew) { @@ -142,7 +142,7 @@ function SubscriptionSettings() { const handleAutoIncreaseToggle = () => { if (isActingAsDelegate) { - setIsNoDelegateAccessMenuVisible(true); + showDelegateNoAccessModal(); return; } updateSubscriptionAddNewUsersAutomatically(!privateSubscription?.addNewUsersAutomatically); @@ -253,10 +253,6 @@ function SubscriptionSettings() { titleStyle={privateTaxExempt ? undefined : styles.textBold} title={privateTaxExempt ? translate('subscription.details.taxExemptEnabled') : translate('subscription.details.taxExempt')} /> - setIsNoDelegateAccessMenuVisible(false)} - /> ); diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardPageEmptyState.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardPageEmptyState.tsx index 22be739d971e..7c662fe9a798 100644 --- a/src/pages/workspace/companyCards/WorkspaceCompanyCardPageEmptyState.tsx +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardPageEmptyState.tsx @@ -1,7 +1,8 @@ -import React, {useCallback, useState} from 'react'; +import React, {useCallback, useContext, useState} from 'react'; import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import DelegateNoAccessModal from '@components/DelegateNoAccessModal'; +import {DelegateNoAccessContext} from '@components/DelegateNoAccessModalProvider'; import FeatureList from '@components/FeatureList'; import type {FeatureListItem} from '@components/FeatureList'; import {CompanyCardsEmptyState, CreditCardsNew, HandCard, MagnifyingGlassMoney} from '@components/Icon/Illustrations'; @@ -43,8 +44,7 @@ function WorkspaceCompanyCardPageEmptyState({policy, shouldShowGBDisclaimer}: Wo const {translate} = useLocalize(); const styles = useThemeStyles(); const {shouldUseNarrowLayout} = useResponsiveLayout(); - const [isActingAsDelegate] = useOnyx(ONYXKEYS.ACCOUNT, {selector: (account) => !!account?.delegatedAccess?.delegate, canBeMissing: true}); - const [isNoDelegateAccessMenuVisible, setIsNoDelegateAccessMenuVisible] = useState(false); + const {isActingAsDelegate, showDelegateNoAccessModal} = useContext(DelegateNoAccessContext); const [allWorkspaceCards] = useOnyx(ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST, {canBeMissing: true}); const shouldShowExpensifyCardPromotionBanner = !hasIssuedExpensifyCard(policy?.workspaceAccountID ?? CONST.DEFAULT_NUMBER_ID, allWorkspaceCards); const workspaceAccountID = policy?.workspaceAccountID ?? CONST.DEFAULT_NUMBER_ID; @@ -54,7 +54,7 @@ function WorkspaceCompanyCardPageEmptyState({policy, shouldShowGBDisclaimer}: Wo return; } if (isActingAsDelegate) { - setIsNoDelegateAccessMenuVisible(true); + showDelegateNoAccessModal(); return; } clearAddNewCardFlow(); @@ -78,10 +78,6 @@ function WorkspaceCompanyCardPageEmptyState({policy, shouldShowGBDisclaimer}: Wo titleStyles={styles.textHeadlineH1} isButtonDisabled={workspaceAccountID === CONST.DEFAULT_NUMBER_ID} /> - setIsNoDelegateAccessMenuVisible(false)} - /> {!!shouldShowGBDisclaimer && {translate('workspace.companyCards.ukRegulation')}} ); diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardsPage.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardsPage.tsx index 012220f81543..fe75e62b7ef2 100644 --- a/src/pages/workspace/companyCards/WorkspaceCompanyCardsPage.tsx +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardsPage.tsx @@ -1,8 +1,9 @@ -import React, {useCallback, useEffect, useState} from 'react'; +import React, {useCallback, useContext, useEffect, useState} from 'react'; import {ActivityIndicator} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import DecisionModal from '@components/DecisionModal'; import DelegateNoAccessModal from '@components/DelegateNoAccessModal'; +import {DelegateNoAccessContext} from '@components/DelegateNoAccessModalProvider'; import * as Illustrations from '@components/Icon/Illustrations'; import useCardFeeds from '@hooks/useCardFeeds'; import useCardsList from '@hooks/useCardsList'; @@ -61,8 +62,7 @@ function WorkspaceCompanyCardsPage({route}: WorkspaceCompanyCardsPageProps) { const {cardList, ...cards} = cardsList ?? {}; - const [isActingAsDelegate] = useOnyx(ONYXKEYS.ACCOUNT, {selector: (account) => !!account?.delegatedAccess?.delegate, canBeMissing: false}); - const [isNoDelegateAccessMenuVisible, setIsNoDelegateAccessMenuVisible] = useState(false); + const {isActingAsDelegate, showDelegateNoAccessModal} = useContext(DelegateNoAccessContext); const filteredCardList = getFilteredCardList(cardsList, selectedFeed ? cardFeeds?.settings?.oAuthAccountDetails?.[selectedFeed] : undefined); @@ -98,7 +98,7 @@ function WorkspaceCompanyCardsPage({route}: WorkspaceCompanyCardsPageProps) { const handleAssignCard = () => { if (isActingAsDelegate) { - setIsNoDelegateAccessMenuVisible(true); + showDelegateNoAccessModal(); return; } if (!selectedFeed) { @@ -194,10 +194,6 @@ function WorkspaceCompanyCardsPage({route}: WorkspaceCompanyCardsPageProps) { )} )} - setIsNoDelegateAccessMenuVisible(false)} - /> 1; - const [isActingAsDelegate] = useOnyx(ONYXKEYS.ACCOUNT, {selector: (account) => !!account?.delegatedAccess?.delegate, canBeMissing: false}); - const [isNoDelegateAccessMenuVisible, setIsNoDelegateAccessMenuVisible] = useState(false); + const {isActingAsDelegate, showDelegateNoAccessModal} = useContext(DelegateNoAccessContext); const {isAccountLocked, showLockedAccountModal} = useContext(LockedAccountContext); const shouldChangeLayout = isMediumScreenWidth || shouldUseNarrowLayout; @@ -98,7 +98,7 @@ function WorkspaceExpensifyCardListPage({route, cardsList, fundID}: WorkspaceExp return; } if (isActingAsDelegate) { - setIsNoDelegateAccessMenuVisible(true); + showDelegateNoAccessModal(); return; } const activeRoute = Navigation.getActiveRoute(); @@ -247,10 +247,6 @@ function WorkspaceExpensifyCardListPage({route, cardsList, fundID}: WorkspaceExp {translate('workspace.expensifyCard.disclaimer')} )} - setIsNoDelegateAccessMenuVisible(false)} - /> ); } diff --git a/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardPageEmptyState.tsx b/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardPageEmptyState.tsx index 14d563fe627b..8b953e48f6f3 100644 --- a/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardPageEmptyState.tsx +++ b/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardPageEmptyState.tsx @@ -2,7 +2,7 @@ import React, {useCallback, useContext, useState} from 'react'; import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import ConfirmModal from '@components/ConfirmModal'; -import DelegateNoAccessModal from '@components/DelegateNoAccessModal'; +import {DelegateNoAccessContext} from '@components/DelegateNoAccessModalProvider'; import FeatureList from '@components/FeatureList'; import type {FeatureListItem} from '@components/FeatureList'; import * as Illustrations from '@components/Icon/Illustrations'; @@ -56,8 +56,7 @@ function WorkspaceExpensifyCardPageEmptyState({route, policy}: WorkspaceExpensif const [reimbursementAccount] = useOnyx(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {canBeMissing: false}); const [isCurrencyModalOpen, setIsCurrencyModalOpen] = useDismissModalForUSD(policy?.outputCurrency); - const [isActingAsDelegate] = useOnyx(ONYXKEYS.ACCOUNT, {selector: (account) => !!account?.delegatedAccess?.delegate, canBeMissing: false}); - const [isNoDelegateAccessMenuVisible, setIsNoDelegateAccessMenuVisible] = useState(false); + const {isActingAsDelegate, showDelegateNoAccessModal} = useContext(DelegateNoAccessContext); const {isAccountLocked, showLockedAccountModal} = useContext(LockedAccountContext); const eligibleBankAccounts = getEligibleBankAccountsForCard(bankAccountList ?? {}); @@ -101,7 +100,7 @@ function WorkspaceExpensifyCardPageEmptyState({route, policy}: WorkspaceExpensif ctaAccessibilityLabel={translate('workspace.moreFeatures.expensifyCard.feed.ctaTitle')} onCtaPress={() => { if (isActingAsDelegate) { - setIsNoDelegateAccessMenuVisible(true); + showDelegateNoAccessModal(); return; } if (isAccountLocked) { @@ -131,10 +130,6 @@ function WorkspaceExpensifyCardPageEmptyState({route, policy}: WorkspaceExpensif /> {translate('workspace.expensifyCard.disclaimer')} - setIsNoDelegateAccessMenuVisible(false)} - /> ); } From 25aea9e83cea3cf5184dfbce30c2df4dd419f52d Mon Sep 17 00:00:00 2001 From: NikkiWines Date: Thu, 12 Jun 2025 17:09:59 +0100 Subject: [PATCH 06/12] update delegatenoaccess for reports --- src/components/MoneyReportHeader.tsx | 16 ++++++---------- .../MoneyRequestReportPreviewContent.tsx | 14 +++++--------- .../MoneyRequestReportPreview/index.tsx | 2 -- .../MoneyRequestReportPreview/types.ts | 1 - .../ReportActionItem/ReportPreview.tsx | 14 +++++--------- 5 files changed, 16 insertions(+), 31 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 3ff79309e737..fb4293349325 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -1,5 +1,5 @@ import {useRoute} from '@react-navigation/native'; -import React, {useCallback, useEffect, useMemo, useState} from 'react'; +import React, {useCallback, useContext, useEffect, useMemo, useState} from 'react'; import {ActivityIndicator, InteractionManager, View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {useOnyx} from 'react-native-onyx'; @@ -113,6 +113,7 @@ import ProcessMoneyReportHoldMenu from './ProcessMoneyReportHoldMenu'; import {useSearchContext} from './Search/SearchContext'; import AnimatedSettlementButton from './SettlementButton/AnimatedSettlementButton'; import Text from './Text'; +import { DelegateNoAccessContext } from './DelegateNoAccessModalProvider'; type MoneyReportHeaderProps = { /** The report currently being looked at */ @@ -295,8 +296,7 @@ function MoneyReportHeader({ const bankAccountRoute = getBankAccountRoute(chatReport); const {nonHeldAmount, fullAmount, hasValidNonHeldAmount} = getNonHeldAndFullAmount(moneyRequestReport, shouldShowPayButton); const isAnyTransactionOnHold = hasHeldExpensesReportUtils(moneyRequestReport?.reportID); - const {isDelegateAccessRestricted} = useDelegateUserDetails(); - const [isNoDelegateAccessMenuVisible, setIsNoDelegateAccessMenuVisible] = useState(false); + const {isDelegateAccessRestricted, showDelegateNoAccessModal} = useContext(DelegateNoAccessContext); const [isLoadingReportData] = useOnyx(ONYXKEYS.IS_LOADING_REPORT_DATA, {canBeMissing: true}); const isReportInRHP = route.name === SCREENS.SEARCH.REPORT_RHP; @@ -310,7 +310,7 @@ function MoneyReportHeader({ setPaymentType(type); setRequestType(CONST.IOU.REPORT_ACTION_TYPE.PAY); if (isDelegateAccessRestricted) { - setIsNoDelegateAccessMenuVisible(true); + showDelegateNoAccessModal(); } else if (isAnyTransactionOnHold) { InteractionManager.runAfterInteractions(() => setIsHoldMenuVisible(true)); } else if (isInvoiceReport) { @@ -327,7 +327,7 @@ function MoneyReportHeader({ const confirmApproval = () => { setRequestType(CONST.IOU.REPORT_ACTION_TYPE.APPROVE); if (isDelegateAccessRestricted) { - setIsNoDelegateAccessMenuVisible(true); + showDelegateNoAccessModal(); } else if (isAnyTransactionOnHold) { setIsHoldMenuVisible(true); } else { @@ -704,7 +704,7 @@ function MoneyReportHeader({ value: CONST.REPORT.SECONDARY_ACTIONS.UNAPPROVE, onSelected: () => { if (isDelegateAccessRestricted) { - setIsNoDelegateAccessMenuVisible(true); + showDelegateNoAccessModal(); return; } @@ -1019,10 +1019,6 @@ function MoneyReportHeader({ transactionCount={transactionIDs?.length ?? 0} /> )} - setIsNoDelegateAccessMenuVisible(false)} - /> isWaitingForSubmissionFromCurrentUserReportUtils(chatReport, policy), [chatReport, policy]); - const [isNoDelegateAccessMenuVisible, setIsNoDelegateAccessMenuVisible] = useState(false); + const {isDelegateAccessRestricted, showDelegateNoAccessModal} = useContext(DelegateNoAccessContext); const [reportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReportID}`, {canBeMissing: true}); const confirmPayment = useCallback( (type: PaymentMethodType | undefined, payAsBusiness?: boolean) => { @@ -200,7 +200,7 @@ function MoneyRequestReportPreviewContent({ setPaymentType(type); setRequestType(CONST.IOU.REPORT_ACTION_TYPE.PAY); if (isDelegateAccessRestricted) { - setIsNoDelegateAccessMenuVisible(true); + showDelegateNoAccessModal(); } else if (hasHeldExpensesReportUtils(iouReport?.reportID)) { setIsHoldMenuVisible(true); } else if (chatReport && iouReport) { @@ -218,7 +218,7 @@ function MoneyRequestReportPreviewContent({ const confirmApproval = () => { setRequestType(CONST.IOU.REPORT_ACTION_TYPE.APPROVE); if (isDelegateAccessRestricted) { - setIsNoDelegateAccessMenuVisible(true); + showDelegateNoAccessModal(); } else if (hasHeldExpensesReportUtils(iouReport?.reportID)) { setIsHoldMenuVisible(true); } else { @@ -767,10 +767,6 @@ function MoneyRequestReportPreviewContent({ - setIsNoDelegateAccessMenuVisible(false)} - /> {isHoldMenuVisible && !!iouReport && !!requestType && ( (0); @@ -125,7 +124,6 @@ function MoneyRequestReportPreview({ invoiceReceiverPersonalDetail={invoiceReceiverPersonalDetail} invoiceReceiverPolicy={invoiceReceiverPolicy} lastTransactionViolations={lastTransactionViolations} - isDelegateAccessRestricted={isDelegateAccessRestricted} renderTransactionItem={renderItem} onCarouselLayout={(e: LayoutChangeEvent) => { setCurrentWidth(e.nativeEvent.layout.width); diff --git a/src/components/ReportActionItem/MoneyRequestReportPreview/types.ts b/src/components/ReportActionItem/MoneyRequestReportPreview/types.ts index 922b910ddb1b..cf9c6bf59178 100644 --- a/src/components/ReportActionItem/MoneyRequestReportPreview/types.ts +++ b/src/components/ReportActionItem/MoneyRequestReportPreview/types.ts @@ -67,7 +67,6 @@ type MoneyRequestReportPreviewContentOnyxProps = { policy: OnyxEntry; invoiceReceiverPersonalDetail: OnyxEntry; lastTransactionViolations: TransactionViolations; - isDelegateAccessRestricted: boolean; }; type MoneyRequestReportPreviewContentProps = MoneyRequestReportPreviewContentOnyxProps & diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index 55eae6c81e99..78b7da245f1a 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -1,5 +1,5 @@ import truncate from 'lodash/truncate'; -import React, {useCallback, useEffect, useMemo, useState} from 'react'; +import React, {useCallback, useContext, useEffect, useMemo, useState} from 'react'; import type {StyleProp, ViewStyle} from 'react-native'; import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; @@ -100,6 +100,7 @@ import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage'; import ExportWithDropdownMenu from './ExportWithDropdownMenu'; import type {PendingMessageProps} from './MoneyRequestPreview/types'; import ReportActionItemImages from './ReportActionItemImages'; +import { DelegateNoAccessContext } from '@components/DelegateNoAccessModalProvider'; type ReportPreviewProps = { /** All the data of the action */ @@ -267,8 +268,7 @@ function ReportPreview({ // The submit button should be success green color only if the user is submitter and the policy does not have Scheduled Submit turned on const isWaitingForSubmissionFromCurrentUser = useMemo(() => isWaitingForSubmissionFromCurrentUserReportUtils(chatReport, policy), [chatReport, policy]); - const {isDelegateAccessRestricted} = useDelegateUserDetails(); - const [isNoDelegateAccessMenuVisible, setIsNoDelegateAccessMenuVisible] = useState(false); + const {isDelegateAccessRestricted, showDelegateNoAccessModal} = useContext(DelegateNoAccessContext); const confirmPayment = useCallback( (type: PaymentMethodType | undefined, payAsBusiness?: boolean, methodID?: number, paymentMethod?: PaymentMethod) => { @@ -278,7 +278,7 @@ function ReportPreview({ setPaymentType(type); setRequestType(CONST.IOU.REPORT_ACTION_TYPE.PAY); if (isDelegateAccessRestricted) { - setIsNoDelegateAccessMenuVisible(true); + showDelegateNoAccessModal(); } else if (hasHeldExpensesReportUtils(iouReport?.reportID)) { setIsHoldMenuVisible(true); } else if (chatReport && iouReport) { @@ -296,7 +296,7 @@ function ReportPreview({ const confirmApproval = () => { setRequestType(CONST.IOU.REPORT_ACTION_TYPE.APPROVE); if (isDelegateAccessRestricted) { - setIsNoDelegateAccessMenuVisible(true); + showDelegateNoAccessModal(); } else if (hasHeldExpensesReportUtils(iouReport?.reportID)) { setIsHoldMenuVisible(true); } else { @@ -739,10 +739,6 @@ function ReportPreview({ - setIsNoDelegateAccessMenuVisible(false)} - /> {isHoldMenuVisible && !!iouReport && requestType !== undefined && ( Date: Thu, 12 Jun 2025 17:10:23 +0100 Subject: [PATCH 07/12] remove unused components, update delegatenoaccess modal to include isDelegateAccessRestricted --- src/components/DelegateNoAccessModal.tsx | 39 ------------------- .../DelegateNoAccessModalProvider.tsx | 16 +++++--- src/hooks/useDelegateUserDetails.ts | 18 --------- 3 files changed, 11 insertions(+), 62 deletions(-) delete mode 100644 src/components/DelegateNoAccessModal.tsx delete mode 100644 src/hooks/useDelegateUserDetails.ts diff --git a/src/components/DelegateNoAccessModal.tsx b/src/components/DelegateNoAccessModal.tsx deleted file mode 100644 index ae5a98293407..000000000000 --- a/src/components/DelegateNoAccessModal.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import React from 'react'; -import useDelegateUserDetails from '@hooks/useDelegateUserDetails'; -import useLocalize from '@hooks/useLocalize'; -import CONST from '@src/CONST'; -import ConfirmModal from './ConfirmModal'; -import Text from './Text'; -import TextLink from './TextLink'; - -type DelegateNoAccessModalProps = { - isNoDelegateAccessMenuVisible: boolean; - onClose: () => void; -}; - -export default function DelegateNoAccessModal({isNoDelegateAccessMenuVisible = false, onClose}: DelegateNoAccessModalProps) { - const {translate} = useLocalize(); - const {delegatorEmail} = useDelegateUserDetails(); - const noDelegateAccessPromptStart = translate('delegate.notAllowedMessageStart'); - const noDelegateAccessHyperLinked = translate('delegate.notAllowedMessageHyperLinked'); - const noDelegateAccessPromptEnd = translate('delegate.notAllowedMessageEnd', {accountOwnerEmail: delegatorEmail ?? ''}); - const delegateNoAccessPrompt = ( - - {noDelegateAccessPromptStart} - {noDelegateAccessHyperLinked} - {noDelegateAccessPromptEnd} - - ); - - return ( - - ); -} diff --git a/src/components/DelegateNoAccessModalProvider.tsx b/src/components/DelegateNoAccessModalProvider.tsx index 72c07f7bc6a8..b773939d7e17 100644 --- a/src/components/DelegateNoAccessModalProvider.tsx +++ b/src/components/DelegateNoAccessModalProvider.tsx @@ -1,5 +1,4 @@ import React, {createContext, useMemo, useState} from 'react'; -import useDelegateUserDetails from '@hooks/useDelegateUserDetails'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import CONST from '@src/CONST'; @@ -7,34 +6,41 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ConfirmModal from './ConfirmModal'; import Text from './Text'; import TextLink from './TextLink'; +import AccountUtils from '@libs/AccountUtils'; +import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; type DelegateNoAccessContextType = { - showDelegateNoAccessModal: () => void; isActingAsDelegate: boolean; + isDelegateAccessRestricted: boolean, + showDelegateNoAccessModal: () => void; }; const DelegateNoAccessContext = createContext({ - showDelegateNoAccessModal: () => {}, isActingAsDelegate: false, + isDelegateAccessRestricted: false, + showDelegateNoAccessModal: () => {}, }); function DelegateNoAccessModalProvider({children}: React.PropsWithChildren) { const {translate} = useLocalize(); - const {delegatorEmail} = useDelegateUserDetails(); const [isModalOpen, setIsModalOpen] = useState(false); + const currentUserDetails = useCurrentUserPersonalDetails(); + const delegatorEmail = currentUserDetails?.login ?? ''; const [account] = useOnyx(ONYXKEYS.ACCOUNT, {canBeMissing: true}); const isActingAsDelegate = !!account?.delegatedAccess?.delegate; + const isDelegateAccessRestricted = isActingAsDelegate && AccountUtils.isDelegateOnlySubmitter(account); const delegateNoAccessPrompt = ( {translate('delegate.notAllowedMessageStart')} {translate('delegate.notAllowedMessageHyperLinked')} - {translate('delegate.notAllowedMessageEnd', {accountOwnerEmail: delegatorEmail ?? ''})} + {translate('delegate.notAllowedMessageEnd', {accountOwnerEmail: delegatorEmail})} ); const contextValue = useMemo( () => ({ isActingAsDelegate, + isDelegateAccessRestricted, showDelegateNoAccessModal: () => setIsModalOpen(true), }), [isActingAsDelegate], diff --git a/src/hooks/useDelegateUserDetails.ts b/src/hooks/useDelegateUserDetails.ts deleted file mode 100644 index 6e90f2906277..000000000000 --- a/src/hooks/useDelegateUserDetails.ts +++ /dev/null @@ -1,18 +0,0 @@ -import {useOnyx} from 'react-native-onyx'; -import AccountUtils from '@libs/AccountUtils'; -import ONYXKEYS from '@src/ONYXKEYS'; -import useCurrentUserPersonalDetails from './useCurrentUserPersonalDetails'; - -function useDelegateUserDetails() { - const currentUserDetails = useCurrentUserPersonalDetails(); - const [currentUserAccountDetails] = useOnyx(ONYXKEYS.ACCOUNT); - const isDelegateAccessRestricted = AccountUtils.isDelegateOnlySubmitter(currentUserAccountDetails); - const delegatorEmail = currentUserDetails?.login; - - return { - isDelegateAccessRestricted, - delegatorEmail, - }; -} - -export default useDelegateUserDetails; From 7b12a83c60c3ffa9b57793b4574f27863c258fc0 Mon Sep 17 00:00:00 2001 From: NikkiWines Date: Thu, 12 Jun 2025 17:26:09 +0100 Subject: [PATCH 08/12] style/lint --- .../DelegateNoAccessModalProvider.tsx | 8 +++---- src/components/MoneyReportHeader.tsx | 6 ++---- .../MoneyRequestReportPreviewContent.tsx | 5 ++--- .../MoneyRequestReportPreview/index.tsx | 1 - .../ReportActionItem/ReportPreview.tsx | 6 ++---- .../AttachmentPickerWithMenuItems.tsx | 10 ++++----- .../Profile/Contacts/ContactMethodsPage.tsx | 5 ++--- .../CardSectionDataEmpty/index.tsx | 18 +++++++--------- .../index.tsx | 21 +++++++------------ .../SubscriptionSettings/index.tsx | 3 +-- .../WorkspaceCompanyCardPageEmptyState.tsx | 5 ++--- .../WorkspaceCompanyCardsPage.tsx | 1 - .../WorkspaceExpensifyCardListPage.tsx | 3 +-- .../WorkspaceExpensifyCardPageEmptyState.tsx | 2 +- 14 files changed, 36 insertions(+), 58 deletions(-) diff --git a/src/components/DelegateNoAccessModalProvider.tsx b/src/components/DelegateNoAccessModalProvider.tsx index b773939d7e17..4ace3c5758f8 100644 --- a/src/components/DelegateNoAccessModalProvider.tsx +++ b/src/components/DelegateNoAccessModalProvider.tsx @@ -1,17 +1,17 @@ import React, {createContext, useMemo, useState} from 'react'; +import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; +import AccountUtils from '@libs/AccountUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ConfirmModal from './ConfirmModal'; import Text from './Text'; import TextLink from './TextLink'; -import AccountUtils from '@libs/AccountUtils'; -import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; type DelegateNoAccessContextType = { isActingAsDelegate: boolean; - isDelegateAccessRestricted: boolean, + isDelegateAccessRestricted: boolean; showDelegateNoAccessModal: () => void; }; @@ -43,7 +43,7 @@ function DelegateNoAccessModalProvider({children}: React.PropsWithChildren) { isDelegateAccessRestricted, showDelegateNoAccessModal: () => setIsModalOpen(true), }), - [isActingAsDelegate], + [isActingAsDelegate, isDelegateAccessRestricted], ); return ( diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index fb4293349325..b27aa464017d 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -79,7 +79,6 @@ import { } from '@userActions/IOU'; import {markAsCash as markAsCashAction} from '@userActions/Transaction'; import CONST from '@src/CONST'; -import useDelegateUserDetails from '@src/hooks/useDelegateUserDetails'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Route} from '@src/ROUTES'; import ROUTES from '@src/ROUTES'; @@ -94,7 +93,7 @@ import ButtonWithDropdownMenu from './ButtonWithDropdownMenu'; import type {DropdownOption} from './ButtonWithDropdownMenu/types'; import ConfirmModal from './ConfirmModal'; import DecisionModal from './DecisionModal'; -import DelegateNoAccessModal from './DelegateNoAccessModal'; +import {DelegateNoAccessContext} from './DelegateNoAccessModalProvider'; import Header from './Header'; import HeaderWithBackButton from './HeaderWithBackButton'; import Icon from './Icon'; @@ -113,7 +112,6 @@ import ProcessMoneyReportHoldMenu from './ProcessMoneyReportHoldMenu'; import {useSearchContext} from './Search/SearchContext'; import AnimatedSettlementButton from './SettlementButton/AnimatedSettlementButton'; import Text from './Text'; -import { DelegateNoAccessContext } from './DelegateNoAccessModalProvider'; type MoneyReportHeaderProps = { /** The report currently being looked at */ @@ -321,7 +319,7 @@ function MoneyReportHeader({ payMoneyRequest(type, chatReport, moneyRequestReport, true); } }, - [chatReport, isAnyTransactionOnHold, isDelegateAccessRestricted, isInvoiceReport, moneyRequestReport, startAnimation], + [chatReport, isAnyTransactionOnHold, isDelegateAccessRestricted, showDelegateNoAccessModal, isInvoiceReport, moneyRequestReport, startAnimation], ); const confirmApproval = () => { diff --git a/src/components/ReportActionItem/MoneyRequestReportPreview/MoneyRequestReportPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestReportPreview/MoneyRequestReportPreviewContent.tsx index 6d92f27508d8..87549dea7cf3 100644 --- a/src/components/ReportActionItem/MoneyRequestReportPreview/MoneyRequestReportPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestReportPreview/MoneyRequestReportPreviewContent.tsx @@ -6,7 +6,7 @@ import type {LayoutRectangle} from 'react-native/Libraries/Types/CoreEventTypes' import Button from '@components/Button'; import {getButtonRole} from '@components/Button/utils'; import ButtonWithDropdownMenu from '@components/ButtonWithDropdownMenu'; -import DelegateNoAccessModal from '@components/DelegateNoAccessModal'; +import {DelegateNoAccessContext} from '@components/DelegateNoAccessModalProvider'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import ImageSVG from '@components/ImageSVG'; @@ -74,7 +74,6 @@ import type {Transaction} from '@src/types/onyx'; import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage'; import EmptyMoneyRequestReportPreview from './EmptyMoneyRequestReportPreview'; import type {MoneyRequestReportPreviewContentProps} from './types'; -import { DelegateNoAccessContext } from '@components/DelegateNoAccessModalProvider'; type WebLayoutNativeEvent = { layout: LayoutRectangle; @@ -212,7 +211,7 @@ function MoneyRequestReportPreviewContent({ } } }, - [chatReport, iouReport, isDelegateAccessRestricted, startAnimation], + [chatReport, iouReport, isDelegateAccessRestricted, showDelegateNoAccessModal, startAnimation], ); const confirmApproval = () => { diff --git a/src/components/ReportActionItem/MoneyRequestReportPreview/index.tsx b/src/components/ReportActionItem/MoneyRequestReportPreview/index.tsx index 89bca0a90080..033d6a0babe0 100644 --- a/src/components/ReportActionItem/MoneyRequestReportPreview/index.tsx +++ b/src/components/ReportActionItem/MoneyRequestReportPreview/index.tsx @@ -2,7 +2,6 @@ import React, {useCallback, useMemo, useState} from 'react'; import type {LayoutChangeEvent, ListRenderItem} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import TransactionPreview from '@components/ReportActionItem/TransactionPreview'; -import useDelegateUserDetails from '@hooks/useDelegateUserDetails'; import usePolicy from '@hooks/usePolicy'; import useReportWithTransactionsAndViolations from '@hooks/useReportWithTransactionsAndViolations'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index 78b7da245f1a..fb42093bc99b 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -7,7 +7,7 @@ import Animated, {useAnimatedStyle, useSharedValue, withDelay, withSpring, withT import Button from '@components/Button'; import {getButtonRole} from '@components/Button/utils'; import ButtonWithDropdownMenu from '@components/ButtonWithDropdownMenu'; -import DelegateNoAccessModal from '@components/DelegateNoAccessModal'; +import {DelegateNoAccessContext} from '@components/DelegateNoAccessModalProvider'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import type {PaymentMethod} from '@components/KYCWall/types'; @@ -19,7 +19,6 @@ import {useSearchContext} from '@components/Search/SearchContext'; import AnimatedSettlementButton from '@components/SettlementButton/AnimatedSettlementButton'; import {showContextMenuForReport} from '@components/ShowContextMenuContext'; import Text from '@components/Text'; -import useDelegateUserDetails from '@hooks/useDelegateUserDetails'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import usePaymentAnimations from '@hooks/usePaymentAnimations'; @@ -100,7 +99,6 @@ import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage'; import ExportWithDropdownMenu from './ExportWithDropdownMenu'; import type {PendingMessageProps} from './MoneyRequestPreview/types'; import ReportActionItemImages from './ReportActionItemImages'; -import { DelegateNoAccessContext } from '@components/DelegateNoAccessModalProvider'; type ReportPreviewProps = { /** All the data of the action */ @@ -290,7 +288,7 @@ function ReportPreview({ } } }, - [chatReport, iouReport, isDelegateAccessRestricted, startAnimation], + [chatReport, iouReport, isDelegateAccessRestricted, showDelegateNoAccessModal, startAnimation], ); const confirmApproval = () => { diff --git a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx index 93c7a6a16be2..d08f031995e4 100644 --- a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx +++ b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx @@ -1,10 +1,11 @@ import {useIsFocused} from '@react-navigation/native'; -import React, {useCallback, useEffect, useMemo, useState} from 'react'; +import React, {useCallback, useContext, useEffect, useMemo} from 'react'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {useOnyx} from 'react-native-onyx'; import type {FileObject} from '@components/AttachmentModal'; import AttachmentPicker from '@components/AttachmentPicker'; +import {DelegateNoAccessContext} from '@components/DelegateNoAccessModalProvider'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import type {PopoverMenuItem} from '@components/PopoverMenu'; @@ -26,10 +27,8 @@ import {startMoneyRequest} from '@userActions/IOU'; import {close} from '@userActions/Modal'; import {createNewReport, setIsComposerFullSize} from '@userActions/Report'; import {clearOutTaskInfoAndNavigate} from '@userActions/Task'; -import DelegateNoAccessModal from '@src/components/DelegateNoAccessModal'; import type {IOUType} from '@src/CONST'; import CONST from '@src/CONST'; -import useDelegateUserDetails from '@src/hooks/useDelegateUserDetails'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type * as OnyxTypes from '@src/types/onyx'; @@ -128,8 +127,7 @@ function AttachmentPickerWithMenuItems({ const {translate} = useLocalize(); const {windowHeight, windowWidth} = useWindowDimensions(); const {shouldUseNarrowLayout} = useResponsiveLayout(); - const {isDelegateAccessRestricted} = useDelegateUserDetails(); - const [isNoDelegateAccessMenuVisible, setIsNoDelegateAccessMenuVisible] = useState(false); + const {isDelegateAccessRestricted, showDelegateNoAccessModal} = useContext(DelegateNoAccessContext); const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID}`, {canBeMissing: true}); const selectOption = useCallback( @@ -193,7 +191,7 @@ function AttachmentPickerWithMenuItems({ })); return moneyRequestOptionsList.filter((item, index, self) => index === self.findIndex((t) => t.text === item.text)); - }, [translate, shouldUseNarrowLayout, report, policy, reportParticipantIDs, selectOption, isDelegateAccessRestricted]); + }, [translate, shouldUseNarrowLayout, report, policy, reportParticipantIDs, selectOption, isDelegateAccessRestricted, showDelegateNoAccessModal]); const createReportOption: PopoverMenuItem[] = useMemo(() => { if (!isPolicyExpenseChat(report) || !isPaidGroupPolicy(report) || !isReportOwner(report)) { diff --git a/src/pages/settings/Profile/Contacts/ContactMethodsPage.tsx b/src/pages/settings/Profile/Contacts/ContactMethodsPage.tsx index e59d2683ef78..0bdd2b31544c 100644 --- a/src/pages/settings/Profile/Contacts/ContactMethodsPage.tsx +++ b/src/pages/settings/Profile/Contacts/ContactMethodsPage.tsx @@ -1,10 +1,9 @@ import {Str} from 'expensify-common'; -import React, {useCallback, useContext, useState} from 'react'; +import React, {useCallback, useContext} from 'react'; import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import Button from '@components/Button'; import CopyTextToClipboard from '@components/CopyTextToClipboard'; -import DelegateNoAccessModal from '@components/DelegateNoAccessModal'; import {DelegateNoAccessContext} from '@components/DelegateNoAccessModalProvider'; import FixedFooter from '@components/FixedFooter'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; @@ -107,7 +106,7 @@ function ContactMethodsPage({route}: ContactMethodsPageProps) { } Navigation.navigate(ROUTES.SETTINGS_NEW_CONTACT_METHOD.getRoute(navigateBackTo)); - }, [navigateBackTo, isActingAsDelegate, isAccountLocked, isUserValidated, showLockedAccountModal]); + }, [navigateBackTo, isActingAsDelegate, showDelegateNoAccessModal, isAccountLocked, isUserValidated, showLockedAccountModal]); return ( -