diff --git a/src/CONST/index.ts b/src/CONST/index.ts index b52f7bc9bb79..d8fe12e2f8dd 100755 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -7044,6 +7044,7 @@ const CONST = { HAS_SUBSCRIPTION_INFO: 'hasSubscriptionInfo', HAS_PHONE_NUMBER_ERROR: 'hasPhoneNumberError', HAS_CARD_CONNECTION_ERROR: 'hasCardConnectionError', + HAS_UBER_CREDENTIALS_ERROR: 'hasUberCredentialsError', }, DEBUG: { diff --git a/src/hooks/useGetReceiptPartnersIntegrationData.ts b/src/hooks/useGetReceiptPartnersIntegrationData.ts index 741ea847a1ba..eb9cf9e6e609 100644 --- a/src/hooks/useGetReceiptPartnersIntegrationData.ts +++ b/src/hooks/useGetReceiptPartnersIntegrationData.ts @@ -5,13 +5,13 @@ import useIsPolicyConnectedToUberReceiptPartner from './useIsPolicyConnectedToUb import useLocalize from './useLocalize'; import usePolicy from './usePolicy'; -export default function useGetReceiptPartnersIntegrationData({policyID}: {policyID?: string}) { +export default function useGetReceiptPartnersIntegrationData(policyID?: string) { const policy = usePolicy(policyID); const {translate} = useLocalize(); const uber = policy?.receiptPartners?.uber; const isUberConnected = useIsPolicyConnectedToUberReceiptPartner({policyID}); - const shouldShowEnterCredentialsError = isUberConnected && !!uber?.error; + const shouldShowEnterCredentialsError = !!uber?.error; const getReceiptPartnersIntegrationData = useCallback( (receiptPartnerName: string) => { diff --git a/src/hooks/useWorkspacesTabIndicatorStatus.ts b/src/hooks/useWorkspacesTabIndicatorStatus.ts index a6c8f0add953..5c5c56269d80 100644 --- a/src/hooks/useWorkspacesTabIndicatorStatus.ts +++ b/src/hooks/useWorkspacesTabIndicatorStatus.ts @@ -2,7 +2,7 @@ import {useMemo} from 'react'; import type {ValueOf} from 'type-fest'; import {isConnectionInProgress} from '@libs/actions/connections'; import {shouldShowQBOReimbursableExportDestinationAccountError} from '@libs/actions/connections/QuickbooksOnline'; -import {shouldShowCustomUnitsError, shouldShowEmployeeListError, shouldShowPolicyError, shouldShowSyncError} from '@libs/PolicyUtils'; +import {getUberConnectionErrorDirectlyFromPolicy, shouldShowCustomUnitsError, shouldShowEmployeeListError, shouldShowPolicyError, shouldShowSyncError} from '@libs/PolicyUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import useOnyx from './useOnyx'; @@ -23,7 +23,6 @@ function useWorkspacesTabIndicatorStatus(): WorkspacesTabIndicatorStatusResult { // If a policy was just deleted from Onyx, then Onyx will pass a null value to the props, and // those should be cleaned out before doing any error checking const cleanPolicies = useMemo(() => Object.fromEntries(Object.entries(policies ?? {}).filter(([, policy]) => policy?.id)), [policies]); - const policyErrors = { [CONST.INDICATOR_STATUS.HAS_POLICY_ERRORS]: Object.values(cleanPolicies).find(shouldShowPolicyError), [CONST.INDICATOR_STATUS.HAS_CUSTOM_UNITS_ERROR]: Object.values(cleanPolicies).find(shouldShowCustomUnitsError), @@ -32,6 +31,7 @@ function useWorkspacesTabIndicatorStatus(): WorkspacesTabIndicatorStatusResult { shouldShowSyncError(cleanPolicy, isConnectionInProgress(allConnectionSyncProgresses?.[`${ONYXKEYS.COLLECTION.POLICY_CONNECTION_SYNC_PROGRESS}${cleanPolicy?.id}`], cleanPolicy)), ), [CONST.INDICATOR_STATUS.HAS_QBO_EXPORT_ERROR]: Object.values(cleanPolicies).find(shouldShowQBOReimbursableExportDestinationAccountError), + [CONST.INDICATOR_STATUS.HAS_UBER_CREDENTIALS_ERROR]: Object.values(cleanPolicies).find(getUberConnectionErrorDirectlyFromPolicy), }; // All of the error & info-checking methods are put into an array. This is so that using _.some() will return diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 598af1b8d380..05db58c30558 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -302,6 +302,12 @@ function isPolicyPayer(policy: OnyxEntry, currentUserLogin: string | und return false; } +function getUberConnectionErrorDirectlyFromPolicy(policy: OnyxEntry) { + const receiptUber = policy?.receiptPartners?.uber; + + return !!receiptUber?.error; +} + function isExpensifyTeam(email: string | undefined): boolean { const emailDomain = Str.extractEmailDomain(email ?? ''); return emailDomain === CONST.EXPENSIFY_PARTNER_NAME || emailDomain === CONST.EMAIL.GUIDES_DOMAIN; @@ -1577,6 +1583,7 @@ export { isPolicyAuditor, isPolicyEmployee, isPolicyFeatureEnabled, + getUberConnectionErrorDirectlyFromPolicy, isPolicyOwner, isPolicyMember, isPolicyPayer, diff --git a/src/pages/workspace/WorkspaceInitialPage.tsx b/src/pages/workspace/WorkspaceInitialPage.tsx index 6eb0e903d69e..1f89f3b59ca8 100644 --- a/src/pages/workspace/WorkspaceInitialPage.tsx +++ b/src/pages/workspace/WorkspaceInitialPage.tsx @@ -121,7 +121,7 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, route}: Workspac .filter((domainID): domainID is number => !!domainID); const {login, accountID} = useCurrentUserPersonalDetails(); const hasSyncError = shouldShowSyncError(policy, isConnectionInProgress(connectionSyncProgress, policy)); - const {shouldShowEnterCredentialsError} = useGetReceiptPartnersIntegrationData({policyID: policy?.id}); + const {shouldShowEnterCredentialsError} = useGetReceiptPartnersIntegrationData(policy?.id); const waitForNavigate = useWaitForNavigation(); const {singleExecution, isExecuting} = useSingleExecution(); const activeRoute = useNavigationState((state) => findFocusedRoute(state)?.name); diff --git a/src/pages/workspace/WorkspacesListPage.tsx b/src/pages/workspace/WorkspacesListPage.tsx index aa63b3d57142..3f60679cea7e 100755 --- a/src/pages/workspace/WorkspacesListPage.tsx +++ b/src/pages/workspace/WorkspacesListPage.tsx @@ -44,7 +44,7 @@ import usePreloadFullScreenNavigators from '@libs/Navigation/AppNavigator/usePre import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackRouteProp} from '@libs/Navigation/PlatformStackNavigation/types'; import type {AuthScreensParamList} from '@libs/Navigation/types'; -import {getDefaultApprover, getPolicy, getPolicyBrickRoadIndicatorStatus, isPolicyAdmin, shouldShowPolicy} from '@libs/PolicyUtils'; +import {getDefaultApprover, getPolicy, getPolicyBrickRoadIndicatorStatus, getUberConnectionErrorDirectlyFromPolicy, isPolicyAdmin, shouldShowPolicy} from '@libs/PolicyUtils'; import {getDefaultWorkspaceAvatar} from '@libs/ReportUtils'; import shouldRenderTransferOwnerButton from '@libs/shouldRenderTransferOwnerButton'; import {shouldCalculateBillNewDot as shouldCalculateBillNewDotFn} from '@libs/SubscriptionUtils'; @@ -113,7 +113,7 @@ function WorkspacesListPage() { const shouldShowLoadingIndicator = isLoadingApp && !isOffline; const route = useRoute>(); const [fundList] = useOnyx(ONYXKEYS.FUND_LIST, {canBeMissing: true}); - const [duplicateWorkspace] = useOnyx(ONYXKEYS.DUPLICATE_WORKSPACE, {canBeMissing: false}); + const [duplicateWorkspace] = useOnyx(ONYXKEYS.DUPLICATE_WORKSPACE, {canBeMissing: true}); const {isRestrictedToPreferredPolicy} = usePreferredPolicy(); // This hook preloads the screens of adjacent tabs to make changing tabs faster. @@ -354,6 +354,8 @@ function WorkspacesListPage() { return Object.values(policies) .filter((policy): policy is PolicyType => shouldShowPolicy(policy, isOffline, session?.email)) .map((policy): WorkspaceItem => { + const receiptUberBrickRoadIndicator = getUberConnectionErrorDirectlyFromPolicy(policy) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined; + if (policy?.isJoinRequestPending && policy?.policyDetailsForNonMembers) { const policyInfo = Object.values(policy.policyDetailsForNonMembers).at(0) as PolicyDetailsForNonMembers; const id = Object.keys(policy.policyDetailsForNonMembers).at(0); @@ -381,6 +383,7 @@ function WorkspacesListPage() { brickRoadIndicator: !isPolicyAdmin(policy) ? undefined : (reimbursementAccountBrickRoadIndicator ?? + receiptUberBrickRoadIndicator ?? getPolicyBrickRoadIndicatorStatus( policy, isConnectionInProgress(allConnectionSyncProgresses?.[`${ONYXKEYS.COLLECTION.POLICY_CONNECTION_SYNC_PROGRESS}${policy.id}`], policy), diff --git a/src/pages/workspace/receiptPartners/WorkspaceReceiptPartnersPage.tsx b/src/pages/workspace/receiptPartners/WorkspaceReceiptPartnersPage.tsx index d4e2240f4524..3a15ca5b175e 100644 --- a/src/pages/workspace/receiptPartners/WorkspaceReceiptPartnersPage.tsx +++ b/src/pages/workspace/receiptPartners/WorkspaceReceiptPartnersPage.tsx @@ -45,7 +45,7 @@ function WorkspaceReceiptPartnersPage({route}: WorkspaceReceiptPartnersPageProps const {isOffline} = useNetwork(); const threeDotsMenuContainerRef = useRef(null); const policy = usePolicy(policyID); - const {getReceiptPartnersIntegrationData, shouldShowEnterCredentialsError, isUberConnected} = useGetReceiptPartnersIntegrationData({policyID}); + const {getReceiptPartnersIntegrationData, shouldShowEnterCredentialsError, isUberConnected} = useGetReceiptPartnersIntegrationData(policyID); const [selectedPartner, setSelectedPartner] = useState(null); const isLoading = policy?.isLoading; const [isDisconnectModalOpen, setIsDisconnectModalOpen] = useState(false); diff --git a/tests/unit/PolicyUtilsTest.ts b/tests/unit/PolicyUtilsTest.ts index 95cbd5488767..29204206a876 100644 --- a/tests/unit/PolicyUtilsTest.ts +++ b/tests/unit/PolicyUtilsTest.ts @@ -13,6 +13,7 @@ import { getSubmitToAccountID, getTagList, getTagListByOrderWeight, + getUberConnectionErrorDirectlyFromPolicy, getUnitRateValue, isCurrentUserMemberOfAnyPolicy, isPolicyMemberWithoutPendingDelete, @@ -318,6 +319,50 @@ describe('PolicyUtils', () => { }); }); + describe('getUberConnectionErrorDirectlyFromPolicy', () => { + it('should return true if Uber connection is enabled and has an error', () => { + const policy: Policy = { + ...createRandomPolicy(1, CONST.POLICY.TYPE.TEAM), + receiptPartners: { + uber: { + enabled: true, + error: 'Some error', + connectFormData: 'Some data', + }, + }, + }; + + expect(getUberConnectionErrorDirectlyFromPolicy(policy)).toBe(true); + }); + + it('should return false if Uber connection is enabled but has no error', () => { + const policy: Policy = { + ...createRandomPolicy(1, CONST.POLICY.TYPE.TEAM), + receiptPartners: { + uber: { + enabled: true, + error: undefined, + connectFormData: 'Some data', + }, + }, + }; + + expect(getUberConnectionErrorDirectlyFromPolicy(policy)).toBe(false); + }); + + it('should return false if Uber connection does not exist', () => { + const policy: Policy = { + ...createRandomPolicy(1, CONST.POLICY.TYPE.TEAM), + }; + + expect(getUberConnectionErrorDirectlyFromPolicy(policy)).toBe(false); + }); + + it('should return false if policy is undefined', () => { + expect(getUberConnectionErrorDirectlyFromPolicy(undefined)).toBe(false); + }); + }); + describe('getUnitRateValue', () => { it('should return an empty string for NaN', () => { const rate = getUnitRateValue(toLocaleDigitMock, {rate: 'invalid' as unknown as number});